osgEarth 2.1.1
|
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 }