TileDrawable::TileDrawable(const TileKey& key, const RenderBindings& bindings, osg::Geometry* geometry, int tileSize, int skirtSize) : osg::Drawable( ), _key ( key ), _bindings ( bindings ), _geom ( geometry ), _tileSize ( tileSize ), _drawPatch ( false ), _skirtSize ( skirtSize ) { this->setDataVariance( DYNAMIC ); if (_geom.valid()) _geom->setDataVariance( DYNAMIC ); this->setName( key.str() ); setUseVertexBufferObjects( true ); setUseDisplayList( false ); _supportsGLSL = Registry::capabilities().supportsGLSL(); // establish uniform name IDs. _uidUniformNameID = osg::Uniform::getNameID( "oe_layer_uid" ); _orderUniformNameID = osg::Uniform::getNameID( "oe_layer_order" ); _opacityUniformNameID = osg::Uniform::getNameID( "oe_layer_opacity" ); _texMatrixUniformNameID = osg::Uniform::getNameID( "oe_layer_texMatrix" ); _texMatrixParentUniformNameID = osg::Uniform::getNameID( "oe_layer_texParentMatrix" ); _texParentExistsUniformNameID = osg::Uniform::getNameID( "oe_layer_texParentExists" ); _minRangeUniformNameID = osg::Uniform::getNameID( "oe_layer_minRange" ); _maxRangeUniformNameID = osg::Uniform::getNameID( "oe_layer_maxRange" ); _textureImageUnit = SamplerBinding::findUsage(bindings, SamplerBinding::COLOR)->unit(); _textureParentImageUnit = SamplerBinding::findUsage(bindings, SamplerBinding::COLOR_PARENT)->unit(); int tileSize2 = tileSize*tileSize; _heightCache = new float[ tileSize2 ]; for(int i=0; i<tileSize2; ++i) _heightCache[i] = 0.0f; }
TilePagedLOD::TilePagedLOD(TileGroup* tilegroup, const TileKey& subkey, const UID& engineUID, TileNodeRegistry* live, TileNodeRegistry* dead) : osg::PagedLOD(), _tilegroup ( tilegroup ), _live ( live ), _dead ( dead ), _isUpsampled ( false ), _isCanceled ( false ), _familyReady ( false ) { _numChildrenThatCannotBeExpired = 0; // set up the paging properties: _prefix = Stringify() << subkey.str() << "." << engineUID << "."; this->setRange ( 0, 0.0f, FLT_MAX ); this->setFileName( 0, Stringify() << _prefix << ".osgearth_engine_mp_tile" ); }
TileNode::TileNode( const TileKey& key, const TileModel* model ) : _key ( key ), _model ( model ), _bornTime ( 0.0 ), _lastTraversalFrame( 0 ) { this->setName( key.str() ); osg::StateSet* stateset = getOrCreateStateSet(); // TileKey uniform. _keyUniform = new osg::Uniform(osg::Uniform::FLOAT_VEC4, "oe_tile_key"); _keyUniform->setDataVariance( osg::Object::STATIC ); _keyUniform->set( osg::Vec4f(0,0,0,0) ); stateset->addUniform( _keyUniform ); // born-on date uniform. _bornUniform = new osg::Uniform(osg::Uniform::FLOAT, "oe_tile_birthtime"); _bornUniform->set( -1.0f ); stateset->addUniform( _bornUniform ); }
void Profile::getIntersectingTiles(const TileKey& key, std::vector<TileKey>& out_intersectingKeys) const { OE_DEBUG << "GET ISECTING TILES for key " << key.str() << " -----------------" << std::endl; //If the profiles are exactly equal, just add the given tile key. if ( isHorizEquivalentTo( key.getProfile() ) ) { //Clear the incoming list out_intersectingKeys.clear(); out_intersectingKeys.push_back(key); } else { // figure out which LOD in the local profile is a best match for the LOD // in the source LOD in terms of resolution. unsigned localLOD = getEquivalentLOD(key.getProfile(), key.getLOD()); getIntersectingTiles(key.getExtent(), localLOD, out_intersectingKeys); //OE_INFO << LC << "GIT, key="<< key.str() << ", localLOD=" << localLOD // << ", resulted in " << out_intersectingKeys.size() << " tiles" << std::endl; } }
GeoImage ImageLayer::createImageInKeyProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } // 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; } 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() ); // validate that we have either a valid tile source, or we're cache-only. if (getTileSource() || (cacheBin && policy.isCacheOnly())) { //nop = OK. } else { 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; } } // Get an image from the underlying TileSource. result = createImageFromTileSource( 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; }
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::createImageFromTileSource(const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback) { // Results: // // * return an osg::Image matching the key extent is all goes well; // // * return NULL to indicate that the key exceeds the maximum LOD of the source data, // and that the engine may need to generate a "fallback" tile if necessary; // // deprecated: // * return an "empty image" if the LOD is valid BUT the key does not intersect the // source's data extents. out_isFallback = false; TileSource* source = getTileSource(); if ( !source ) return GeoImage::INVALID; // If the profiles are different, use a compositing method to assemble the tile. if ( !key.getProfile()->isEquivalentTo( getProfile() ) ) { return assembleImageFromTileSource( key, progress, out_isFallback ); } // Good to go, ask the tile source for an image: osg::ref_ptr<TileSource::ImageOperation> op = _preCacheOp; osg::ref_ptr<osg::Image> result; if ( forceFallback ) { // check if the tile source has any data coverage for the requested key. // the LOD is ignore here and checked later if ( !source->hasDataInExtent( key.getExtent() ) ) { OE_DEBUG << LC << "createImageFromTileSource: hasDataInExtent(" << key.str() << ") == false" << std::endl; return GeoImage::INVALID; } TileKey finalKey = key; while( !result.valid() && finalKey.valid() ) { if ( !source->getBlacklist()->contains( finalKey.getTileId() ) && source->hasDataForFallback(finalKey)) { result = source->createImage( finalKey, op.get(), progress ); if ( result.valid() ) { if ( finalKey.getLevelOfDetail() != key.getLevelOfDetail() ) { // crop the fallback image to match the input key, and ensure that it remains the // same pixel size; because chances are if we're requesting a fallback that we're // planning to mosaic it later, and the mosaicer requires same-size images. GeoImage raw( result.get(), finalKey.getExtent() ); GeoImage cropped = raw.crop( key.getExtent(), true, raw.getImage()->s(), raw.getImage()->t(), *_runtimeOptions.driver()->bilinearReprojection() ); result = cropped.takeImage(); } } } if ( !result.valid() ) { finalKey = finalKey.createParentKey(); out_isFallback = true; } } if ( !result.valid() ) { result = 0L; //result = _emptyImage.get(); finalKey = key; } } else { // Fail is the image is blacklisted. if ( source->getBlacklist()->contains( key.getTileId() ) ) { OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl; return GeoImage::INVALID; } if ( !source->hasData( key ) ) { OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl; return GeoImage::INVALID; } result = source->createImage( key, op.get(), progress ); } // Process images with full alpha to properly support MP blending. if ( result != 0L && *_runtimeOptions.featherPixels()) { ImageUtils::featherAlphaRegions( result.get() ); } // If image creation failed (but was not intentionally canceled), // blacklist this tile for future requests. if ( result == 0L && (!progress || !progress->isCanceled()) ) { source->getBlacklist()->add( key.getTileId() ); } return GeoImage(result.get(), key.getExtent()); }
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; }
void FractalElevationLayer::createImplementation(const TileKey& key, osg::ref_ptr<osg::HeightField>& out_hf, osg::ref_ptr<NormalMap>& out_normalMap, ProgressCallback* progress) { double min_n = FLT_MAX, max_n = -FLT_MAX; double min_h = FLT_MAX, max_h = -FLT_MAX; double h_mean = 0.0; ImageUtils::PixelReader noise1(_noiseImage1.get()); noise1.setBilinear(true); ImageUtils::PixelReader noise2(_noiseImage2.get()); noise2.setBilinear(true); osg::ref_ptr<osg::HeightField> hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), getTileSize(), getTileSize(), 0u); // land cover tile GeoImage lcTile; osg::ref_ptr<LandCoverLayer> lcLayer; _landCover.lock(lcLayer); if (lcLayer.valid()) { lcTile = lcLayer->createImage(key, progress); } for (int s = 0; s < getTileSize(); ++s) { for (int t = 0; t < getTileSize(); ++t) { double u = (double)s / (double)(getTileSize() - 1); double v = (double)t / (double)(getTileSize() - 1); double n = 0.0; double uScaled, vScaled; double finalScale = 4.0; // Step 1 if (_noiseImage1.valid()) { uScaled = u, vScaled = v; scaleCoordsToLOD(uScaled, vScaled, options().baseLOD().get(), key); double uMod = fmod(uScaled, 1.0); double vMod = fmod(vScaled, 1.0); n += noise1(uMod, vMod).r() - 0.5; finalScale *= 0.5; } if (_noiseImage2.valid()) { uScaled = u, vScaled = v; scaleCoordsToLOD(uScaled, vScaled, options().baseLOD().get() + 3, key); double uMod = fmod(uScaled, 1.0); double vMod = fmod(vScaled, 1.0); n += noise2(uMod, vMod).r() - 0.5; finalScale *= 0.5; } n *= finalScale; // default amplitude: float amp = options().amplitude().get(); // if we have land cover mappings, use them: if (lcTile.valid()) { const LandCoverClass* lcClass = lcLayer->getClassByUV(lcTile, u, v); if (lcClass) { const FractalElevationLayerLandCoverMapping* mapping = getMapping(lcClass); if (mapping) { amp = mapping->amplitude.getOrUse(amp); } } } hf->setHeight(s, t, n * amp); if (_debug) { h_mean += hf->getHeight(s, t); min_n = std::min(min_n, n); max_n = std::max(max_n, n); min_h = std::min(min_h, (double)hf->getHeight(s, t)); max_h = std::max(max_h, (double)hf->getHeight(s, t)); } } } if (_debug) { h_mean /= double(getTileSize()*getTileSize()); double q_mean = 0.0; for (int s = 0; s < getTileSize(); ++s) { for (int t = 0; t < getTileSize(); ++t) { double q = hf->getHeight(s, t) - h_mean; q_mean += q*q; } } double stdev = sqrt(q_mean / double(getTileSize()*getTileSize())); OE_INFO << LC << "Tile " << key.str() << " Hmean=" << h_mean << ", stdev=" << stdev << ", n[" << min_n << ", " << max_n << "] " << "h[" << min_h << ", " << max_h << "]\n"; } out_hf = hf.release(); out_normalMap = 0L; }
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 the max data level, which limits the LOD of available data. if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() ) { 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(), getCachePolicy().getMinAcceptTime() ); if ( r.succeeded() ) { ImageUtils::normalizeImage( r.getImage() ); return GeoImage( r.releaseImage(), key.getExtent() ); } //else if ( r.code() == ReadResult::RESULT_EXPIRED ) //{ // OE_INFO << LC << getName() << " : " << key.str() << " record expired!" << 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; }
bool ElevationLayerVector::populateHeightFieldAndNormalMap(osg::HeightField* hf, NormalMap* normalMap, const TileKey& key, const Profile* haeProfile, ElevationInterpolation interpolation, ProgressCallback* progress ) const { // heightfield must already exist. if ( !hf ) return false; METRIC_SCOPED("ElevationLayer.populateHeightField"); // if the caller provided an "HAE map profile", he wants an HAE elevation grid even if // the map profile has a vertical datum. This is the usual case when building the 3D // terrain, for example. Construct a temporary key that doesn't have the vertical // datum info and use that to query the elevation data. TileKey keyToUse = key; if ( haeProfile ) { keyToUse = TileKey(key.getLOD(), key.getTileX(), key.getTileY(), haeProfile ); } // Collect the valid layers for this tile. LayerDataVector contenders; LayerDataVector offsets; #ifdef ANALYZE struct LayerAnalysis { LayerAnalysis() : samples(0), used(false), failed(false), fallback(false), actualKeyValid(true) { } int samples; bool used; bool failed; bool fallback; bool actualKeyValid; std::string message; }; std::map<ElevationLayer*, LayerAnalysis> layerAnalysis; #endif // Track the number of layers that would return fallback data. unsigned numFallbackLayers = 0; // Check them in reverse order since the highest priority is last. for (int i = size()-1; i>=0; --i) //for(ElevationLayerVector::const_reverse_iterator i = this->rbegin(); i != this->rend(); ++i) { ElevationLayer* layer = (*this)[i].get(); //i->get(); if ( layer->getEnabled() && layer->getVisible() ) { // calculate the resolution-mapped key (adjusted for tile resolution differential). TileKey mappedKey = keyToUse.mapResolution( hf->getNumColumns(), layer->getTileSize() ); bool useLayer = true; TileKey bestKey( mappedKey ); // Check whether the non-mapped key is valid according to the user's min/max level settings: if ( !layer->isKeyInLegalRange(key) ) { useLayer = false; } // Find the "best available" mapped key from the tile source: else { bestKey = layer->getBestAvailableTileKey(mappedKey); if (bestKey.valid()) { // If the bestKey is not the mappedKey, this layer is providing // fallback data (data at a lower resolution than requested) if ( mappedKey != bestKey ) { numFallbackLayers++; } } else { useLayer = false; } } if ( useLayer ) { if ( layer->isOffset() ) { offsets.push_back(LayerData()); LayerData& ld = offsets.back(); ld.layer = layer; ld.key = bestKey; ld.index = i; } else { contenders.push_back(LayerData()); LayerData& ld = contenders.back(); ld.layer = layer; ld.key = bestKey; ld.index = i; } #ifdef ANALYZE layerAnalysis[layer].used = true; #endif } } } // nothing? bail out. if ( contenders.empty() && offsets.empty() ) { return false; } // if everything is fallback data, bail out. if ( contenders.size() + offsets.size() == numFallbackLayers ) { return false; } // Sample the layers into our target. unsigned numColumns = hf->getNumColumns(); unsigned numRows = hf->getNumRows(); double xmin = key.getExtent().xMin(); double ymin = key.getExtent().yMin(); double dx = key.getExtent().width() / (double)(numColumns-1); double dy = key.getExtent().height() / (double)(numRows-1); // We will load the actual heightfields on demand. We might not need them all. GeoHeightFieldVector heightFields(contenders.size()); GeoHeightFieldVector offsetFields(offsets.size()); std::vector<bool> heightFallback(contenders.size(), false); std::vector<bool> heightFailed(contenders.size(), false); std::vector<bool> offsetFailed(offsets.size(), false); // The maximum number of heightfields to keep in this local cache const unsigned maxHeightFields = 50; unsigned numHeightFieldsInCache = 0; const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); bool realData = false; unsigned int total = numColumns * numRows; // query resolution interval (x, y) of each sample. osg::ref_ptr<osg::ShortArray> deltaLOD = new osg::ShortArray(total); int nodataCount = 0; TileKey scratchKey; // Storage if a new key needs to be constructed bool requiresResample = true; // If we only have a single contender layer, and the tile is the same size as the requested // heightfield then we just use it directly and avoid having to resample it if (contenders.size() == 1 && offsets.empty()) { ElevationLayer* layer = contenders[0].layer.get(); TileKey& contenderKey = contenders[0].key; GeoHeightField layerHF = layer->createHeightField(contenderKey, 0); if (layerHF.valid()) { if (layerHF.getHeightField()->getNumColumns() == hf->getNumColumns() && layerHF.getHeightField()->getNumRows() == hf->getNumRows()) { requiresResample = false; memcpy(hf->getFloatArray()->asVector().data(), layerHF.getHeightField()->getFloatArray()->asVector().data(), sizeof(float) * hf->getFloatArray()->size() ); deltaLOD->resize(hf->getFloatArray()->size(), 0); realData = true; } } } // If we need to mosaic multiple layers or resample it to a new output tilesize go through a resampling loop. if (requiresResample) { for (unsigned c = 0; c < numColumns; ++c) { double x = xmin + (dx * (double)c); // periodically check for cancelation if (progress && progress->isCanceled()) { return false; } for (unsigned r = 0; r < numRows; ++r) { double y = ymin + (dy * (double)r); // Collect elevations from each layer as necessary. int resolvedIndex = -1; osg::Vec3 normal_sum(0, 0, 0); for (int i = 0; i < contenders.size() && resolvedIndex < 0; ++i) { ElevationLayer* layer = contenders[i].layer.get(); TileKey& contenderKey = contenders[i].key; int index = contenders[i].index; if (heightFailed[i]) continue; TileKey* actualKey = &contenderKey; GeoHeightField& layerHF = heightFields[i]; if (!layerHF.valid()) { // We couldn't get the heightfield from the cache, so try to create it. // We also fallback on parent layers to make sure that we have data at the location even if it's fallback. while (!layerHF.valid() && actualKey->valid() && layer->isKeyInLegalRange(*actualKey)) { layerHF = layer->createHeightField(*actualKey, progress); if (!layerHF.valid()) { if (actualKey != &scratchKey) { scratchKey = *actualKey; actualKey = &scratchKey; } *actualKey = actualKey->createParentKey(); } } // Mark this layer as fallback if necessary. if (layerHF.valid()) { heightFallback[i] = (*actualKey != contenderKey); // actualKey != contenders[i].second; numHeightFieldsInCache++; } else { heightFailed[i] = true; #ifdef ANALYZE layerAnalysis[layer].failed = true; layerAnalysis[layer].actualKeyValid = actualKey->valid(); if (progress) layerAnalysis[layer].message = progress->message(); #endif continue; } } if (layerHF.valid()) { bool isFallback = heightFallback[i]; #ifdef ANALYZE layerAnalysis[layer].fallback = isFallback; #endif // We only have real data if this is not a fallback heightfield. if (!isFallback) { realData = true; } float elevation; if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation)) { if (elevation != NO_DATA_VALUE) { // remember the index so we can only apply offset layers that // sit on TOP of this layer. resolvedIndex = index; hf->setHeight(c, r, elevation); #ifdef ANALYZE layerAnalysis[layer].samples++; #endif if (deltaLOD) { (*deltaLOD)[r*numColumns + c] = key.getLOD() - actualKey->getLOD(); } } else { ++nodataCount; } } } // Clear the heightfield cache if we have too many heightfields in the cache. if (numHeightFieldsInCache >= maxHeightFields) { //OE_NOTICE << "Clearing cache" << std::endl; for (unsigned int k = 0; k < heightFields.size(); k++) { heightFields[k] = GeoHeightField::INVALID; heightFallback[k] = false; } numHeightFieldsInCache = 0; } } for (int i = offsets.size() - 1; i >= 0; --i) { // Only apply an offset layer if it sits on top of the resolved layer // (or if there was no resolved layer). if (resolvedIndex >= 0 && offsets[i].index < resolvedIndex) continue; TileKey &contenderKey = offsets[i].key; if (offsetFailed[i] == true) continue; GeoHeightField& layerHF = offsetFields[i]; if (!layerHF.valid()) { ElevationLayer* offset = offsets[i].layer.get(); layerHF = offset->createHeightField(contenderKey, progress); if (!layerHF.valid()) { offsetFailed[i] = true; continue; } } // If we actually got a layer then we have real data realData = true; float elevation = 0.0f; if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) && elevation != NO_DATA_VALUE) { hf->getHeight(c, r) += elevation; // Update the resolution tracker to account for the offset. Sadly this // will wipe out the resolution of the actual data, and might result in // normal faceting. See the comments on "createNormalMap" for more info if (deltaLOD) { (*deltaLOD)[r*numColumns + c] = key.getLOD() - contenderKey.getLOD(); } } } } } } if (normalMap) { // periodically check for cancelation if (progress && progress->isCanceled()) { return false; } createNormalMap(key.getExtent(), hf, deltaLOD.get(), normalMap); } #ifdef ANALYZE { static Threading::Mutex m; Threading::ScopedMutexLock lock(m); std::cout << key.str() << ": "; for (std::map<ElevationLayer*, LayerAnalysis>::const_iterator i = layerAnalysis.begin(); i != layerAnalysis.end(); ++i) { std::cout << i->first->getName() << " used=" << i->second.used << " failed=" << i->second.failed << " akv=" << i->second.actualKeyValid << " fallback=" << i->second.fallback << " samples=" << i->second.samples << " msg=" << i->second.message << "; "; } std::cout << std::endl; } #endif if (progress && progress->isCanceled()) { return false; } // Return whether or not we actually read any real data return realData; }
void TileModelFactory::buildElevation(const TileKey& key, const MapFrame& frame, bool accumulate, bool buildTexture, TileModel* model, ProgressCallback* progress) { const MapInfo& mapInfo = frame.getMapInfo(); const osgEarth::ElevationInterpolation& interp = frame.getMapOptions().elevationInterpolation().get(); // Request a heightfield from the map, falling back on lower resolution tiles // if necessary (fallback=true) osg::ref_ptr<osg::HeightField> hf; bool isFallback = false; // look up the parent's heightfield to use as a template osg::ref_ptr<osg::HeightField> parentHF; TileKey parentKey = key.createParentKey(); if ( accumulate ) { osg::ref_ptr<TileNode> parentNode; if (_liveTiles->get(parentKey, parentNode)) { parentHF = parentNode->getTileModel()->_elevationData.getHeightField(); if ( _debug && key.getLOD() > 0 && !parentHF.valid() ) { OE_NOTICE << LC << "Could not find a parent tile HF for " << key.str() << "\n"; } } } // Make a new heightfield: if (_meshHFCache->getOrCreateHeightField(frame, key, parentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress)) { model->_elevationData = TileModel::ElevationData( hf, GeoLocator::createForKey( key, mapInfo ), isFallback ); // Edge normalization: requires adjacency information if ( _terrainOptions.normalizeEdges() == true ) { for( int x=-1; x<=1; x++ ) { for( int y=-1; y<=1; y++ ) { if ( x != 0 || y != 0 ) { TileKey neighborKey = key.createNeighborKey(x, y); if ( neighborKey.valid() ) { osg::ref_ptr<osg::HeightField> neighborParentHF; if ( accumulate ) { TileKey neighborParentKey = neighborKey.createParentKey(); if (neighborParentKey == parentKey) { neighborParentHF = parentHF; } else { osg::ref_ptr<TileNode> neighborParentNode; if (_liveTiles->get(neighborParentKey, neighborParentNode)) { neighborParentHF = neighborParentNode->getTileModel()->_elevationData.getHeightField(); } } } // only pull the tile if we have a valid parent HF for it -- otherwise // you might get a flat tile when upsampling data. if ( neighborParentHF.valid() ) { osg::ref_ptr<osg::HeightField> hf; if (_meshHFCache->getOrCreateHeightField(frame, neighborKey, neighborParentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) ) { model->_elevationData.setNeighbor( x, y, hf.get() ); } } } } } } // parent too. if ( parentHF.valid() ) { model->_elevationData.setParent( parentHF.get() ); } } if ( buildTexture ) { model->generateElevationTexture(); } } }
void TerrainTileModelFactory::addElevation(TerrainTileModel* model, const Map* map, const TileKey& key, const CreateTileModelFilter& filter, unsigned border, ProgressCallback* progress) { // make an elevation layer. OE_START_TIMER(fetch_elevation); if (!filter.empty() && !filter.elevation().isSetTo(true)) return; const osgEarth::ElevationInterpolation& interp = map->getMapOptions().elevationInterpolation().get(); // Request a heightfield from the map. osg::ref_ptr<osg::HeightField> mainHF; osg::ref_ptr<NormalMap> normalMap; bool hfOK = getOrCreateHeightField(map, key, SAMPLE_FIRST_VALID, interp, border, mainHF, normalMap, progress) && mainHF.valid(); if (hfOK == false && key.getLOD() == _options.firstLOD().get()) { OE_DEBUG << LC << "No HF at key " << key.str() << ", making placeholder" << std::endl; mainHF = new osg::HeightField(); mainHF->allocate(1, 1); mainHF->setHeight(0, 0, 0.0f); hfOK = true; } if (hfOK && mainHF.valid()) { osg::ref_ptr<TerrainTileElevationModel> layerModel = new TerrainTileElevationModel(); layerModel->setHeightField( mainHF.get() ); // pre-calculate the min/max heights: for( unsigned col = 0; col < mainHF->getNumColumns(); ++col ) { for( unsigned row = 0; row < mainHF->getNumRows(); ++row ) { float h = mainHF->getHeight(col, row); if ( h > layerModel->getMaxHeight() ) layerModel->setMaxHeight( h ); if ( h < layerModel->getMinHeight() ) layerModel->setMinHeight( h ); } } // needed for normal map generation model->heightFields().setNeighbor(0, 0, mainHF.get()); // convert the heightfield to a 1-channel 32-bit fp image: ImageToHeightFieldConverter conv; osg::Image* hfImage = conv.convertToR32F(mainHF.get()); if ( hfImage ) { // Made an image, so store this as a texture with no matrix. osg::Texture* texture = createElevationTexture( hfImage ); layerModel->setTexture( texture ); model->elevationModel() = layerModel.get(); } if (normalMap.valid()) { TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel(); layerModel->setName( "oe_normal_map" ); // Made an image, so store this as a texture with no matrix. osg::Texture* texture = createNormalTexture(normalMap.get()); layerModel->setTexture( texture ); model->normalModel() = layerModel; } } if (progress) progress->stats()["fetch_elevation_time"] += OE_STOP_TIMER(fetch_elevation); }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { GeoHeightField result; osg::ref_ptr<osg::HeightField> hf; // If the layer is disabled, bail out. if ( getEnabled() == false ) { return GeoHeightField::INVALID; } // Check the memory cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); ReadResult cacheResult = bin->readObject(key.str() ); if ( cacheResult.succeeded() ) { result = GeoHeightField( static_cast<osg::HeightField*>(cacheResult.releaseObject()), key.getExtent()); } //_memCache->dumpStats(key.getProfile()->getFullSignature()); } if ( !result.valid() ) { // See if there's a persistent cache. 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 heightfield" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // Now attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; osg::ref_ptr< osg::HeightField > cachedHF; if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readObject( key.str() ); if ( r.succeeded() ) { bool expired = getCachePolicy().isExpired(r.lastModifiedTime()); cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF) ) { if (!expired) { hf = cachedHF; fromCache = true; } } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !hf.valid() && isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !hf.valid() ) { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; if ( !isKeyInRange(key) ) return GeoHeightField::INVALID; // build a HF from the TileSource. hf = createHeightFieldFromTileSource( key, progress ); // validate it to make sure it's legal. if ( hf.valid() && !validateHeightField(hf.get()) ) { OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl; hf = 0L; // to fall back on cached data if possible. } // memory cache first: if ( hf && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); bin->write(key.str(), hf.get()); } // cache if necessary if ( hf && cacheBin && !fromCache && getCachePolicy().isCacheWriteable() ) { cacheBin->write( key.str(), hf ); } // We have an expired heightfield from the cache and no new data from the TileSource. So just return the cached data. if (!hf.valid() && cachedHF.valid()) { OE_DEBUG << LC << "Using cached but expired heightfield for " << key.str() << std::endl; hf = cachedHF; } if ( !hf.valid() ) { return GeoHeightField::INVALID; } // Set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(hf->getNumColumns()-1); double dy = (maxy - miny)/(double)(hf->getNumRows()-1); hf->setXInterval( dx ); hf->setYInterval( dy ); hf->setBorderWidth( 0 ); } if ( hf.valid() ) { result = GeoHeightField( hf.get(), key.getExtent() ); } } // post-processing: if ( result.valid() ) { if ( _runtimeOptions.noDataPolicy() == NODATA_MSL ) { // requested VDatum: const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum(); const Geoid* geoid = 0L; // if there's an output vdatum, just set all invalid's to zero MSL. if ( outputVDatum == 0L ) { // if the output is geodetic (HAE), but the input has a geoid, // use that geoid to populate the invalid data at sea level. const VerticalDatum* profileDatum = getProfile()->getSRS()->getVerticalDatum(); if ( profileDatum ) geoid = profileDatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( result.getHeightField(), result.getExtent(), NO_DATA_VALUE, geoid ); } } return result; }
bool TerrainLayer::isCached(const TileKey& key) const { CacheBin* bin = const_cast<TerrainLayer*>(this)->getCacheBin( key.getProfile() ); return bin ? bin->isCached(key.str()) : false; }
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(), 0); 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; } // 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(), getCachePolicy().getMinAcceptTime() ); if ( r.succeeded() ) { ImageUtils::normalizeImage( r.getImage() ); return GeoImage( r.releaseImage(), key.getExtent() ); } } // 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 ); // 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; } return result; }
bool ElevationQuery::getElevationImpl(const osg::Vec3d& point, const SpatialReference* pointSRS, double& out_elevation, double desiredResolution, double* out_actualResolution) { if ( _maxDataLevel == 0 || _tileSize == 0 ) { // this means there are no heightfields. out_elevation = 0.0; return true; } // this is the ideal LOD for the requested resolution: unsigned int idealLevel = desiredResolution > 0.0 ? _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, _tileSize ) : _maxDataLevel; // based on the heightfields available, this is the best we can theorically do: unsigned int bestAvailLevel = osg::minimum( idealLevel, _maxDataLevel ); if (_maxLevelOverride >= 0) { bestAvailLevel = osg::minimum(bestAvailLevel, (unsigned int)_maxLevelOverride); } // transform the input coords to map coords: osg::Vec3d mapPoint = point; if ( pointSRS && !pointSRS->isEquivalentTo( _mapf.getProfile()->getSRS() ) ) { if ( !pointSRS->transform2D( point.x(), point.y(), _mapf.getProfile()->getSRS(), mapPoint.x(), mapPoint.y() ) ) { OE_WARN << LC << "Fail: coord transform failed" << std::endl; return false; } } osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<osgTerrain::TerrainTile> tile; // get the tilekey corresponding to the tile we need: TileKey key = _mapf.getProfile()->createTileKey( mapPoint.x(), mapPoint.y(), bestAvailLevel ); if ( !key.valid() ) { OE_WARN << LC << "Fail: coords fall outside map" << std::endl; return false; } // Check the tile cache. Note that the TileSource already likely has a MemCache // attached to it. We employ a secondary cache here for a couple reasons. One, this // cache will store not only the heightfield, but also the tesselated tile in the event // that we're using GEOMETRIC mode. Second, since the call the getHeightField can // fallback on a lower resolution, this cache will hold the final resolution heightfield // instead of trying to fetch the higher resolution one each tiem. TileCache::Record record = _tileCache.get( key ); if ( record.valid() ) tile = record.value().get(); // if we found it, make sure it has a heightfield in it: if ( tile.valid() ) { osgTerrain::HeightFieldLayer* layer = dynamic_cast<osgTerrain::HeightFieldLayer*>(tile->getElevationLayer()); if ( layer ) hf = layer->getHeightField(); if ( !hf.valid() ) tile = 0L; } // if we didn't find it (or it didn't have heightfield data), build it. if ( !tile.valid() ) { // generate the heightfield corresponding to the tile key, automatically falling back // on lower resolution if necessary: _mapf.getHeightField( key, true, hf, 0L, _interpolation ); // bail out if we could not make a heightfield a all. if ( !hf.valid() ) { OE_WARN << LC << "Unable to create heightfield for key " << key.str() << std::endl; return false; } // All this stuff is requires for GEOMETRIC mode. An optimization would be to // defer this so that PARAMETRIC mode doesn't waste time GeoLocator* locator = GeoLocator::createForKey( key, _mapf.getMapInfo() ); tile = new osgTerrain::TerrainTile(); osgTerrain::HeightFieldLayer* layer = new osgTerrain::HeightFieldLayer( hf.get() ); layer->setLocator( locator ); tile->setElevationLayer( layer ); tile->setRequiresNormals( false ); tile->setTerrainTechnique( new osgTerrain::GeometryTechnique ); // store it in the local tile cache. _tileCache.insert( key, tile.get() ); } OE_DEBUG << LC << "LRU Cache, hit ratio = " << _tileCache.getStats()._hitRatio << std::endl; // see what the actual resolution of the heightfield is. if ( out_actualResolution ) *out_actualResolution = (double)hf->getXInterval(); // finally it's time to get a height value: if ( _technique == TECHNIQUE_PARAMETRIC ) { const GeoExtent& extent = key.getExtent(); double xInterval = extent.width() / (double)(hf->getNumColumns()-1); double yInterval = extent.height() / (double)(hf->getNumRows()-1); out_elevation = (double) HeightFieldUtils::getHeightAtLocation( hf.get(), mapPoint.x(), mapPoint.y(), extent.xMin(), extent.yMin(), xInterval, yInterval ); return true; } else // ( _technique == TECHNIQUE_GEOMETRIC ) { osg::Vec3d start, end, zero; if ( _mapf.getMapInfo().isGeocentric() ) { const SpatialReference* mapSRS = _mapf.getProfile()->getSRS(); mapSRS->transformToECEF( osg::Vec3d(mapPoint.y(), mapPoint.x(), 50000.0), start ); mapSRS->transformToECEF( osg::Vec3d(mapPoint.y(), mapPoint.x(), -50000.0), end ); mapSRS->transformToECEF( osg::Vec3d(mapPoint.y(), mapPoint.x(), 0.0), zero ); } else // PROJECTED { start.set( mapPoint.x(), mapPoint.y(), 50000.0 ); end.set ( mapPoint.x(), mapPoint.y(), -50000.0 ); zero.set ( mapPoint.x(), mapPoint.y(), 0.0 ); } osgUtil::LineSegmentIntersector* i = new osgUtil::LineSegmentIntersector( start, end ); osgUtil::IntersectionVisitor iv; iv.setIntersector( i ); tile->accept( iv ); osgUtil::LineSegmentIntersector::Intersections& results = i->getIntersections(); if ( !results.empty() ) { const osgUtil::LineSegmentIntersector::Intersection& result = *results.begin(); osg::Vec3d isectPoint = result.getWorldIntersectPoint(); out_elevation = (isectPoint-end).length2() > (zero-end).length2() ? (isectPoint-zero).length() : -(isectPoint-zero).length(); return true; } OE_DEBUG << LC << "No intersection" << std::endl; return false; } }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { METRIC_SCOPED_EX("ElevationLayer::createHeightField", 2, "key", key.str().c_str(), "name", getName().c_str()); if (getStatus().isError()) { return GeoHeightField::INVALID; } // If the layer is disabled, bail out. if ( getEnabled() == false ) { return GeoHeightField::INVALID; } GeoHeightField result; osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<NormalMap> normalMap; // Check the memory cache first bool fromMemCache = false; // cache key combines the key with the full signature (incl vdatum) // the cache key combines the Key and the horizontal profile. std::string cacheKey = Cache::makeCacheKey( Stringify() << key.str() << "-" << key.getProfile()->getHorizSignature(), "elevation"); const CachePolicy& policy = getCacheSettings()->cachePolicy().get(); if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); ReadResult cacheResult = bin->readObject(cacheKey, 0L); if ( cacheResult.succeeded() ) { result = GeoHeightField( static_cast<osg::HeightField*>(cacheResult.releaseObject()), key.getExtent()); fromMemCache = true; } } if ( !result.valid() ) { // See if there's a persistent cache. 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 heightfield"); return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !policy.isCacheOnly() && !getProfile() ) { disable("Could not establish a valid profile.. did you set one?"); return GeoHeightField::INVALID; } // Now attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; osg::ref_ptr< osg::HeightField > cachedHF; if ( cacheBin && policy.isCacheReadable() ) { ReadResult r = cacheBin->readObject(cacheKey, 0L); if ( r.succeeded() ) { bool expired = policy.isExpired(r.lastModifiedTime()); cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF.get()) ) { if (!expired) { hf = cachedHF; fromCache = true; } } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !hf.valid() && policy.isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !hf.valid() ) { if ( !isKeyInLegalRange(key) ) return GeoHeightField::INVALID; // If no tile source is expected, create a height field by calling // the raw inheritable method. if (!isTileSourceExpected()) { createImplementation(key, hf, normalMap, progress); //hf = createHeightFieldImplementation(key, progress); } else { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; // build a HF from the TileSource. //hf = createHeightFieldImplementation( key, progress ); createImplementation(key, hf, normalMap, progress); } // Check for cancelation before writing to a cache if (progress && progress->isCanceled()) { return GeoHeightField::INVALID; } // validate it to make sure it's legal. if ( hf.valid() && !validateHeightField(hf.get()) ) { OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl; hf = 0L; // to fall back on cached data if possible. } // cache if necessary if ( hf && cacheBin && !fromCache && policy.isCacheWriteable() ) { cacheBin->write(cacheKey, hf.get(), 0L); } // We have an expired heightfield from the cache and no new data from the TileSource. So just return the cached data. if (!hf.valid() && cachedHF.valid()) { OE_DEBUG << LC << "Using cached but expired heightfield for " << key.str() << std::endl; hf = cachedHF; } if ( !hf.valid() ) { return GeoHeightField::INVALID; } // Set up the heightfield params. double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(hf->getNumColumns()-1); double dy = (maxy - miny)/(double)(hf->getNumRows()-1); hf->setXInterval( dx ); hf->setYInterval( dy ); hf->setBorderWidth( 0 ); } if ( hf.valid() ) { result = GeoHeightField( hf.get(), normalMap.get(), key.getExtent() ); } } // Check for cancelation before writing to a cache: if ( progress && progress->isCanceled() ) { return GeoHeightField::INVALID; } // post-processing -- must be done before caching because it may alter the heightfield data if ( result.valid() && !fromMemCache && hf.valid() ) { if ( options().noDataPolicy() == NODATA_MSL ) { // requested VDatum: const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum(); const Geoid* geoid = 0L; // if there's an output vdatum, just set all invalid's to zero MSL. if ( outputVDatum == 0L ) { // if the output is geodetic (HAE), but the input has a geoid, // use that geoid to populate the invalid data at sea level. const VerticalDatum* profileDatum = getProfile()->getSRS()->getVerticalDatum(); if ( profileDatum ) geoid = profileDatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( hf.get(), result.getExtent(), NO_DATA_VALUE, geoid ); } } // write to mem cache if needed: if ( result.valid() && !fromMemCache && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); bin->write(cacheKey, result.getHeightField(), 0L); } return result; }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { osg::HeightField* result = 0L; // If the layer is disabled, bail out. if ( _runtimeOptions.enabled().isSetTo( false ) ) { return GeoHeightField::INVALID; } 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 heightfield" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readObject( key.str() ); if ( r.succeeded() ) { result = r.release<osg::HeightField>(); if ( result ) fromCache = true; } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !result && isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !result ) { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; if ( !isKeyValid(key) ) return GeoHeightField::INVALID; // build a HF from the TileSource. result = createHeightFieldFromTileSource( key, progress ); } // cache if necessary if ( result && cacheBin && !fromCache && getCachePolicy().isCacheWriteable() ) { cacheBin->write( key.str(), result ); } if ( result ) { // Set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); result->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(result->getNumColumns()-1); double dy = (maxy - miny)/(double)(result->getNumRows()-1); result->setXInterval( dx ); result->setYInterval( dy ); result->setBorderWidth( 0 ); } return result ? GeoHeightField( result, key.getExtent() ) : GeoHeightField::INVALID; }
bool HeightFieldCache::getOrCreateHeightField(const MapFrame& frame, const TileKey& key, //bool cummulative, const osg::HeightField* parent_hf, osg::ref_ptr<osg::HeightField>& out_hf, bool& out_isFallback, ElevationSamplePolicy samplePolicy, ElevationInterpolation interp, ProgressCallback* progress ) { // default out_isFallback = false; // check the quick cache. HFKey cachekey; cachekey._key = key; cachekey._revision = frame.getRevision(); cachekey._samplePolicy = samplePolicy; if (progress) progress->stats()["hfcache_try_count"] += 1; bool hit = false; LRUCache<HFKey,HFValue>::Record rec; if ( _cache.get(cachekey, rec) ) { out_hf = rec.value()._hf.get(); out_isFallback = rec.value()._isFallback; if (progress) { progress->stats()["hfcache_hit_count"] += 1; progress->stats()["hfcache_hit_rate"] = progress->stats()["hfcache_hit_count"]/progress->stats()["hfcache_try_count"]; } return true; } // Find the parent tile and start with its heightfield. if ( parent_hf ) { TileKey parentKey = key.createParentKey(); out_hf = HeightFieldUtils::createSubSample( parent_hf, parentKey.getExtent(), key.getExtent(), interp ); if ( !out_hf.valid() && ((int)key.getLOD())-1 > _firstLOD ) { // This most likely means that a parent tile expired while we were building the child. // No harm done in that case as this tile will soo be discarded as well. OE_DEBUG << "MP HFC: Unable to find tile " << key.str() << " in the live tile registry" << std::endl; return false; } } if ( !out_hf.valid() ) { //TODO. // This sets the elevation tile size; query size for all tiles. out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), _tileSize, _tileSize, true ); } bool populated = frame.populateHeightField( out_hf, key, true, // convertToHAE samplePolicy, progress ); // Treat Plate Carre specially by scaling the height values. (There is no need // to do this with an empty heightfield) const MapInfo& mapInfo = frame.getMapInfo(); if ( mapInfo.isPlateCarre() ) { HeightFieldUtils::scaleHeightFieldToDegrees( out_hf.get() ); } // cache it. HFValue cacheval; cacheval._hf = out_hf.get(); cacheval._isFallback = !populated; _cache.insert( cachekey, cacheval ); out_isFallback = !populated; return true; }
bool ElevationQuery::getElevationImpl(const GeoPoint& point, double& out_elevation, double desiredResolution, double* out_actualResolution) { osg::Timer_t start = osg::Timer::instance()->tick(); if ( _maxDataLevel == 0 || _tileSize == 0 ) { // this means there are no heightfields. out_elevation = 0.0; return true; } //This is the max resolution that we actually have data at this point unsigned int bestAvailLevel = getMaxLevel( point.x(), point.y(), point.getSRS(), _mapf.getProfile()); if (desiredResolution > 0.0) { unsigned int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, _tileSize ); if (desiredLevel < bestAvailLevel) bestAvailLevel = desiredLevel; } OE_DEBUG << "Best available data level " << point.x() << ", " << point.y() << " = " << bestAvailLevel << std::endl; // transform the input coords to map coords: GeoPoint mapPoint = point; if ( point.isValid() && !point.getSRS()->isEquivalentTo( _mapf.getProfile()->getSRS() ) ) { mapPoint = point.transform(_mapf.getProfile()->getSRS()); if ( !mapPoint.isValid() ) { OE_WARN << LC << "Fail: coord transform failed" << std::endl; return false; } } osg::ref_ptr<osg::HeightField> tile; // get the tilekey corresponding to the tile we need: TileKey key = _mapf.getProfile()->createTileKey( mapPoint.x(), mapPoint.y(), bestAvailLevel ); if ( !key.valid() ) { OE_WARN << LC << "Fail: coords fall outside map" << std::endl; return false; } // Check the tile cache. Note that the TileSource already likely has a MemCache // attached to it. We employ a secondary cache here for a couple reasons. One, this // cache will store not only the heightfield, but also the tesselated tile in the event // that we're using GEOMETRIC mode. Second, since the call the getHeightField can // fallback on a lower resolution, this cache will hold the final resolution heightfield // instead of trying to fetch the higher resolution one each item. TileCache::Record record; if ( _tileCache.get(key, record) ) { tile = record.value().get(); } // if we didn't find it, build it. if ( !tile.valid() ) { // generate the heightfield corresponding to the tile key, automatically falling back // on lower resolution if necessary: _mapf.getHeightField( key, true, tile, 0L ); // bail out if we could not make a heightfield a all. if ( !tile.valid() ) { OE_WARN << LC << "Unable to create heightfield for key " << key.str() << std::endl; return false; } _tileCache.insert(key, tile.get()); } OE_DEBUG << LC << "LRU Cache, hit ratio = " << _tileCache.getStats()._hitRatio << std::endl; // see what the actual resolution of the heightfield is. if ( out_actualResolution ) *out_actualResolution = (double)tile->getXInterval(); bool result = true; const GeoExtent& extent = key.getExtent(); double xInterval = extent.width() / (double)(tile->getNumColumns()-1); double yInterval = extent.height() / (double)(tile->getNumRows()-1); out_elevation = (double) HeightFieldUtils::getHeightAtLocation( tile.get(), mapPoint.x(), mapPoint.y(), extent.xMin(), extent.yMin(), xInterval, yInterval, _mapf.getMapInfo().getElevationInterpolation() ); osg::Timer_t end = osg::Timer::instance()->tick(); _queries++; _totalTime += osg::Timer::instance()->delta_s( start, end ); return result; }
void CacheSeed::processKey(const MapFrame& mapf, const TileKey& key ) const { unsigned int x, y, lod; key.getTileXY(x, y); lod = key.getLevelOfDetail(); bool gotData = true; if ( _minLevel <= lod && _maxLevel >= lod ) { gotData = cacheTile( mapf, key ); if (gotData) { incrementCompleted( 1 ); } if ( _progress.valid() && _progress->isCanceled() ) return; // Task has been cancelled by user if ( _progress.valid() && gotData && _progress->reportProgress(_completed, _total, std::string("Cached tile: ") + key.str()) ) return; // Canceled } if ( gotData && lod <= _maxLevel ) { TileKey k0 = key.createChildKey(0); TileKey k1 = key.createChildKey(1); TileKey k2 = key.createChildKey(2); TileKey k3 = key.createChildKey(3); bool intersectsKey = false; if (_extents.empty()) intersectsKey = true; else { for (unsigned int i = 0; i < _extents.size(); ++i) { if (_extents[i].intersects( k0.getExtent() ) || _extents[i].intersects( k1.getExtent() ) || _extents[i].intersects( k2.getExtent() ) || _extents[i].intersects( k3.getExtent() )) { intersectsKey = true; } } } //Check to see if the bounds intersects ANY of the tile's children. If it does, then process all of the children //for this level if (intersectsKey) { processKey(mapf, k0); processKey(mapf, k1); processKey(mapf, k2); processKey(mapf, k3); } } }
bool ElevationPool::tryTile(const TileKey& key, MapFrame& frame, osg::ref_ptr<Tile>& out) { // first see whether the tile is available _tilesMutex.lock(); // locate the tile in the local tile cache: osg::observer_ptr<Tile>& tile_obs = _tiles[key]; osg::ref_ptr<Tile> tile; // Get a safe pointer to it. If this is NULL, we need to create and // fetch a new tile from the Map. if (!tile_obs.lock(tile)) { // a new tile; status -> EMPTY tile = new Tile(); tile->_key = key; // update the LRU: _mru.push_front(tile.get()); // prune the MRU if necessary: if (++_entries > _maxEntries ) { popMRU(); --_entries; } // add to the main cache (after putting it on the LRU). tile_obs = tile; } // This means the tile object exists but has yet to be populated: if ( tile->_status == STATUS_EMPTY ) { OE_TEST << " getTile(" << key.str() << ") -> fetch from map\n"; tile->_status.exchange(STATUS_IN_PROGRESS); _tilesMutex.unlock(); bool ok = fetchTileFromMap(key, frame, tile.get()); tile->_status.exchange( ok ? STATUS_AVAILABLE : STATUS_FAIL ); out = ok ? tile.get() : 0L; return ok; } // This means the tile object is populated and available for use: else if ( tile->_status == STATUS_AVAILABLE ) { OE_TEST << " getTile(" << key.str() << ") -> available\n"; out = tile.get(); // Mark this tile as recently used: _mru.push_front(tile.get()); // prune the MRU if necessary if (++_entries > _maxEntries) { popMRU(); --_entries; } _tilesMutex.unlock(); return true; } // This means the attempt to populate the tile with data failed. else if ( tile->_status == STATUS_FAIL ) { OE_TEST << " getTile(" << key.str() << ") -> fail\n"; _tilesMutex.unlock(); out = 0L; return false; } // This means tile data fetch is still in progress (in another thread) // and the caller should check back later. else //if ( tile->_status == STATUS_IN_PROGRESS ) { OE_DEBUG << " getTile(" << key.str() << ") -> in progress...waiting\n"; _tilesMutex.unlock(); out = 0L; return true; // out:NULL => check back later please. } }