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; // keep track of failed tiles. std::vector<TileKey> failedKeys; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { GeoImage image = createImageFromTileSource( *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 = createImageFromTileSource( 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 << "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() && !isCoverage() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
GeoImage ImageLayer::createImageInNativeProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } const Profile* nativeProfile = getProfile(); if ( !nativeProfile ) { OE_WARN << LC << "Could not establish the profile" << std::endl; return GeoImage::INVALID; } GeoImage result; if ( key.getProfile()->isHorizEquivalentTo(nativeProfile) ) { // requested profile matches native profile, move along. result = createImageInKeyProfile( key, progress ); } else { // find the intersection of keys. std::vector<TileKey> nativeKeys; nativeProfile->getIntersectingTiles(key, nativeKeys); // build a mosaic of the images from the native profile keys: bool foundAtLeastOneRealTile = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k ) { bool isFallback = false; GeoImage image = createImageInKeyProfile( *k, progress ); if ( image.valid() ) { mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // if we get EVEN ONE invalid tile, we have to abort because there will be // empty spots in the mosaic. (By "invalid" we mean a tile that could not // even be resolved through the fallback procedure.) return GeoImage::INVALID; //TODO: probably need to change this so the mosaic uses alpha. } } // bail out if we got nothing. if ( mosaic.getImages().size() > 0 ) { // assemble new GeoImage from the mosaic. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); result = GeoImage( mosaic.createImage(), GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); } } return result; }
GeoImage ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback) { out_isFallback = false; const Profile* nativeProfile = getProfile(); if ( !nativeProfile ) { OE_WARN << LC << "Could not establish the profile" << std::endl; return GeoImage::INVALID; } if ( key.getProfile()->isEquivalentTo(nativeProfile) ) { // requested profile matches native profile, move along. return createImageInKeyProfile( key, progress, forceFallback, out_isFallback ); } else { // find the intersection of keys. std::vector<TileKey> nativeKeys; nativeProfile->getIntersectingTiles(key.getExtent(), nativeKeys); //OE_INFO << "KEY = " << key.str() << ":" << std::endl; //for(int i=0; i<nativeKeys.size(); ++i) // OE_INFO << " " << nativeKeys[i].str() << std::endl; // build a mosaic of the images from the native profile keys: bool foundAtLeastOneRealTile = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k ) { bool isFallback = false; GeoImage image = createImageInKeyProfile( *k, progress, true, isFallback ); if ( image.valid() ) { mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); if ( !isFallback ) foundAtLeastOneRealTile = true; } else { // if we get EVEN ONE invalid tile, we have to abort because there will be // empty spots in the mosaic. (By "invalid" we mean a tile that could not // even be resolved through the fallback procedure.) return GeoImage::INVALID; } } // bail out if we got nothing. if ( mosaic.getImages().size() == 0 ) return GeoImage::INVALID; // if the mosaic is ALL fallback data, this tile is fallback data. if ( foundAtLeastOneRealTile ) { // assemble new GeoImage from the mosaic. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); GeoImage result( mosaic.createImage(), GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); #if 1 return result; #else // let's try this. why crop? Just leave it. Faster and more compatible with NPOT // systems (like iOS) // calculate a tigher extent that matches the original input key: GeoExtent tightExtent = nativeProfile->clampAndTransformExtent( key.getExtent() ); // a non-exact crop is critical here to avoid resampling the data return result.crop( tightExtent, false, 0, 0, *_runtimeOptions.driver()->bilinearReprojection() ); #endif } else // all fallback data { GeoImage result; if ( forceFallback && key.getLevelOfDetail() > 0 ) { result = createImageInNativeProfile( key.createParentKey(), progress, forceFallback, out_isFallback ); } out_isFallback = true; return result; } //if ( !foundAtLeastOneRealTile ) // out_isFallback = true; } }
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() && *_runtimeOptions.featherPixels() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
GeoImage ImageLayer::createImageInNativeProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } const Profile* nativeProfile = getProfile(); if ( !nativeProfile ) { OE_WARN << LC << "Could not establish the profile" << std::endl; return GeoImage::INVALID; } GeoImage result; if ( key.getProfile()->isHorizEquivalentTo(nativeProfile) ) { // requested profile matches native profile, move along. result = createImageInKeyProfile( key, progress ); } else { // find the intersection of keys. std::vector<TileKey> nativeKeys; nativeProfile->getIntersectingTiles(key, nativeKeys); // build a mosaic of the images from the native profile keys: bool foundAtLeastOneRealTile = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k ) { GeoImage image = createImageInKeyProfile( *k, progress ); if ( image.valid() ) { foundAtLeastOneRealTile = true; mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // We didn't get an image so pad the mosaic with a transparent image. mosaic.getImages().push_back( TileImage(ImageUtils::createEmptyImage(getTileSize(), getTileSize()), *k)); } } // bail out if we got nothing. if ( foundAtLeastOneRealTile ) { // assemble new GeoImage from the mosaic. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); result = GeoImage( mosaic.createImage(), GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); } } return result; }