osgEarth 2.1.1

/home/cube/sources/osgearth/src/osgEarth/Profile.cpp

Go to the documentation of this file.
00001 /* -*-c++-*- */
00002 /* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
00003  * Copyright 2008-2010 Pelican Mapping
00004  * http://osgearth.org
00005  *
00006  * osgEarth is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU Lesser General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU Lesser General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU Lesser General Public License
00017  * along with this program.  If not, see <http://www.gnu.org/licenses/>
00018  */
00019 
00020 #include <osgEarth/Profile>
00021 #include <osgEarth/Registry>
00022 #include <osgEarth/TileKey>
00023 #include <osgEarth/Cube>
00024 #include <osgEarth/SpatialReference>
00025 #include <osgDB/FileNameUtils>
00026 #include <algorithm>
00027 #include <sstream>
00028 
00029 using namespace osgEarth;
00030 
00031 #define LC "[Profile] "
00032 
00033 //------------------------------------------------------------------------
00034 
00035 ProfileOptions::ProfileOptions( const ConfigOptions& options ) :
00036 ConfigOptions( options ),
00037 _namedProfile( "" ),
00038 _srsInitString( "" ),
00039 _vsrsInitString( "" ),
00040 _bounds( Bounds() ),
00041 _numTilesWideAtLod0( 1 ),
00042 _numTilesHighAtLod0( 1 )
00043 {
00044     fromConfig( _conf );
00045 }
00046 
00047 ProfileOptions::ProfileOptions( const std::string& namedProfile ) :
00048 _srsInitString( "" ),
00049 _vsrsInitString( "" ),
00050 _bounds( Bounds() ),
00051 _numTilesWideAtLod0( 1 ),
00052 _numTilesHighAtLod0( 1 )
00053 {
00054     _namedProfile = namedProfile; // don't set above
00055 }
00056 
00057 void
00058 ProfileOptions::fromConfig( const Config& conf )
00059 {
00060     if ( !conf.value().empty() )
00061         _namedProfile = conf.value();
00062 
00063     conf.getIfSet( "srs", _srsInitString );
00064     conf.getIfSet( "vsrs", _vsrsInitString );
00065 
00066     if ( conf.hasValue( "xmin" ) && conf.hasValue( "ymin" ) && conf.hasValue( "xmax" ) && conf.hasValue( "ymax" ) )
00067     {
00068         _bounds = Bounds(
00069             conf.value<double>( "xmin", 0 ),
00070             conf.value<double>( "ymin", 0 ),
00071             conf.value<double>( "xmax", 0 ),
00072             conf.value<double>( "ymax", 0 ) );
00073     }
00074 
00075     conf.getIfSet( "num_tiles_wide_at_lod_0", _numTilesWideAtLod0 );
00076     conf.getIfSet( "num_tiles_high_at_lod_0", _numTilesHighAtLod0 );
00077 }
00078 
00079 Config
00080 ProfileOptions::getConfig() const
00081 {
00082     Config conf( "profile" );
00083     if ( _namedProfile.isSet() )
00084     {
00085         conf.value() = _namedProfile.value();
00086     }
00087     else
00088     {
00089         conf.updateIfSet( "srs", _srsInitString );
00090         conf.updateIfSet( "vsrs", _vsrsInitString );
00091 
00092         if ( _bounds.isSet() )
00093         {
00094             conf.update( "xmin", toString(_bounds->xMin()) );
00095             conf.update( "ymin", toString(_bounds->yMin()) );
00096             conf.update( "xmax", toString(_bounds->xMax()) );
00097             conf.update( "ymax", toString(_bounds->yMax()) );
00098         }
00099 
00100         conf.updateIfSet( "num_tiles_wide_at_lod_0", _numTilesWideAtLod0 );
00101         conf.updateIfSet( "num_tiles_high_at_lod_0", _numTilesHighAtLod0 );
00102     }
00103     return conf;
00104 }
00105 
00106 bool
00107 ProfileOptions::defined() const
00108 {
00109     return _namedProfile.isSet() || _srsInitString.isSet();
00110 }
00111 
00112 /***********************************************************************/
00113 
00114 
00115 // FACTORY METHODS:
00116 
00117 const Profile*
00118 Profile::create(const std::string& srsInitString,
00119                 double xmin, double ymin, double xmax, double ymax,
00120                 const std::string& vsrsInitString,
00121                 unsigned int numTilesWideAtLod0,
00122                 unsigned int numTilesHighAtLod0)
00123 {
00124     return new Profile(
00125         SpatialReference::create( srsInitString ),
00126         xmin, ymin, xmax, ymax,
00127         VerticalSpatialReference::create( vsrsInitString ),
00128         numTilesWideAtLod0,
00129         numTilesHighAtLod0 );
00130 }
00131 
00132 const Profile*
00133 Profile::create(const SpatialReference* srs,
00134                 double xmin, double ymin, double xmax, double ymax,
00135                 const VerticalSpatialReference* vsrs,
00136                 unsigned int numTilesWideAtLod0,
00137                 unsigned int numTilesHighAtLod0)
00138 {
00139     return new Profile(
00140         srs,
00141         xmin, ymin, xmax, ymax,
00142         vsrs,
00143         numTilesWideAtLod0,
00144         numTilesHighAtLod0 );
00145 }
00146 
00147 const Profile*
00148 Profile::create(const SpatialReference* srs,
00149                 double xmin, double ymin, double xmax, double ymax,
00150                 double geoxmin, double geoymin, double geoxmax, double geoymax,
00151                 const VerticalSpatialReference* vsrs,
00152                 unsigned int numTilesWideAtLod0,
00153                 unsigned int numTilesHighAtLod0)
00154 {
00155     return new Profile(
00156         srs,
00157         xmin, ymin, xmax, ymax,
00158         geoxmin, geoymin, geoxmax, geoymax,
00159         vsrs,
00160         numTilesWideAtLod0,
00161         numTilesHighAtLod0 );
00162 }
00163 
00164 const Profile*
00165 Profile::create(const std::string& srsInitString,
00166                 const std::string& vsrsInitString,
00167                 unsigned int numTilesWideAtLod0,
00168                 unsigned int numTilesHighAtLod0)
00169 {
00170     const Profile* named = osgEarth::Registry::instance()->getNamedProfile( srsInitString );
00171     if ( named )
00172         return const_cast<Profile*>( named );
00173 
00174     osg::ref_ptr<const SpatialReference> srs = SpatialReference::create( srsInitString );
00175 
00176     osg::ref_ptr<const VerticalSpatialReference> vsrs = VerticalSpatialReference::create( vsrsInitString );
00177 
00178     if ( srs.valid() && srs->isGeographic() )
00179     {
00180         return new Profile(
00181             srs.get(),
00182             -180.0, -90.0, 180.0, 90.0,
00183             vsrs.get(),
00184             numTilesWideAtLod0, numTilesHighAtLod0 );
00185     }
00186     else if ( srs.valid() && srs->isMercator() )
00187     {
00188         // automatically figure out proper mercator extents:
00189         GDAL_SCOPED_LOCK;
00190         double e, dummy;
00191         srs->getGeographicSRS()->transform2D( 180.0, 0.0, srs.get(), e, dummy );
00192         return Profile::create( srs.get(), -e, -e, e, e, vsrs.get(), numTilesWideAtLod0, numTilesHighAtLod0 );
00193     }
00194     else
00195     {
00196         OE_WARN << LC << "Failed to create profile; SRS spec requires addition information: \"" << srsInitString << 
00197             std::endl;
00198     }
00199 
00200     return NULL;
00201 }
00202 
00203 const Profile*
00204 Profile::create( const ProfileOptions& options )
00205 {
00206     const Profile* result = 0L;
00207 
00208     // Check for a "well known named" profile:
00209     if ( options.namedProfile().isSet() )
00210     {
00211         result = osgEarth::Registry::instance()->getNamedProfile( options.namedProfile().value() );
00212     }
00213 
00214     // Next check for a user-defined extents:
00215     else if ( options.srsString().isSet() && options.bounds().isSet() )
00216     {
00217         if ( options.numTilesWideAtLod0().isSet() && options.numTilesHighAtLod0().isSet() )
00218         {
00219             result = Profile::create(
00220                 options.srsString().value(),
00221                 options.bounds()->xMin(),
00222                 options.bounds()->yMin(),
00223                 options.bounds()->xMax(),
00224                 options.bounds()->yMax(),
00225                 options.vsrsString().value(),
00226                 options.numTilesWideAtLod0().value(),
00227                 options.numTilesHighAtLod0().value() );
00228         }
00229         else
00230         {
00231             result = Profile::create(
00232                 options.srsString().value(),
00233                 options.bounds()->xMin(),
00234                 options.bounds()->yMin(),
00235                 options.bounds()->xMax(),
00236                 options.bounds()->yMax(),
00237                 options.vsrsString().value() );
00238         }
00239     }
00240 
00241     // Next try SRS with default extents
00242     else if ( options.srsString().isSet() )
00243     {
00244         result = Profile::create(
00245             options.srsString().value(),
00246             options.vsrsString().value() );
00247     }
00248 
00249     return result;
00250 }
00251 
00252 /****************************************************************************/
00253 
00254 
00255 Profile::Profile(const SpatialReference* srs,
00256                  double xmin, double ymin, double xmax, double ymax,
00257                  const VerticalSpatialReference* vsrs,
00258                  unsigned int numTilesWideAtLod0,
00259                  unsigned int numTilesHighAtLod0) :
00260 osg::Referenced( true ),
00261 _vsrs( vsrs )
00262 {
00263     _extent = GeoExtent( srs, xmin, ymin, xmax, ymax );
00264 
00265     _numTilesWideAtLod0 = numTilesWideAtLod0 != 0? numTilesWideAtLod0 : srs->isGeographic()? 2 : 1;
00266     _numTilesHighAtLod0 = numTilesHighAtLod0 != 0? numTilesHighAtLod0 : 1;
00267 
00268     // automatically calculate the lat/long extents:
00269     _latlong_extent = srs->isGeographic()?
00270         _extent :
00271         _extent.transform( _extent.getSRS()->getGeographicSRS() );
00272 
00273     if ( !_vsrs.valid() )
00274         _vsrs = Registry::instance()->getDefaultVSRS();
00275 }
00276 
00277 Profile::Profile(const SpatialReference* srs,
00278                  double xmin, double ymin, double xmax, double ymax,
00279                  double geo_xmin, double geo_ymin, double geo_xmax, double geo_ymax,
00280                  const VerticalSpatialReference* vsrs,
00281                  unsigned int numTilesWideAtLod0,
00282                  unsigned int numTilesHighAtLod0 ) :
00283 osg::Referenced( true ),
00284 _vsrs( vsrs )
00285 {
00286     _extent = GeoExtent( srs, xmin, ymin, xmax, ymax );
00287 
00288     _numTilesWideAtLod0 = numTilesWideAtLod0 != 0? numTilesWideAtLod0 : srs->isGeographic()? 2 : 1;
00289     _numTilesHighAtLod0 = numTilesHighAtLod0 != 0? numTilesHighAtLod0 : 1;
00290 
00291     _latlong_extent = GeoExtent( 
00292         srs->getGeographicSRS(),
00293         geo_xmin, geo_ymin, geo_xmax, geo_ymax );
00294 
00295     if ( !_vsrs.valid() )
00296         _vsrs = Registry::instance()->getDefaultVSRS();
00297 }
00298 
00299 Profile::ProfileType
00300 Profile::getProfileType() const
00301 {
00302     return
00303         _extent.isValid() && _extent.getSRS()->isGeographic() ? TYPE_GEODETIC :
00304         _extent.isValid() && _extent.getSRS()->isMercator() ? TYPE_MERCATOR :
00305         _extent.isValid() && _extent.getSRS()->isProjected() ? TYPE_LOCAL :
00306         TYPE_UNKNOWN;
00307 }
00308 
00309 bool
00310 Profile::isOK() const {
00311     return _extent.isValid();
00312 }
00313 
00314 const SpatialReference*
00315 Profile::getSRS() const {
00316     return _extent.getSRS();
00317 }
00318 
00319 const GeoExtent&
00320 Profile::getExtent() const {
00321     return _extent;
00322 }
00323 
00324 const GeoExtent&
00325 Profile::getLatLongExtent() const {
00326     return _latlong_extent;
00327 }
00328 
00329 std::string
00330 Profile::toString() const
00331 {
00332     std::stringstream buf;
00333     buf << "[srs=" << _extent.getSRS()->getName() << ", min=" << _extent.xMin() << "," << _extent.yMin()
00334         << " max=" << _extent.xMax() << "," << _extent.yMax()
00335         << " lod0=" << _numTilesWideAtLod0 << "," << _numTilesHighAtLod0
00336         << " vsrs=" << ( _vsrs.valid() ? _vsrs->getName() : "default" )
00337         << "]";
00338     std::string bufStr;
00339         bufStr = buf.str();
00340         return bufStr;
00341 }
00342 
00343 void
00344 Profile::getRootKeys( std::vector<TileKey>& out_keys ) const
00345 {
00346     out_keys.clear();
00347 
00348     for (unsigned int c = 0; c < _numTilesWideAtLod0; ++c)
00349     {
00350         for (unsigned int r = 0; r < _numTilesHighAtLod0; ++r)
00351         {
00352             //TODO: upgrade to support multi-face profile:
00353             out_keys.push_back( TileKey(0, c, r, this) ); // lod, x, y, profile
00354         }
00355     }
00356 }
00357 
00358 GeoExtent
00359 Profile::calculateExtent( unsigned int lod, unsigned int tileX, unsigned int tileY )
00360 {
00361     double width, height;
00362     getTileDimensions(lod, width, height);
00363 
00364     double xmin = getExtent().xMin() + (width * (double)tileX);
00365     double ymax = getExtent().yMax() - (height * (double)tileY);
00366     double xmax = xmin + width;
00367     double ymin = ymax - height;
00368 
00369     return GeoExtent( getSRS(), xmin, ymin, xmax, ymax );
00370 }
00371 
00372 //TODO: DEPRECATE THIS and replace by examining the SRS itself.
00373 Profile::ProfileType
00374 Profile::getProfileTypeFromSRS(const std::string& srs_string)
00375 {
00376     osg::ref_ptr<SpatialReference> srs = SpatialReference::create( srs_string );
00377     return 
00378         srs.valid() && srs->isGeographic()? Profile::TYPE_GEODETIC :
00379         srs.valid() && srs->isMercator()? Profile::TYPE_MERCATOR :
00380         srs.valid() && srs->isProjected()? Profile::TYPE_LOCAL :
00381         Profile::TYPE_UNKNOWN;
00382 }
00383 
00384 bool
00385 Profile::isEquivalentTo( const Profile* rhs ) const
00386 {
00387     return
00388         rhs &&
00389         _extent.isValid() && rhs->getExtent().isValid() &&
00390         _extent == rhs->getExtent() &&
00391         _numTilesWideAtLod0 == rhs->_numTilesWideAtLod0 &&
00392         _numTilesHighAtLod0 == rhs->_numTilesHighAtLod0;
00393 }
00394 
00395 void
00396 Profile::getTileDimensions(unsigned int lod, double& out_width, double& out_height) const
00397 {
00398     out_width  = (_extent.xMax() - _extent.xMin()) / (double)_numTilesWideAtLod0;
00399     out_height = (_extent.yMax() - _extent.yMin()) / (double)_numTilesHighAtLod0;
00400 
00401     for (unsigned int i = 0; i < lod; ++i)
00402     {
00403         out_width /= 2.0;
00404         out_height /= 2.0;
00405     }
00406 }
00407 
00408 void
00409 Profile::getNumTiles(unsigned int lod, unsigned int& out_tiles_wide, unsigned int& out_tiles_high) const
00410 {
00411     out_tiles_wide = _numTilesWideAtLod0;
00412     out_tiles_high = _numTilesHighAtLod0;
00413 
00414     for (unsigned int i = 0; i < lod; ++i)
00415     {
00416         out_tiles_wide *= 2;
00417         out_tiles_high *= 2;
00418     }
00419 }
00420 
00421 unsigned int
00422 Profile::getLevelOfDetailForHorizResolution( double resolution, int tileSize ) const
00423 {
00424     if ( tileSize <= 0 || resolution <= 0.0 ) return 0;
00425 
00426     double tileRes = (_extent.width() / (double)_numTilesWideAtLod0) / (double)tileSize;
00427     unsigned int level = 0;
00428     while( tileRes > resolution ) 
00429     {
00430         level++;
00431         tileRes *= 0.5;
00432     }
00433     return level;
00434 }
00435 
00436 TileKey
00437 Profile::createTileKey( double x, double y, unsigned int level ) const
00438 {
00439     if ( _extent.contains( x, y ) )
00440     {
00441         int tilesX = (int)_numTilesWideAtLod0 * (1 << (int)level);
00442         int tilesY = (int)_numTilesHighAtLod0 * (1 << (int)level);
00443 
00444         double rx = (x - _extent.xMin()) / _extent.width();
00445         int tileX = osg::clampBelow( (int)(rx * (double)tilesX), tilesX-1 );
00446         double ry = (y - _extent.yMin()) / _extent.height();
00447         int tileY = osg::clampBelow( (int)((1.0-ry) * (double)tilesY), tilesY-1 );
00448 
00449         return TileKey( level, tileX, tileY, this );
00450     }
00451     else
00452     {
00453         return TileKey::INVALID;
00454     }
00455 }
00456 
00457 GeoExtent
00458 Profile::clampAndTransformExtent( const GeoExtent& input, bool* out_clamped ) const
00459 {
00460     if ( out_clamped )
00461         *out_clamped = false;
00462 
00463     // do the clamping in LAT/LONG.
00464     const SpatialReference* geo_srs = getSRS()->getGeographicSRS();
00465 
00466     // get the input in lat/long:
00467     GeoExtent gcs_input =
00468         input.getSRS()->isGeographic()?
00469         input :
00470         input.transform( geo_srs );
00471 
00472     if ( !gcs_input.isValid() )
00473         return GeoExtent::INVALID;
00474 
00475     // clamp it to the profile's extents:
00476     GeoExtent clamped_gcs_input = GeoExtent(
00477         gcs_input.getSRS(),
00478         osg::clampBetween( gcs_input.xMin(), _latlong_extent.xMin(), _latlong_extent.xMax() ),
00479         osg::clampBetween( gcs_input.yMin(), _latlong_extent.yMin(), _latlong_extent.yMax() ),
00480         osg::clampBetween( gcs_input.xMax(), _latlong_extent.xMin(), _latlong_extent.xMax() ),
00481         osg::clampBetween( gcs_input.yMax(), _latlong_extent.yMin(), _latlong_extent.yMax() ) );
00482 
00483     if ( out_clamped )
00484         *out_clamped = (clamped_gcs_input != gcs_input);
00485 
00486     // finally, transform the clamped extent into this profile's SRS and return it.
00487     GeoExtent result =
00488         clamped_gcs_input.getSRS()->isEquivalentTo( this->getSRS() )?
00489         clamped_gcs_input :
00490         clamped_gcs_input.transform( this->getSRS() );
00491 
00492     if (result.isValid())
00493     {
00494         OE_DEBUG << LC << "clamp&xform: input=" << input.toString() << ", output=" << result.toString() << std::endl;
00495     }
00496 
00497     return result;
00498 }
00499 
00500 
00501 void
00502 Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& out_intersectingKeys) const
00503 {
00504     // assume a non-crossing extent here.
00505     if ( key_ext.crossesDateLine() )
00506     {
00507         OE_WARN << "Profile::addIntersectingTiles cannot process date-line cross" << std::endl;
00508         return;
00509     }
00510 
00511     double keyWidth = key_ext.width();
00512     double keyHeight = key_ext.height();
00513     double keyArea = keyWidth * keyHeight;
00514 
00515     // bail out if the key has a null extent. This might happen is the original key represents an
00516     // area in one profile that is out of bounds in this profile.
00517     if ( keyArea <= 0.0 )
00518         return;
00519 
00520     int destLOD = 1;
00521     double destTileWidth, destTileHeight;
00522 
00523     int currLOD = 0;
00524     destLOD = currLOD;
00525     getTileDimensions(destLOD, destTileWidth, destTileHeight);
00526 
00527     //Find the LOD that most closely matches the area of the incoming key without going under.
00528     while (true)
00529     {
00530         currLOD++;
00531         double w, h;
00532         getTileDimensions(currLOD, w,h);
00533         //OE_INFO << std::fixed << "  " << currLOD << "(" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
00534         double a = w * h;
00535         if (a < keyArea) break;
00536         destLOD = currLOD;
00537         destTileWidth = w;
00538         destTileHeight = h;
00539     }
00540 
00541     //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
00542     OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;
00543 
00544     int tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
00545     int tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);
00546 
00547     int tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); 
00548     int tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight); 
00549 
00550     unsigned int numWide, numHigh;
00551     getNumTiles(destLOD, numWide, numHigh);
00552 
00553     tileMinX = osg::clampBetween(tileMinX, 0, (int)numWide-1);
00554     tileMaxX = osg::clampBetween(tileMaxX, 0, (int)numWide-1);
00555     tileMinY = osg::clampBetween(tileMinY, 0, (int)numHigh-1);
00556     tileMaxY = osg::clampBetween(tileMaxY, 0, (int)numHigh-1);
00557 
00558     OE_DEBUG << std::fixed << "  Dest Tiles: " << tileMinX << "," << tileMinY << " => " << tileMaxX << "," << tileMaxY << std::endl;
00559 
00560     for (int i = tileMinX; i <= tileMaxX; ++i)
00561     {
00562         for (int j = tileMinY; j <= tileMaxY; ++j)
00563         {
00564             //TODO: does not support multi-face destination keys.
00565             out_intersectingKeys.push_back( TileKey(destLOD, i, j, this) );
00566         }
00567     }
00568 
00569     OE_DEBUG << "    Found " << out_intersectingKeys.size() << " keys " << std::endl;
00570 }
00571 
00572 
00573 void
00574 Profile::getIntersectingTiles(const TileKey& key, std::vector<TileKey>& out_intersectingKeys) const
00575 {
00576     OE_DEBUG << "GET ISECTING TILES for key " << key.str() << " -----------------" << std::endl;
00577 
00578     //If the profiles are exactly equal, just add the given tile key.
00579     if ( isEquivalentTo( key.getProfile() ) )
00580     {
00581         //Clear the incoming list
00582         out_intersectingKeys.clear();
00583 
00584         out_intersectingKeys.push_back(key);
00585         return;
00586     }
00587     return getIntersectingTiles(key.getExtent(), out_intersectingKeys);
00588 }
00589 
00590 void
00591 Profile::getIntersectingTiles(const GeoExtent& extent, std::vector<TileKey>& out_intersectingKeys) const
00592 {
00593     GeoExtent ext = extent;
00594 
00595     // reproject into the profile's SRS if necessary:
00596     if ( ! getSRS()->isEquivalentTo( extent.getSRS() ) )
00597     {
00598         // localize the extents and clamp them to legal values
00599         ext = clampAndTransformExtent( extent );
00600         if ( !ext.isValid() )
00601             return;
00602     }
00603 
00604     if ( ext.crossesDateLine() )
00605     {
00606         GeoExtent first, second;
00607         if (ext.splitAcrossDateLine( first, second ))
00608         {
00609             addIntersectingTiles( first, out_intersectingKeys );
00610             addIntersectingTiles( second, out_intersectingKeys );
00611         }
00612     }
00613     else
00614     {
00615         addIntersectingTiles( ext, out_intersectingKeys );
00616     }
00617 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines