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 #include <osgEarth/ImageLayer> 00020 #include <osgEarth/TileSource> 00021 #include <osgEarth/ImageMosaic> 00022 #include <osgEarth/ImageUtils> 00023 #include <osgEarth/Registry> 00024 #include <osgEarth/StringUtils> 00025 #include <osg/Version> 00026 #include <memory.h> 00027 #include <limits.h> 00028 00029 using namespace osgEarth; 00030 using namespace OpenThreads; 00031 00032 #define LC "[ImageLayer] " 00033 00034 //------------------------------------------------------------------------ 00035 00036 ImageLayerOptions::ImageLayerOptions( const ConfigOptions& options ) : 00037 TerrainLayerOptions(options) 00038 { 00039 setDefaults(); 00040 fromConfig( _conf ); 00041 } 00042 00043 ImageLayerOptions::ImageLayerOptions( const std::string& name, const TileSourceOptions& driverOpt ) : 00044 TerrainLayerOptions(name, driverOpt) 00045 { 00046 setDefaults(); 00047 fromConfig( _conf ); 00048 } 00049 00050 void 00051 ImageLayerOptions::setDefaults() 00052 { 00053 _opacity.init( 1.0f ); 00054 _transparentColor.init( osg::Vec4ub(0,0,0,0) ); 00055 _minRange.init( -FLT_MAX ); 00056 _maxRange.init( FLT_MAX ); 00057 _lodBlending.init( false ); 00058 } 00059 00060 void 00061 ImageLayerOptions::mergeConfig( const Config& conf ) 00062 { 00063 TerrainLayerOptions::mergeConfig( conf ); 00064 fromConfig( conf ); 00065 } 00066 00067 void 00068 ImageLayerOptions::fromConfig( const Config& conf ) 00069 { 00070 conf.getIfSet( "nodata_image", _noDataImageFilename ); 00071 conf.getIfSet( "opacity", _opacity ); 00072 conf.getIfSet( "min_range", _minRange ); 00073 conf.getIfSet( "max_range", _maxRange ); 00074 conf.getIfSet( "lod_blending", _lodBlending ); 00075 00076 if ( conf.hasValue( "transparent_color" ) ) 00077 _transparentColor = stringToColor( conf.value( "transparent_color" ), osg::Vec4ub(0,0,0,0)); 00078 00079 //Load the filter settings 00080 conf.getIfSet("mag_filter","LINEAR", _magFilter,osg::Texture::LINEAR); 00081 conf.getIfSet("mag_filter","LINEAR_MIPMAP_LINEAR", _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR); 00082 conf.getIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST); 00083 conf.getIfSet("mag_filter","NEAREST", _magFilter,osg::Texture::NEAREST); 00084 conf.getIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR); 00085 conf.getIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST); 00086 conf.getIfSet("min_filter","LINEAR", _minFilter,osg::Texture::LINEAR); 00087 conf.getIfSet("min_filter","LINEAR_MIPMAP_LINEAR", _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR); 00088 conf.getIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST); 00089 conf.getIfSet("min_filter","NEAREST", _minFilter,osg::Texture::NEAREST); 00090 conf.getIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR); 00091 conf.getIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST); 00092 } 00093 00094 Config 00095 ImageLayerOptions::getConfig() const 00096 { 00097 Config conf = TerrainLayerOptions::getConfig(); 00098 conf.updateIfSet( "nodata_image", _noDataImageFilename ); 00099 conf.updateIfSet( "opacity", _opacity ); 00100 conf.updateIfSet( "min_range", _minRange ); 00101 conf.updateIfSet( "max_range", _maxRange ); 00102 conf.updateIfSet( "lod_blending", _lodBlending ); 00103 00104 if (_transparentColor.isSet()) 00105 conf.update("transparent_color", colorToString( _transparentColor.value())); 00106 00107 //Save the filter settings 00108 conf.updateIfSet("mag_filter","LINEAR", _magFilter,osg::Texture::LINEAR); 00109 conf.updateIfSet("mag_filter","LINEAR_MIPMAP_LINEAR", _magFilter,osg::Texture::LINEAR_MIPMAP_LINEAR); 00110 conf.updateIfSet("mag_filter","LINEAR_MIPMAP_NEAREST", _magFilter,osg::Texture::LINEAR_MIPMAP_NEAREST); 00111 conf.updateIfSet("mag_filter","NEAREST", _magFilter,osg::Texture::NEAREST); 00112 conf.updateIfSet("mag_filter","NEAREST_MIPMAP_LINEAR", _magFilter,osg::Texture::NEAREST_MIPMAP_LINEAR); 00113 conf.updateIfSet("mag_filter","NEAREST_MIPMAP_NEAREST",_magFilter,osg::Texture::NEAREST_MIPMAP_NEAREST); 00114 conf.updateIfSet("min_filter","LINEAR", _minFilter,osg::Texture::LINEAR); 00115 conf.updateIfSet("min_filter","LINEAR_MIPMAP_LINEAR", _minFilter,osg::Texture::LINEAR_MIPMAP_LINEAR); 00116 conf.updateIfSet("min_filter","LINEAR_MIPMAP_NEAREST", _minFilter,osg::Texture::LINEAR_MIPMAP_NEAREST); 00117 conf.updateIfSet("min_filter","NEAREST", _minFilter,osg::Texture::NEAREST); 00118 conf.updateIfSet("min_filter","NEAREST_MIPMAP_LINEAR", _minFilter,osg::Texture::NEAREST_MIPMAP_LINEAR); 00119 conf.updateIfSet("min_filter","NEAREST_MIPMAP_NEAREST",_minFilter,osg::Texture::NEAREST_MIPMAP_NEAREST); 00120 00121 return conf; 00122 } 00123 00124 //------------------------------------------------------------------------ 00125 00126 namespace 00127 { 00128 struct ImageLayerPreCacheOperation : public TileSource::ImageOperation 00129 { 00130 void operator()( osg::ref_ptr<osg::Image>& image ) 00131 { 00132 _processor.process( image ); 00133 } 00134 00135 ImageLayerTileProcessor _processor; 00136 }; 00137 00138 struct ApplyChromaKey 00139 { 00140 osg::Vec4f _chromaKey; 00141 bool operator()( osg::Vec4f& pixel ) { 00142 bool equiv = ImageUtils::areRGBEquivalent( pixel, _chromaKey ); 00143 if ( equiv ) pixel.a() = 0.0f; 00144 return equiv; 00145 } 00146 }; 00147 } 00148 00149 //------------------------------------------------------------------------ 00150 00151 ImageLayerTileProcessor::ImageLayerTileProcessor( const ImageLayerOptions& options ) 00152 { 00153 init( options, false ); 00154 } 00155 00156 void 00157 ImageLayerTileProcessor::init( const ImageLayerOptions& options, bool layerInTargetProfile ) 00158 { 00159 _options = options; 00160 _layerInTargetProfile = layerInTargetProfile; 00161 00162 if ( _layerInTargetProfile ) 00163 OE_DEBUG << LC << "Good, the layer and map have the same profile." << std::endl; 00164 00165 const osg::Vec4ub& ck= *_options.transparentColor(); 00166 _chromaKey.set( ck.r() / 255.0f, ck.g() / 255.0f, ck.b() / 255.0f, 1.0 ); 00167 00168 if ( _options.noDataImageFilename().isSet() && !_options.noDataImageFilename()->empty() ) 00169 { 00170 _noDataImage = URI(*_options.noDataImageFilename()).readImage(); 00171 if ( !_noDataImage.valid() ) 00172 { 00173 OE_WARN << "Warning: Could not read nodata image from \"" << _options.noDataImageFilename().value() << "\"" << std::endl; 00174 } 00175 } 00176 } 00177 00178 void 00179 ImageLayerTileProcessor::process( osg::ref_ptr<osg::Image>& image ) const 00180 { 00181 if ( !image.valid() ) 00182 return; 00183 00184 // Check to see if the image is the nodata image 00185 if ( _noDataImage.valid() ) 00186 { 00187 if (ImageUtils::areEquivalent(image.get(), _noDataImage.get())) 00188 { 00189 //OE_DEBUG << LC << "Found nodata" << std::endl; 00190 image = 0L; 00191 return; 00192 } 00193 } 00194 00195 // If this is a compressed image, uncompress it IF the image is not already in the 00196 // target profile...becuase if it's not in the target profile, we will have to do 00197 // some mosaicing...and we can't mosaic a compressed image. 00198 if (!_layerInTargetProfile && 00199 ImageUtils::isCompressed(image.get()) && 00200 ImageUtils::canConvert(image.get(), GL_RGBA, GL_UNSIGNED_BYTE) ) 00201 { 00202 image = ImageUtils::convertToRGBA8( image.get() ); 00203 } 00204 00205 // Apply a transparent color mask if one is specified 00206 if ( _options.transparentColor().isSet() ) 00207 { 00208 if ( !ImageUtils::hasAlphaChannel(image.get()) && ImageUtils::canConvert(image.get(), GL_RGBA, GL_UNSIGNED_BYTE) ) 00209 { 00210 // if the image doesn't have an alpha channel, we must convert it to 00211 // a format that does before continuing. 00212 image = ImageUtils::convertToRGBA8( image.get() ); 00213 } 00214 00215 ImageUtils::PixelVisitor<ApplyChromaKey> applyChroma; 00216 applyChroma._chromaKey = _chromaKey; 00217 applyChroma.accept( image.get() ); 00218 } 00219 00220 // protected against multi threaded access. This is a requirement in sequential/preemptive mode, 00221 // for example. This used to be in TextureCompositorTexArray::prepareImage. 00222 // TODO: review whether this affects performance. 00223 image->setDataVariance( osg::Object::DYNAMIC ); 00224 } 00225 00226 //------------------------------------------------------------------------ 00227 00228 ImageLayer::ImageLayer( const ImageLayerOptions& options ) : 00229 TerrainLayer( &_runtimeOptions ), 00230 _runtimeOptions( options ) 00231 { 00232 init(); 00233 } 00234 00235 ImageLayer::ImageLayer( const std::string& name, const TileSourceOptions& driverOptions ) : 00236 TerrainLayer ( &_runtimeOptions ), 00237 _runtimeOptions( ImageLayerOptions(name, driverOptions) ) 00238 { 00239 init(); 00240 } 00241 00242 ImageLayer::ImageLayer( const ImageLayerOptions& options, TileSource* tileSource ) : 00243 TerrainLayer ( &_runtimeOptions, tileSource ), 00244 _runtimeOptions( options ) 00245 { 00246 init(); 00247 } 00248 00249 void 00250 ImageLayer::init() 00251 { 00252 //nop 00253 } 00254 00255 void 00256 ImageLayer::addCallback( ImageLayerCallback* cb ) 00257 { 00258 _callbacks.push_back( cb ); 00259 } 00260 00261 void 00262 ImageLayer::removeCallback( ImageLayerCallback* cb ) 00263 { 00264 ImageLayerCallbackList::iterator i = std::find( _callbacks.begin(), _callbacks.end(), cb ); 00265 if ( i != _callbacks.end() ) 00266 _callbacks.erase( i ); 00267 } 00268 00269 void 00270 ImageLayer::fireCallback( TerrainLayerCallbackMethodPtr method ) 00271 { 00272 for( ImageLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i ) 00273 { 00274 ImageLayerCallback* cb = i->get(); 00275 (cb->*method)( this ); 00276 } 00277 } 00278 00279 void 00280 ImageLayer::fireCallback( ImageLayerCallbackMethodPtr method ) 00281 { 00282 for( ImageLayerCallbackList::const_iterator i = _callbacks.begin(); i != _callbacks.end(); ++i ) 00283 { 00284 ImageLayerCallback* cb = i->get(); 00285 (cb->*method)( this ); 00286 } 00287 } 00288 00289 void 00290 ImageLayer::setOpacity( float value ) 00291 { 00292 _runtimeOptions.opacity() = osg::clampBetween( value, 0.0f, 1.0f ); 00293 fireCallback( &ImageLayerCallback::onOpacityChanged ); 00294 } 00295 00296 void 00297 ImageLayer::disableLODBlending() 00298 { 00299 _runtimeOptions.lodBlending() = false; 00300 } 00301 00302 void 00303 ImageLayer::setTargetProfileHint( const Profile* profile ) 00304 { 00305 TerrainLayer::setTargetProfileHint( profile ); 00306 00307 // if we've already constructed the pre-cache operation, reinitialize it. 00308 if ( _preCacheOp.valid() ) 00309 initPreCacheOp(); 00310 } 00311 00312 void 00313 ImageLayer::initTileSource() 00314 { 00315 // call superclass first. 00316 TerrainLayer::initTileSource(); 00317 00318 // install the pre-caching image processor operation. 00319 initPreCacheOp(); 00320 } 00321 00322 void 00323 ImageLayer::initPreCacheOp() 00324 { 00325 bool layerInTargetProfile = 00326 _targetProfileHint.valid() && 00327 getProfile() && 00328 _targetProfileHint->isEquivalentTo( getProfile() ); 00329 00330 ImageLayerPreCacheOperation* op = new ImageLayerPreCacheOperation(); 00331 op->_processor.init( _runtimeOptions, layerInTargetProfile ); 00332 00333 _preCacheOp = op; 00334 00335 } 00336 00337 GeoImage 00338 ImageLayer::createImage( const TileKey& key, ProgressCallback* progress) 00339 { 00340 GeoImage result; 00341 00342 //OE_NOTICE << "[osgEarth::MapLayer::createImage] " << key.str() << std::endl; 00343 if ( !isCacheOnly() && !getTileSource() ) 00344 { 00345 OE_WARN << LC << "Error: MapLayer does not have a valid TileSource, cannot create image " << std::endl; 00346 return GeoImage::INVALID; 00347 } 00348 00349 const Profile* layerProfile = getProfile(); 00350 const Profile* mapProfile = key.getProfile(); 00351 00352 if ( !getProfile() ) 00353 { 00354 OE_WARN << LC << "Could not get a valid profile for Layer \"" << getName() << "\"" << std::endl; 00355 return GeoImage::INVALID; 00356 } 00357 00358 //Determine whether we should cache in the Map profile or the Layer profile. 00359 bool cacheInMapProfile = true; 00360 if (mapProfile->isEquivalentTo( layerProfile )) 00361 { 00362 OE_DEBUG << LC << "Layer \"" << getName() << "\": Map and Layer profiles are equivalent " << std::endl; 00363 } 00364 //If the map profile and layer profile are in the same SRS but with different tiling scemes and exact cropping is not required, cache in the layer profile. 00365 else if (mapProfile->getSRS()->isEquivalentTo( layerProfile->getSRS()) && _runtimeOptions.exactCropping() == false ) 00366 { 00367 OE_DEBUG << LC << "Layer \"" << getName() << "\": Map and Layer profiles are in the same SRS and non-exact cropping is allowed, caching in layer profile." << std::endl; 00368 cacheInMapProfile = false; 00369 } 00370 00371 bool cacheInLayerProfile = !cacheInMapProfile; 00372 00373 //Write the cache TMS file if it hasn't been written yet. 00374 if (!_cacheProfile.valid() && _cache.valid() && _runtimeOptions.cacheEnabled() == true && _tileSource.valid()) 00375 { 00376 _cacheProfile = cacheInMapProfile ? mapProfile : _profile.get(); 00377 _cache->storeProperties( _cacheSpec, _cacheProfile.get(), _tileSource->getPixelsPerTile() ); 00378 } 00379 00380 if (cacheInMapProfile) 00381 { 00382 OE_DEBUG << LC << "Layer \"" << getName() << "\" caching in Map profile " << std::endl; 00383 } 00384 00385 //If we are caching in the map profile, try to get the image immediately. 00386 if (cacheInMapProfile && _cache.valid() && _runtimeOptions.cacheEnabled() == true ) 00387 { 00388 osg::ref_ptr<const osg::Image> cachedImage; 00389 if ( _cache->getImage( key, _cacheSpec, cachedImage ) ) 00390 { 00391 OE_DEBUG << LC << "Layer \"" << getName()<< "\" got tile " << key.str() << " from map cache " << std::endl; 00392 00393 result = GeoImage( ImageUtils::cloneImage(cachedImage.get()), key.getExtent() ); 00394 ImageUtils::normalizeImage( result.getImage() ); 00395 return result; 00396 } 00397 } 00398 00399 //If the key profile and the source profile exactly match, simply request the image from the source 00400 if ( mapProfile->isEquivalentTo( layerProfile ) ) 00401 { 00402 OE_DEBUG << LC << "Key and source profiles are equivalent, requesting single tile" << std::endl; 00403 osg::ref_ptr<const osg::Image> image; 00404 osg::Image* im = createImageWrapper( key, cacheInLayerProfile, progress ); 00405 if ( im ) 00406 { 00407 result = GeoImage( im, key.getExtent() ); 00408 } 00409 } 00410 00411 // Otherwise, we need to process the tiles. 00412 else 00413 { 00414 OE_DEBUG << LC << "Key and source profiles are different, creating mosaic" << std::endl; 00415 GeoImage mosaic; 00416 00417 // Determine the intersecting keys and create and extract an appropriate image from the tiles 00418 std::vector<TileKey> intersectingTiles; 00419 00420 //Scale the extent if necessary 00421 GeoExtent ext = key.getExtent(); 00422 if ( _runtimeOptions.edgeBufferRatio().isSet() ) 00423 { 00424 double ratio = _runtimeOptions.edgeBufferRatio().get(); 00425 ext.scale(ratio, ratio); 00426 } 00427 00428 layerProfile->getIntersectingTiles(ext, intersectingTiles); 00429 00430 if (intersectingTiles.size() > 0) 00431 { 00432 double dst_minx, dst_miny, dst_maxx, dst_maxy; 00433 key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); 00434 00435 osg::ref_ptr<ImageMosaic> mi = new ImageMosaic; 00436 std::vector<TileKey> missingTiles; 00437 00438 bool retry = false; 00439 for (unsigned int j = 0; j < intersectingTiles.size(); ++j) 00440 { 00441 double minX, minY, maxX, maxY; 00442 intersectingTiles[j].getExtent().getBounds(minX, minY, maxX, maxY); 00443 00444 OE_DEBUG << LC << "\t Intersecting Tile " << j << ": " << minX << ", " << minY << ", " << maxX << ", " << maxY << std::endl; 00445 00446 osg::ref_ptr<osg::Image> img; 00447 img = createImageWrapper( intersectingTiles[j], cacheInLayerProfile, progress ); 00448 00449 if ( img.valid() ) 00450 { 00451 if (img->getPixelFormat() != GL_RGBA || img->getDataType() != GL_UNSIGNED_BYTE || img->getInternalTextureFormat() != GL_RGBA8 ) 00452 { 00453 osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(img.get()); 00454 if (convertedImg.valid()) 00455 { 00456 img = convertedImg; 00457 } 00458 } 00459 mi->getImages().push_back(TileImage(img.get(), intersectingTiles[j])); 00460 } 00461 else 00462 { 00463 if (progress && (progress->isCanceled() || progress->needsRetry())) 00464 { 00465 retry = true; 00466 break; 00467 } 00468 missingTiles.push_back(intersectingTiles[j]); 00469 } 00470 } 00471 00472 //if (mi->getImages().empty() || missingTiles.size() > 0) 00473 if (mi->getImages().empty() || retry) 00474 { 00475 OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; 00476 return GeoImage::INVALID; 00477 } 00478 else if (missingTiles.size() > 0) 00479 { 00480 osg::ref_ptr<const osg::Image> validImage = mi->getImages()[0].getImage(); 00481 unsigned int tileWidth = validImage->s(); 00482 unsigned int tileHeight = validImage->t(); 00483 unsigned int tileDepth = validImage->r(); 00484 for (unsigned int j = 0; j < missingTiles.size(); ++j) 00485 { 00486 // Create transparent image which size equals to the size of a valid image 00487 osg::ref_ptr<osg::Image> newImage = new osg::Image; 00488 newImage->allocateImage(tileWidth, tileHeight, tileDepth, validImage->getPixelFormat(), validImage->getDataType()); 00489 unsigned char *data = newImage->data(0,0); 00490 memset(data, 0, newImage->getTotalSizeInBytes()); 00491 00492 mi->getImages().push_back(TileImage(newImage.get(), missingTiles[j])); 00493 } 00494 } 00495 00496 double rxmin, rymin, rxmax, rymax; 00497 mi->getExtents( rxmin, rymin, rxmax, rymax ); 00498 00499 mosaic = GeoImage( 00500 mi->createImage(), 00501 GeoExtent( layerProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); 00502 } 00503 00504 if ( mosaic.valid() ) 00505 { 00506 // the imagery must be reprojected iff: 00507 // * the SRS of the image is different from the SRS of the key; 00508 // * UNLESS they are both geographic SRS's (in which case we can skip reprojection) 00509 bool needsReprojection = 00510 !mosaic.getSRS()->isEquivalentTo( key.getProfile()->getSRS()) && 00511 !(mosaic.getSRS()->isGeographic() && key.getProfile()->getSRS()->isGeographic()); 00512 00513 bool needsLeftBorder = false; 00514 bool needsRightBorder = false; 00515 bool needsTopBorder = false; 00516 bool needsBottomBorder = false; 00517 00518 // If we don't need to reproject the data, we had to mosaic the data, so check to see if we need to add 00519 // an extra, transparent pixel on the sides because the data doesn't encompass the entire map. 00520 if (!needsReprojection) 00521 { 00522 GeoExtent keyExtent = key.getExtent(); 00523 // If the key is geographic and the mosaic is mercator, we need to get the mercator 00524 // extents to determine if we need to add the border or not 00525 // (TODO: this might be OBE due to the elimination of the Mercator fast-path -gw) 00526 if (key.getExtent().getSRS()->isGeographic() && mosaic.getSRS()->isMercator()) 00527 { 00528 keyExtent = osgEarth::Registry::instance()->getGlobalMercatorProfile()->clampAndTransformExtent( 00529 key.getExtent( )); 00530 } 00531 00532 00533 //Use an epsilon to only add the border if it is significant enough. 00534 double eps = 1e-6; 00535 00536 double leftDiff = mosaic.getExtent().xMin() - keyExtent.xMin(); 00537 if (leftDiff > eps) 00538 { 00539 needsLeftBorder = true; 00540 } 00541 00542 double rightDiff = keyExtent.xMax() - mosaic.getExtent().xMax(); 00543 if (rightDiff > eps) 00544 { 00545 needsRightBorder = true; 00546 } 00547 00548 double bottomDiff = mosaic.getExtent().yMin() - keyExtent.yMin(); 00549 if (bottomDiff > eps) 00550 { 00551 needsBottomBorder = true; 00552 } 00553 00554 double topDiff = keyExtent.yMax() - mosaic.getExtent().yMax(); 00555 if (topDiff > eps) 00556 { 00557 needsTopBorder = true; 00558 } 00559 } 00560 00561 if ( needsReprojection ) 00562 { 00563 OE_DEBUG << LC << " Reprojecting image" << std::endl; 00564 00565 // We actually need to reproject the image. Note: GeoImage::reproject() will automatically 00566 // crop the image to the correct extents, so there is no need to crop after reprojection. 00567 result = mosaic.reproject( 00568 key.getProfile()->getSRS(), 00569 &key.getExtent(), 00570 _runtimeOptions.reprojectedTileSize().value(), _runtimeOptions.reprojectedTileSize().value() ); 00571 } 00572 else 00573 { 00574 OE_DEBUG << LC << " Cropping image" << std::endl; 00575 // crop to fit the map key extents 00576 GeoExtent clampedMapExt = layerProfile->clampAndTransformExtent( key.getExtent() ); 00577 if ( clampedMapExt.isValid() ) 00578 { 00579 int size = _runtimeOptions.exactCropping() == true ? _runtimeOptions.reprojectedTileSize().value() : 0; 00580 result = mosaic.crop(clampedMapExt, _runtimeOptions.exactCropping().value(), size, size); 00581 } 00582 else 00583 result = GeoImage::INVALID; 00584 } 00585 00586 //Add the transparent pixel AFTER the crop so that it doesn't get cropped out 00587 if (result.valid() && (needsLeftBorder || needsRightBorder || needsBottomBorder || needsTopBorder)) 00588 { 00589 result = result.addTransparentBorder(needsLeftBorder, needsRightBorder, needsBottomBorder, needsTopBorder); 00590 } 00591 } 00592 } 00593 00594 // Normalize the image if necessary 00595 if ( result.valid() ) 00596 { 00597 ImageUtils::normalizeImage( result.getImage() ); 00598 } 00599 00600 //If we got a result, the cache is valid and we are caching in the map profile, write to the map cache. 00601 if (result.valid() && _cache.valid() && _runtimeOptions.cacheEnabled() == true && cacheInMapProfile) 00602 { 00603 OE_DEBUG << LC << "Layer \"" << getName() << "\" writing tile " << key.str() << " to cache " << std::endl; 00604 _cache->setImage( key, _cacheSpec, result.getImage()); 00605 } 00606 return result; 00607 } 00608 00609 osg::Image* 00610 ImageLayer::createImageWrapper(const TileKey& key, 00611 bool cacheInLayerProfile, 00612 ProgressCallback* progress ) 00613 { 00614 // Results: 00615 // 00616 // * return NULL to indicate that the key exceeds the maximum LOD of the source data, 00617 // and that the engine may need to generate a "fallback" tile if necessary. 00618 // 00619 // * return an "empty image" if the LOD is valid BUT the key does not intersect the 00620 // source's data extents. 00621 00622 osg::Image* result = 0L; 00623 00624 // first check the cache. 00625 // TODO: find a way to avoid caching/checking when the LOD falls 00626 if (_cache.valid() && cacheInLayerProfile && _runtimeOptions.cacheEnabled() == true ) 00627 { 00628 osg::ref_ptr<const osg::Image> cachedImage; 00629 if ( _cache->getImage( key, _cacheSpec, cachedImage ) ) 00630 { 00631 OE_INFO << LC << " Layer \"" << getName() << "\" got " << key.str() << " from cache " << std::endl; 00632 return ImageUtils::cloneImage(cachedImage.get()); 00633 } 00634 } 00635 00636 if ( !isCacheOnly() ) 00637 { 00638 TileSource* source = getTileSource(); 00639 if ( !source ) 00640 return 0L; 00641 00642 // Only try to get the image if it's not in the blacklist 00643 if ( !source->getBlacklist()->contains(key.getTileId()) ) 00644 { 00645 // if the tile source cannot service this key's LOD, return NULL. 00646 if ( source->hasDataAtLOD( key.getLevelOfDetail() ) ) 00647 { 00648 // if the key's extent intersects the source's extent, ask the 00649 // source for an image. 00650 if ( source->hasDataInExtent( key.getExtent() ) ) 00651 { 00652 //Take a reference to the preCacheOp, there is a potential for it to be 00653 //overwritten and deleted if this ImageLayer is added to another Map 00654 //while createImage is going on. 00655 osg::ref_ptr< TileSource::ImageOperation > op = _preCacheOp; 00656 result = source->createImage( key, op.get(), progress ); 00657 00658 // if no result was created, add this key to the blacklist. 00659 if ( result == 0L && (!progress || !progress->isCanceled()) ) 00660 { 00661 //Add the tile to the blacklist 00662 source->getBlacklist()->add(key.getTileId()); 00663 } 00664 } 00665 00666 // otherwise, generate an empty image. 00667 else 00668 { 00669 result = ImageUtils::createEmptyImage(); 00670 } 00671 } 00672 00673 else 00674 { 00675 // in this case, the source cannot service the LOD 00676 result = NULL; 00677 } 00678 } 00679 00680 // Cache is necessary: 00681 if ( result && _cache.valid() && cacheInLayerProfile && _runtimeOptions.cacheEnabled() == true ) 00682 { 00683 _cache->setImage( key, _cacheSpec, result ); 00684 } 00685 } 00686 00687 return result; 00688 }