void OSGTerrainEngineNode::addImageLayer( ImageLayer* layerAdded ) { if ( !layerAdded || !layerAdded->getTileSource() ) return; // visit all existing terrain tiles and inform each one of the new image layer: TileVector tiles; _terrain->getTiles( tiles ); for( TileVector::iterator itr = tiles.begin(); itr != tiles.end(); ++itr ) { Tile* tile = itr->get(); StreamingTile* streamingTile = 0L; GeoImage geoImage; bool needToUpdateImagery = false; int imageLOD = -1; if ( !_isStreaming || tile->getKey().getLevelOfDetail() == 1 ) { // in standard mode, or at the first LOD in seq/pre mode, fetch the image immediately. TileKey geoImageKey = tile->getKey(); _tileFactory->createValidGeoImage( layerAdded, tile->getKey(), geoImage, geoImageKey ); imageLOD = tile->getKey().getLevelOfDetail(); } else { // in seq/pre mode, set up a placeholder and mark the tile as dirty. geoImage = GeoImage(ImageUtils::createEmptyImage(), tile->getKey().getExtent() ); needToUpdateImagery = true; streamingTile = static_cast<StreamingTile*>(tile); } if (geoImage.valid()) { const MapInfo& mapInfo = _update_mapf->getMapInfo(); double img_min_lon, img_min_lat, img_max_lon, img_max_lat; geoImage.getExtent().getBounds(img_min_lon, img_min_lat, img_max_lon, img_max_lat); //Specify a new locator for the color with the coordinates of the TileKey that was actually used to create the image osg::ref_ptr<GeoLocator> img_locator = tile->getKey().getProfile()->getSRS()->createLocator( img_min_lon, img_min_lat, img_max_lon, img_max_lat, !mapInfo.isGeocentric() ); //Set the CS to geocentric if we are dealing with a geocentric map if ( mapInfo.isGeocentric() ) { img_locator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC ); } tile->setCustomColorLayer( CustomColorLayer( layerAdded, geoImage.getImage(), img_locator.get(), imageLOD, tile->getKey() ) ); // if necessary, tell the tile to queue up a new imagery request (since we // just installed a placeholder) if ( needToUpdateImagery ) { streamingTile->updateImagery( layerAdded, *_update_mapf, _tileFactory.get() ); } } else { // this can happen if there's no data in the new layer for the given tile. // we will rely on the driver to dump out a warning if this is an error. } tile->applyImmediateTileUpdate( TileUpdate::ADD_IMAGE_LAYER, layerAdded->getUID() ); } updateTextureCombining(); }
GeoImage ImageLayer::assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress, bool& out_isFallback) { GeoImage mosaicedImage, result; out_isFallback = false; // Scale the extent if necessary to apply an "edge buffer" GeoExtent ext = key.getExtent(); if ( _runtimeOptions.edgeBufferRatio().isSet() ) { double ratio = _runtimeOptions.edgeBufferRatio().get(); ext.scale(ratio, ratio); } // Get a set of layer tiles that intersect the requested extent. std::vector<TileKey> intersectingKeys; getProfile()->getIntersectingTiles( ext, intersectingKeys ); if ( intersectingKeys.size() > 0 ) { double dst_minx, dst_miny, dst_maxx, dst_maxy; key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); // if we find at least one "real" tile in the mosaic, then the whole result tile is // "real" (i.e. not a fallback tile) bool foundAtLeastOneRealTile = false; bool retry = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { double minX, minY, maxX, maxY; k->getExtent().getBounds(minX, minY, maxX, maxY); bool isFallback = false; GeoImage image = createImageFromTileSource( *k, progress, true, isFallback ); if ( image.valid() ) { // make sure the image is RGBA. // (TODO: investigate whether we still need this -gw 6/25/2012) if (image.getImage()->getPixelFormat() != GL_RGBA || image.getImage()->getDataType() != GL_UNSIGNED_BYTE || image.getImage()->getInternalTextureFormat() != GL_RGBA8 ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); if ( !isFallback ) foundAtLeastOneRealTile = true; } else { // the tile source did not return a tile, so make a note of it. if (progress && (progress->isCanceled() || progress->needsRetry())) { retry = true; break; } } } if ( mosaic.getImages().empty() || retry ) { // if we didn't get any data, fail OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; return GeoImage::INVALID; } // all set. Mosaic all the images together. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); mosaicedImage = GeoImage( mosaic.createImage(), GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) ); if ( !foundAtLeastOneRealTile ) out_isFallback = true; } else { OE_DEBUG << LC << "assembleImageFromTileSource: no intersections (" << key.str() << ")" << std::endl; } // Final step: transform the mosaic into the requesting key's extent. if ( mosaicedImage.valid() ) { // GeoImage::reproject() will automatically crop the image to the correct extents. // so there is no need to crop after reprojection. Also note that if the SRS's are the // same (even though extents are different), then this operation is technically not a // reprojection but merely a resampling. result = mosaicedImage.reproject( key.getProfile()->getSRS(), &key.getExtent(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.driver()->bilinearReprojection()); } // Process images with full alpha to properly support MP blending. if ( result.valid() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
void TextureCompositorTexArray::applyLayerUpdate(osg::StateSet* stateSet, UID layerUID, const GeoImage& preparedImage, const TileKey& tileKey, const TextureLayout& layout, osg::StateSet* parentStateSet) const { GeoExtent tileExtent(tileKey.getExtent()); int slot = layout.getSlot( layerUID ); if ( slot < 0 ) return; // means the layer no longer exists // access the texture array, creating or growing it if necessary: osg::Texture2DArray* texture = s_getTexture( stateSet, layout, 0, textureSize() ); ensureSampler( stateSet, 0 ); // assign the new image at the proper position in the texture array. osg::Image* image = preparedImage.getImage(); assignImage(texture, slot, image); // update the region uniform to reflect the geo extent of the image: const GeoExtent& imageExtent = preparedImage.getExtent(); osg::Vec4 tileTransform; getImageTransform(tileExtent, imageExtent, tileTransform); // access the region uniform, creating or growing it if necessary: ArrayUniform regionUni( "region", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot()+1 ); if ( regionUni.isValid() ) { int layerOffset = slot * 8; for (int i = 0; i < 4; ++i) regionUni.setElement( layerOffset + i, tileTransform[i]); //region->dirty(); } if ( layout.isBlendingEnabled( layerUID ) && regionUni.isValid() ) { osg::Uniform* secondarySampler = ensureSampler( stateSet, 1 ); osg::Texture2DArray* parentTexture = 0; const unsigned parentLayerOffset = slot * 8 + 4; if ( parentStateSet ) { ArrayUniform parentRegion( "region", osg::Uniform::FLOAT, parentStateSet, layout.getMaxUsedSlot()+1 ); //osg::Uniform* parentRegion = s_getRegionUniform( parentStateSet, // layout ); GeoExtent parentExtent(tileKey.createParentKey().getExtent()); float widthRatio, heightRatio; parentRegion.getElement(slot * 8 + 2, widthRatio); parentRegion.getElement(slot * 8 + 3, heightRatio); float parentImageWidth = parentExtent.width() / widthRatio; float parentImageHeight = parentExtent.height() / heightRatio; float xRatio, yRatio; parentRegion.getElement(slot * 8, xRatio); parentRegion.getElement(slot * 8 + 1, yRatio); float ParentImageXmin = parentExtent.xMin() - xRatio * parentImageWidth; float ParentImageYmin = parentExtent.yMin() - yRatio * parentImageHeight; regionUni.setElement(parentLayerOffset, static_cast<float>((tileExtent.xMin() - ParentImageXmin) / parentImageWidth)); regionUni.setElement(parentLayerOffset + 1, static_cast<float>((tileExtent.yMin() - ParentImageYmin) / parentImageHeight)); regionUni.setElement(parentLayerOffset + 2, static_cast<float>(tileExtent.width() / parentImageWidth)); regionUni.setElement(parentLayerOffset + 3, static_cast<float>(tileExtent.height() / parentImageHeight)); //regionUni.dirty(); parentTexture = static_cast<osg::Texture2DArray*>(parentStateSet->getTextureAttribute(0, osg::StateAttribute::TEXTURE)); } else { // setting the parent transform values to -1 disabled blending for this layer. #hack -gw for (int i = 0; i < 4; ++i) regionUni.setElement(parentLayerOffset + i, tileTransform[i]); } if (parentTexture) stateSet->setTextureAttribute(1, parentTexture, osg::StateAttribute::ON); else secondarySampler->set(0); // update the timestamp on the image layer to support fade-in blending. float now = (float)osg::Timer::instance()->delta_s( osg::Timer::instance()->getStartTick(), osg::Timer::instance()->tick() ); ArrayUniform stampUniform( "osgearth_SlotStamp", osg::Uniform::FLOAT, stateSet, layout.getMaxUsedSlot()+1 ); stampUniform.setElement( slot, now ); } }
GeoImage ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback ) { GeoImage result; out_isFallback = false; // If the layer is disabled, bail out. if ( !getEnabled() ) { return GeoImage::INVALID; } // Check for a "Minumum level" setting on this layer. If we are before the // min level, just return the empty image. Do not cache empties if ( _runtimeOptions.minLevel().isSet() && key.getLOD() < _runtimeOptions.minLevel().value() ) { return GeoImage( _emptyImage.get(), key.getExtent() ); } // Check for a "Minimum resolution" setting on the layer. If we are before the // min resolution, return the empty image. Do not cache empties. if ( _runtimeOptions.minResolution().isSet() ) { double keyres = key.getExtent().width() / getTileSize(); double keyresInLayerProfile = key.getProfile()->getSRS()->transformUnits(keyres, getProfile()->getSRS()); if ( keyresInLayerProfile > _runtimeOptions.minResolution().value() ) { return GeoImage( _emptyImage.get(), key.getExtent() ); } } OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // locate the cache bin for the target profile for this layer: CacheBin* cacheBin = getCacheBin( key.getProfile() ); // validate that we have either a valid tile source, or we're cache-only. if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) ) { OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create image " << std::endl; _runtimeOptions.enabled() = false; return GeoImage::INVALID; } // validate the existance of a valid layer profile (unless we're in cache-only mode, in which // case there is no layer profile) if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoImage::INVALID; } // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readImage( key.str() ); if ( r.succeeded() ) { ImageUtils::normalizeImage( r.getImage() ); return GeoImage( r.releaseImage(), key.getExtent() ); } else { //OE_INFO << LC << getName() << " : " << key.str() << " cache miss" << std::endl; } } // The data was not in the cache. If we are cache-only, fail sliently if ( isCacheOnly() ) { return GeoImage::INVALID; } // Get an image from the underlying TileSource. result = createImageFromTileSource( key, progress, forceFallback, out_isFallback ); // Normalize the image if necessary if ( result.valid() ) { ImageUtils::normalizeImage( result.getImage() ); } // If we got a result, the cache is valid and we are caching in the map profile, write to the map cache. if (result.valid() && //JB: Removed the check to not write out fallback data. If you have a low resolution base dataset (max lod 3) and a high resolution insert (max lod 22) // then the low res data needs to "fallback" from LOD 4 - 22 so you can display the high res inset. If you don't cache these intermediate tiles then // performance can suffer generating all those fallback tiles, especially if you have to do reprojection or mosaicing. //!out_isFallback && cacheBin && getCachePolicy().isCacheWriteable() ) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write( key.str(), result.getImage() ); //OE_INFO << LC << "WRITING " << key.str() << " to the cache." << std::endl; } if ( result.valid() ) { OE_DEBUG << LC << key.str() << " result OK" << std::endl; } else { OE_DEBUG << LC << key.str() << "result INVALID" << std::endl; } return result; }
GeoImage ImageLayer::assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress) { GeoImage mosaicedImage, result; // Scale the extent if necessary to apply an "edge buffer" GeoExtent ext = key.getExtent(); if ( _runtimeOptions.edgeBufferRatio().isSet() ) { double ratio = _runtimeOptions.edgeBufferRatio().get(); ext.scale(ratio, ratio); } // Get a set of layer tiles that intersect the requested extent. std::vector<TileKey> intersectingKeys; getProfile()->getIntersectingTiles( key, intersectingKeys ); if ( intersectingKeys.size() > 0 ) { double dst_minx, dst_miny, dst_maxx, dst_maxy; key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); // if we find at least one "real" tile in the mosaic, then the whole result tile is // "real" (i.e. not a fallback tile) bool retry = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { double minX, minY, maxX, maxY; k->getExtent().getBounds(minX, minY, maxX, maxY); GeoImage image = createImageFromTileSource( *k, progress ); if ( image.valid() ) { ImageUtils::normalizeImage(image.getImage()); // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels. // This is not the smarter choice (in some case RGB would be sufficient) but // it ensure consistency between all images / layers. // // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images. // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint // if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // the tile source did not return a tile, so make a note of it. if (progress && (progress->isCanceled() || progress->needsRetry())) { retry = true; break; } } } if ( mosaic.getImages().empty() || retry ) { // if we didn't get any data, fail. OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; return GeoImage::INVALID; } // all set. Mosaic all the images together. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); mosaicedImage = GeoImage( mosaic.createImage(), GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) ); } else { OE_DEBUG << LC << "assembleImageFromTileSource: no intersections (" << key.str() << ")" << std::endl; } // Final step: transform the mosaic into the requesting key's extent. if ( mosaicedImage.valid() ) { // GeoImage::reproject() will automatically crop the image to the correct extents. // so there is no need to crop after reprojection. Also note that if the SRS's are the // same (even though extents are different), then this operation is technically not a // reprojection but merely a resampling. result = mosaicedImage.reproject( key.getProfile()->getSRS(), &key.getExtent(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.driver()->bilinearReprojection()); } // Process images with full alpha to properly support MP blending. if ( result.valid() && *_runtimeOptions.featherPixels() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
GeoImage ImageLayer::createImageInKeyProfile(const TileKey& key, ProgressCallback* progress) { GeoImage result; // If the layer is disabled, bail out. if ( !getEnabled() ) { return GeoImage::INVALID; } // Make sure the request is in range. if ( !isKeyInRange(key) ) { return GeoImage::INVALID; } OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // Check the layer L2 cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); ReadResult result = bin->readObject(key.str() ); if ( result.succeeded() ) return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent()); //_memCache->dumpStats(key.getProfile()->getFullSignature()); } // locate the cache bin for the target profile for this layer: CacheBin* cacheBin = getCacheBin( key.getProfile() ); // validate that we have either a valid tile source, or we're cache-only. if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) ) { OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create image " << std::endl; _runtimeOptions.enabled() = false; return GeoImage::INVALID; } // validate the existance of a valid layer profile (unless we're in cache-only mode, in which // case there is no layer profile) if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoImage::INVALID; } osg::ref_ptr< osg::Image > cachedImage; // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readImage( key.str() ); if ( r.succeeded() ) { cachedImage = r.releaseImage(); ImageUtils::normalizeImage( cachedImage.get() ); bool expired = getCachePolicy().isExpired(r.lastModifiedTime()); if (!expired) { OE_DEBUG << "Got cached image for " << key.str() << std::endl; return GeoImage( cachedImage.get(), key.getExtent() ); } else { OE_DEBUG << "Expired image for " << key.str() << std::endl; } } } // The data was not in the cache. If we are cache-only, fail sliently if ( isCacheOnly() ) { // If it's cache only and we have an expired but cached image, just return it. if (cachedImage.valid()) { return GeoImage( cachedImage.get(), key.getExtent() ); } else { return GeoImage::INVALID; } } // Get an image from the underlying TileSource. result = createImageFromTileSource( key, progress ); // Normalize the image if necessary if ( result.valid() ) { ImageUtils::normalizeImage( result.getImage() ); } // memory cache first: if ( result.valid() && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); bin->write(key.str(), result.getImage()); } // If we got a result, the cache is valid and we are caching in the map profile, // write to the map cache. if (result.valid() && cacheBin && getCachePolicy().isCacheWriteable() ) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write( key.str(), result.getImage() ); } if ( result.valid() ) { OE_DEBUG << LC << key.str() << " result OK" << std::endl; } else { OE_DEBUG << LC << key.str() << "result INVALID" << std::endl; // We couldn't get an image from the source. So see if we have an expired cached image if (cachedImage.valid()) { OE_DEBUG << LC << "Using cached but expired image for " << key.str() << std::endl; result = GeoImage( cachedImage.get(), key.getExtent()); } } return result; }
GeoImage ImageLayer::assembleImage(const TileKey& key, ProgressCallback* progress) { // If we got here, asset that there's a non-null layer profile. if (!getProfile()) { setStatus(Status::Error(Status::AssertionFailure, "assembleImage with undefined profile")); return GeoImage::INVALID; } GeoImage mosaicedImage, result; // Scale the extent if necessary to apply an "edge buffer" GeoExtent ext = key.getExtent(); if ( options().edgeBufferRatio().isSet() ) { double ratio = options().edgeBufferRatio().get(); ext.scale(ratio, ratio); } // Get a set of layer tiles that intersect the requested extent. std::vector<TileKey> intersectingKeys; getProfile()->getIntersectingTiles( key, intersectingKeys ); if ( intersectingKeys.size() > 0 ) { double dst_minx, dst_miny, dst_maxx, dst_maxy; key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); // if we find at least one "real" tile in the mosaic, then the whole result tile is // "real" (i.e. not a fallback tile) bool retry = false; ImageMosaic mosaic; // keep track of failed tiles. std::vector<TileKey> failedKeys; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { GeoImage image = createImageImplementation( *k, progress ); if ( image.valid() ) { if ( !isCoverage() ) { ImageUtils::fixInternalFormat(image.getImage()); // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels. // This is not the smarter choice (in some case RGB would be sufficient) but // it ensure consistency between all images / layers. // // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images. // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint // if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } } mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // the tile source did not return a tile, so make a note of it. failedKeys.push_back( *k ); if (progress && (progress->isCanceled() || progress->needsRetry())) { retry = true; break; } } } if ( mosaic.getImages().empty() || retry ) { // if we didn't get any data, fail. OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; return GeoImage::INVALID; } // We got at least one good tile, so go through the bad ones and try to fall back on // lower resolution data to fill in the gaps. The entire mosaic must be populated or // this qualifies as a bad tile. for(std::vector<TileKey>::iterator k = failedKeys.begin(); k != failedKeys.end(); ++k) { GeoImage image; for(TileKey parentKey = k->createParentKey(); parentKey.valid() && !image.valid(); parentKey = parentKey.createParentKey()) { image = createImageImplementation( parentKey, progress ); if ( image.valid() ) { GeoImage cropped; if ( !isCoverage() ) { ImageUtils::fixInternalFormat(image.getImage()); if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } cropped = image.crop( k->getExtent(), false, image.getImage()->s(), image.getImage()->t() ); } else { // TODO: may not work.... test; tilekey extent will <> cropped extent cropped = image.crop( k->getExtent(), true, image.getImage()->s(), image.getImage()->t(), false ); } // and queue it. mosaic.getImages().push_back( TileImage(cropped.getImage(), *k) ); } } if ( !image.valid() ) { // a tile completely failed, even with fallback. Eject. OE_DEBUG << LC << "Couldn't fallback on tiles for ImageMosaic" << std::endl; // let it go. The empty areas will be filled with alpha by ImageMosaic. } } // all set. Mosaic all the images together. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); mosaicedImage = GeoImage( mosaic.createImage(), GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) ); } else { OE_DEBUG << LC << "assembleImage: no intersections (" << key.str() << ")" << std::endl; } // Final step: transform the mosaic into the requesting key's extent. if ( mosaicedImage.valid() ) { // GeoImage::reproject() will automatically crop the image to the correct extents. // so there is no need to crop after reprojection. Also note that if the SRS's are the // same (even though extents are different), then this operation is technically not a // reprojection but merely a resampling. result = mosaicedImage.reproject( key.getProfile()->getSRS(), &key.getExtent(), options().reprojectedTileSize().get(), options().reprojectedTileSize().get(), options().driver()->bilinearReprojection().get()); } // Process images with full alpha to properly support MP blending. if (result.valid() && options().featherPixels() == true && isCoverage() == false) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
GeoImage ImageLayer::createImageInKeyProfile(const TileKey& key, ProgressCallback* progress) { // If the layer is disabled, bail out. if ( !getEnabled() ) { return GeoImage::INVALID; } // Make sure the request is in range. // TODO: perhaps this should be a call to mayHaveData(key) instead. if ( !isKeyInLegalRange(key) ) { return GeoImage::INVALID; } GeoImage result; OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // the cache key combines the Key and the horizontal profile. std::string cacheKey = Stringify() << key.str() << "_" << key.getProfile()->getHorizSignature(); const CachePolicy& policy = getCacheSettings()->cachePolicy().get(); // Check the layer L2 cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); ReadResult result = bin->readObject(cacheKey, 0L); if ( result.succeeded() ) return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent()); } // locate the cache bin for the target profile for this layer: CacheBin* cacheBin = getCacheBin( key.getProfile() ); // Can we continue? Only if either: // a) there is a valid tile source plugin; // b) a tile source is not expected, meaning the subclass overrides getHeightField; or // c) we are in cache-only mode and there is a valid cache bin. bool canContinue = getTileSource() || !isTileSourceExpected() || (policy.isCacheOnly() && cacheBin != 0L); if (!canContinue) { disable("Error: layer does not have a valid TileSource, cannot create image"); return GeoImage::INVALID; } // validate the existance of a valid layer profile (unless we're in cache-only mode, in which // case there is no layer profile) if ( !policy.isCacheOnly() && !getProfile() ) { disable("Could not establish a valid profile"); return GeoImage::INVALID; } osg::ref_ptr< osg::Image > cachedImage; // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. if ( cacheBin && policy.isCacheReadable() ) { ReadResult r = cacheBin->readImage(cacheKey, 0L); if ( r.succeeded() ) { cachedImage = r.releaseImage(); ImageUtils::fixInternalFormat( cachedImage.get() ); bool expired = policy.isExpired(r.lastModifiedTime()); if (!expired) { OE_DEBUG << "Got cached image for " << key.str() << std::endl; return GeoImage( cachedImage.get(), key.getExtent() ); } else { OE_DEBUG << "Expired image for " << key.str() << std::endl; } } } // The data was not in the cache. If we are cache-only, fail sliently if ( policy.isCacheOnly() ) { // If it's cache only and we have an expired but cached image, just return it. if (cachedImage.valid()) { return GeoImage( cachedImage.get(), key.getExtent() ); } else { return GeoImage::INVALID; } } if (key.getProfile()->isHorizEquivalentTo(getProfile())) { result = createImageImplementation(key, progress); } else { // If the profiles are different, use a compositing method to assemble the tile. result = assembleImage( key, progress ); } // Normalize the image if necessary if ( result.valid() ) { ImageUtils::fixInternalFormat( result.getImage() ); } // memory cache first: if ( result.valid() && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); bin->write(cacheKey, result.getImage(), 0L); } // If we got a result, the cache is valid and we are caching in the map profile, // write to the map cache. if (result.valid() && cacheBin && policy.isCacheWriteable()) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write(cacheKey, result.getImage(), 0L); } if ( result.valid() ) { OE_DEBUG << LC << key.str() << " result OK" << std::endl; } else { OE_DEBUG << LC << key.str() << "result INVALID" << std::endl; // We couldn't get an image from the source. So see if we have an expired cached image if (cachedImage.valid()) { OE_DEBUG << LC << "Using cached but expired image for " << key.str() << std::endl; result = GeoImage( cachedImage.get(), key.getExtent()); } } return result; }