bool MapFrame::isCached( const TileKey& key ) const { //Check to see if the tile will load fast // Check the imagery layers for( ImageLayerVector::const_iterator i = imageLayers().begin(); i != imageLayers().end(); i++ ) { //If we're cache only we should be fast if (i->get()->isCacheOnly()) continue; osg::ref_ptr< TileSource > source = i->get()->getTileSource(); if (!source.valid()) continue; //If the tile is blacklisted, it should also be fast. if ( source->getBlacklist()->contains( key.getTileId() ) ) continue; //If no data is available on this tile, we'll be fast if ( !source->hasData( key ) ) continue; if ( !i->get()->isCached( key ) ) return false; } for( ElevationLayerVector::const_iterator i = elevationLayers().begin(); i != elevationLayers().end(); ++i ) { //If we're cache only we should be fast if (i->get()->isCacheOnly()) continue; osg::ref_ptr< TileSource > source = i->get()->getTileSource(); if (!source.valid()) continue; //If the tile is blacklisted, it should also be fast. if ( source->getBlacklist()->contains( key.getTileId() ) ) continue; if ( !source->hasData( key ) ) continue; if ( !i->get()->isCached( key ) ) { return false; } } return true; }
Tile::Tile( const TileKey& key, GeoLocator* keyLocator, bool quickReleaseGLObjects ) : _key( key ), _locator( keyLocator ), _quickReleaseGLObjects( quickReleaseGLObjects ), _hasBeenTraversed( false ), _verticalScale( 1.0f ), _parentTileSet( false ), _tileId( key.getTileId() ), _dirty( true ) { this->setThreadSafeRefUnref( true ); this->setName( key.str() ); // initially bump the update requirement so that this tile will receive an update // traversal the first time through. It is on the first update traversal that we // know the tile is in the scene graph and that it can be registered with the terrain. ADJUST_UPDATE_TRAV_COUNT( this, 1 ); }
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()); }
bool ElevationManager::getElevationImpl(double x, double y, double resolution, const SpatialReference* srs, double& out_elevation, double& out_resolution) { 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 = resolution > 0.0 ? _mapf.getProfile()->getLevelOfDetailForHorizResolution( resolution, _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: double map_x = x, map_y = y; if ( srs && !srs->isEquivalentTo( _mapf.getProfile()->getSRS() ) ) { if ( !srs->transform2D( x, y, _mapf.getProfile()->getSRS(), map_x, map_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( map_x, map_y, bestAvailLevel ); if ( !key.valid() ) { OE_WARN << LC << "Fail: coords fall outside map" << std::endl; return false; } // now, see if we already have this tile loaded somewhere: osgTerrain::TileID tileId = key.getTileId(); if ( !tile.valid() ) { // next check the local tile cache: TileTable::const_iterator i = _tileCache.find( tileId ); if ( i != _tileCache.end() ) tile = i->second.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 = NULL; } } // if we didn't find it (or it didn't have heightfield data), build it. if ( !tile.valid() ) { //OE_NOTICE << "ElevationManager: cache miss" << std::endl; // generate the heightfield corresponding to the tile key, automatically falling back // on lower resolution if necessary: _mapf.getHeightField( key, true, hf, 0L ); // bail out if we could not make a heightfield a all. if ( !hf.valid() ) { OE_WARN << "ElevationManager: unable to create heightfield" << std::endl; return false; } 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. // TODO: limit the size of the cache with a parallel FIFO list. _tileCache[tileId] = tile.get(); _tileCacheFIFO.push_back( tileId ); // prune the cache. this is a terrible pruning method. if ( _tileCache.size() > _maxCacheSize ) { osgTerrain::TileID id = _tileCacheFIFO.front(); _tileCacheFIFO.pop_front(); if ( tileId != id ) _tileCache.erase( id ); } } // see what the actual resolution of the heightfield is. out_resolution = (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(), map_x, map_y, extent.xMin(), extent.yMin(), xInterval, yInterval ); return true; } else // ( _technique == TECHNIQUE_GEOMETRIC ) { osg::Vec3d start, end, zero; if ( _mapf.getMapInfo().isGeocentric() ) { const osg::EllipsoidModel* ellip = _mapf.getProfile()->getSRS()->getEllipsoid(); ellip->convertLatLongHeightToXYZ( osg::DegreesToRadians( map_y ), osg::DegreesToRadians( map_x ), 50000, start.x(), start.y(), start.z() ); ellip->convertLatLongHeightToXYZ( osg::DegreesToRadians( map_y ), osg::DegreesToRadians( map_x ), -50000, end.x(), end.y(), end.z() ); ellip->convertLatLongHeightToXYZ( osg::DegreesToRadians( map_y ), osg::DegreesToRadians( map_x ), 0.0, zero.x(), zero.y(), zero.z() ); } else // PROJECTED { start.x() = map_x; start.y() = map_y; start.z() = 50000; end.x() = map_x; end.y() = map_y; end.z() = -50000; zero.x() = map_x; zero.y() = map_y; zero.z() = 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_WARN << "ElevationManager: no intersections" << std::endl; return false; } }
osg::Node* OSGTileFactory::createPlaceholderTile(const MapFrame& mapf, StreamingTerrain* terrain, const TileKey& key ) { // Start out by finding the nearest registered ancestor tile, since the placeholder is // going to be based on inherited data. Note- the ancestor may not be the immediate // parent, b/c the parent may or may not be in the scene graph. TileKey ancestorKey = key.createParentKey(); osg::ref_ptr<StreamingTile> ancestorTile; while( !ancestorTile.valid() && ancestorKey.valid() ) { terrain->getTile( ancestorKey.getTileId(), ancestorTile ); if ( !ancestorTile.valid() ) ancestorKey = ancestorKey.createParentKey(); } if ( !ancestorTile.valid() ) { OE_WARN << LC << "cannot find ancestor tile for (" << key.str() << ")" <<std::endl; return 0L; } OE_DEBUG << LC << "Creating placeholder for " << key.str() << std::endl; const MapInfo& mapInfo = mapf.getMapInfo(); bool hasElevation = mapf.elevationLayers().size() > 0; // Build a "placeholder" tile. double xmin, ymin, xmax, ymax; key.getExtent().getBounds( xmin, ymin, xmax, ymax ); // A locator will place the tile on the globe: osg::ref_ptr<GeoLocator> locator = GeoLocator::createForKey( key, mapInfo ); // The empty tile: StreamingTile* tile = new StreamingTile( key, locator.get(), terrain->getQuickReleaseGLObjects() ); tile->setTerrainTechnique( terrain->cloneTechnique() ); tile->setVerticalScale( _terrainOptions.verticalScale().value() ); tile->setDataVariance( osg::Object::DYNAMIC ); //tile->setLocator( locator.get() ); // Attach an updatecallback to normalize the edges of TerrainTiles. #if 0 if ( hasElevation && _terrainOptions.normalizeEdges().get() ) { tile->setUpdateCallback(new TerrainTileEdgeNormalizerUpdateCallback()); tile->setDataVariance(osg::Object::DYNAMIC); } #endif // Generate placeholder imagery and elevation layers. These "inherit" data from an // ancestor tile. { //Threading::ScopedReadLock parentLock( ancestorTile->getTileLayersMutex() ); addPlaceholderImageLayers ( tile, ancestorTile.get() ); addPlaceholderHeightfieldLayer( tile, ancestorTile.get(), locator.get(), key, ancestorKey ); } // calculate the switching distances: osg::BoundingSphere bs = tile->getBound(); double max_range = 1e10; double radius = bs.radius(); double min_range = radius * _terrainOptions.minTileRangeFactor().get(); // Set the skirt height of the heightfield osgTerrain::HeightFieldLayer* hfLayer = static_cast<osgTerrain::HeightFieldLayer*>(tile->getElevationLayer()); if (!hfLayer) { OE_WARN << LC << "Warning: Couldn't get hfLayer for " << key.str() << std::endl; } hfLayer->getHeightField()->setSkirtHeight(radius * _terrainOptions.heightFieldSkirtRatio().get() ); // In a Plate Carre tesselation, scale the heightfield elevations from meters to degrees if ( mapInfo.isPlateCarre() && hfLayer->getHeightField() ) HeightFieldUtils::scaleHeightFieldToDegrees( hfLayer->getHeightField() ); bool markTileLoaded = false; if ( _terrainOptions.loadingPolicy()->mode().get() != LoadingPolicy::MODE_STANDARD ) { markTileLoaded = true; tile->setHasElevationHint( hasElevation ); } // install a tile switcher: tile->attachToTerrain( terrain ); //tile->setTerrain( terrain ); //terrain->registerTile( tile ); osg::Node* result = 0L; // create a PLOD so we can keep subdividing: osg::PagedLOD* plod = new osg::PagedLOD(); plod->setCenter( bs.center() ); plod->addChild( tile, min_range, max_range ); if ( key.getLevelOfDetail() < (unsigned int)getTerrainOptions().maxLOD().get() ) { plod->setFileName( 1, createURI( _engineId, key ) ); //map->getId(), key ) ); plod->setRange( 1, 0.0, min_range ); } else { plod->setRange( 0, 0, FLT_MAX ); } #if 0 //USE_FILELOCATIONCALLBACK osgDB::Options* options = new osgDB::Options; options->setFileLocationCallback( new FileLocationCallback); plod->setDatabaseOptions( options ); #endif result = plod; // Install a callback that will load the actual tile data via the pager. result->addCullCallback( new PopulateStreamingTileDataCallback( _cull_thread_mapf ) ); // Install a cluster culler (FIXME for cube mode) //bool isCube = map->getMapOptions().coordSysType() == MapOptions::CSTYPE_GEOCENTRIC_CUBE; if ( mapInfo.isGeocentric() && !mapInfo.isCube() ) { osg::ClusterCullingCallback* ccc = createClusterCullingCallback( tile, locator->getEllipsoidModel() ); result->addCullCallback( ccc ); } return result; }
// This method is called by StreamingTerrainNode::traverse() in the UPDATE TRAVERSAL. void StreamingTerrainNode::refreshFamily(const MapInfo& mapInfo, const TileKey& key, StreamingTile::Relative* family, bool tileTableLocked ) { osgTerrain::TileID tileId = key.getTileId(); // geocentric maps wrap around in the X dimension. bool wrapX = mapInfo.isGeocentric(); unsigned int tileCountX, tileCountY; mapInfo.getProfile()->getNumTiles( tileId.level, tileCountX, tileCountY ); // Relative::PARENT { family[StreamingTile::Relative::PARENT].expected = true; // TODO: is this always correct? family[StreamingTile::Relative::PARENT].elevLOD = -1; family[StreamingTile::Relative::PARENT].imageLODs.clear(); family[StreamingTile::Relative::PARENT].tileID = osgTerrain::TileID( tileId.level-1, tileId.x/2, tileId.y/2 ); osg::ref_ptr<StreamingTile> parent; getTile( family[StreamingTile::Relative::PARENT].tileID, parent, !tileTableLocked ); if ( parent.valid() ) { family[StreamingTile::Relative::PARENT].elevLOD = parent->getElevationLOD(); ColorLayersByUID relLayers; parent->getCustomColorLayers( relLayers ); for( ColorLayersByUID::const_iterator i = relLayers.begin(); i != relLayers.end(); ++i ) { family[StreamingTile::Relative::PARENT].imageLODs[i->first] = i->second.getLevelOfDetail(); } } } // Relative::WEST { family[StreamingTile::Relative::WEST].expected = tileId.x > 0 || wrapX; family[StreamingTile::Relative::WEST].elevLOD = -1; family[StreamingTile::Relative::WEST].imageLODs.clear(); family[StreamingTile::Relative::WEST].tileID = osgTerrain::TileID( tileId.level, tileId.x > 0? tileId.x-1 : tileCountX-1, tileId.y ); osg::ref_ptr<StreamingTile> west; getTile( family[StreamingTile::Relative::WEST].tileID, west, !tileTableLocked ); if ( west.valid() ) { family[StreamingTile::Relative::WEST].elevLOD = west->getElevationLOD(); ColorLayersByUID relLayers; west->getCustomColorLayers( relLayers ); for( ColorLayersByUID::const_iterator i = relLayers.begin(); i != relLayers.end(); ++i ) { family[StreamingTile::Relative::WEST].imageLODs[i->first] = i->second.getLevelOfDetail(); } } } // Relative::NORTH { family[StreamingTile::Relative::NORTH].expected = tileId.y < (int)tileCountY-1; family[StreamingTile::Relative::NORTH].elevLOD = -1; family[StreamingTile::Relative::NORTH].imageLODs.clear(); family[StreamingTile::Relative::NORTH].tileID = osgTerrain::TileID( tileId.level, tileId.x, tileId.y < (int)tileCountY-1 ? tileId.y+1 : 0 ); osg::ref_ptr<StreamingTile> north; getTile( family[StreamingTile::Relative::NORTH].tileID, north, !tileTableLocked ); if ( north.valid() ) { family[StreamingTile::Relative::NORTH].elevLOD = north->getElevationLOD(); ColorLayersByUID relLayers; north->getCustomColorLayers( relLayers ); for( ColorLayersByUID::const_iterator i = relLayers.begin(); i != relLayers.end(); ++i ) { family[StreamingTile::Relative::NORTH].imageLODs[i->first] = i->second.getLevelOfDetail(); } } } // Relative::EAST { family[StreamingTile::Relative::EAST].expected = tileId.x < (int)tileCountX-1 || wrapX; family[StreamingTile::Relative::EAST].elevLOD = -1; family[StreamingTile::Relative::EAST].imageLODs.clear(); family[StreamingTile::Relative::EAST].tileID = osgTerrain::TileID( tileId.level, tileId.x < (int)tileCountX-1 ? tileId.x+1 : 0, tileId.y ); osg::ref_ptr<StreamingTile> east; getTile( family[StreamingTile::Relative::EAST].tileID, east, !tileTableLocked ); if ( east.valid() ) { family[StreamingTile::Relative::EAST].elevLOD = east->getElevationLOD(); ColorLayersByUID relLayers; east->getCustomColorLayers( relLayers ); for( ColorLayersByUID::const_iterator i = relLayers.begin(); i != relLayers.end(); ++i ) { family[StreamingTile::Relative::EAST].imageLODs[i->first] = i->second.getLevelOfDetail(); } } } // Relative::SOUTH { family[StreamingTile::Relative::SOUTH].expected = tileId.y > 0; family[StreamingTile::Relative::SOUTH].elevLOD = -1; family[StreamingTile::Relative::SOUTH].imageLODs.clear(); family[StreamingTile::Relative::SOUTH].tileID = osgTerrain::TileID( tileId.level, tileId.x, tileId.y > 0 ? tileId.y-1 : tileCountY-1 ); osg::ref_ptr<StreamingTile> south; getTile( family[StreamingTile::Relative::SOUTH].tileID, south, !tileTableLocked ); if ( south.valid() ) { family[StreamingTile::Relative::SOUTH].elevLOD = south->getElevationLOD(); ColorLayersByUID relLayers; south->getCustomColorLayers( relLayers ); for( ColorLayersByUID::const_iterator i = relLayers.begin(); i != relLayers.end(); ++i ) { family[StreamingTile::Relative::SOUTH].imageLODs[i->first] = i->second.getLevelOfDetail(); } } } }
osg::HeightField* ElevationLayer::assembleHeightFieldFromTileSource(const TileKey& key, ProgressCallback* progress) { osg::HeightField* result = 0L; // Collect the heightfields for each of the intersecting tiles. GeoHeightFieldVector heightFields; //Determine the intersecting keys std::vector< TileKey > intersectingTiles; getProfile()->getIntersectingTiles( key, intersectingTiles ); //Maintain a list of heightfield tiles that have been added to the list already. std::set< osgTerrain::TileID > existingTiles; // collect heightfield for each intersecting key. Note, we're hitting the // underlying tile source here, so there's no vetical datum shifts happening yet. // we will do that later. if ( intersectingTiles.size() > 0 ) { for (unsigned int i = 0; i < intersectingTiles.size(); ++i) { const TileKey& layerKey = intersectingTiles[i]; if ( isKeyValid(layerKey) ) { osg::HeightField* hf = createHeightFieldFromTileSource( layerKey, progress ); if ( hf ) { heightFields.push_back( GeoHeightField(hf, layerKey.getExtent()) ); } else { // We couldn't get a heightfield at the given key so fall back on parent tiles TileKey parentKey = layerKey.createParentKey(); while (!hf && parentKey.valid()) { // Make sure we haven't already added this heightfield to the list. // This could happen if you have multiple high resolution tiles that dont' have data. // So if you have four level 5 tiles with no data, they will fall back on the same level 4 tile. // This existingTiles check makes sure we don't process and add the same tile multiple times if (existingTiles.find(parentKey.getTileId()) == existingTiles.end()) { hf = createHeightFieldFromTileSource( parentKey, progress ); if (hf) { heightFields.push_back( GeoHeightField(hf, parentKey.getExtent()) ); existingTiles.insert(parentKey.getTileId()); break; } parentKey = parentKey.createParentKey(); } else { break; } } } } } } // If we actually got a HeightField, resample/reproject it to match the incoming TileKey's extents. if (heightFields.size() > 0) { unsigned int width = 0; unsigned int height = 0; for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { if (itr->getHeightField()->getNumColumns() > width) width = itr->getHeightField()->getNumColumns(); if (itr->getHeightField()->getNumRows() > height) height = itr->getHeightField()->getNumRows(); } //Now sort the heightfields by resolution to make sure we're sampling the highest resolution one first. std::sort( heightFields.begin(), heightFields.end(), GeoHeightField::SortByResolutionFunctor()); result = new osg::HeightField(); result->allocate(width, height); //Go ahead and 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); double dx = (maxx - minx)/(double)(width-1); double dy = (maxy - miny)/(double)(height-1); //Create the new heightfield by sampling all of them. for (unsigned int c = 0; c < width; ++c) { double x = minx + (dx * (double)c); for (unsigned r = 0; r < height; ++r) { double y = miny + (dy * (double)r); //For each sample point, try each heightfield. The first one with a valid elevation wins. float elevation = NO_DATA_VALUE; for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { // get the elevation value, at the same time transforming it vertically into the // requesting key's vertical datum. float e = 0.0; if (itr->getElevation(key.getExtent().getSRS(), x, y, INTERP_BILINEAR, key.getExtent().getSRS(), e)) { elevation = e; break; } } result->setHeight( c, r, elevation ); } } } return result; }
bool MapFrame::isCached( const TileKey& key ) const { // is there a map cache at all? if ( _map->getCache() == 0L ) return false; //Check to see if the tile will load fast // Check the imagery layers for( ImageLayerVector::const_iterator i = imageLayers().begin(); i != imageLayers().end(); i++ ) { const ImageLayer* layer = i->get(); if (!layer->getEnabled()) continue; // If we're cache only we should be fast if (layer->isCacheOnly()) continue; // no-cache mode? always slow if (layer->isNoCache()) return false; // No tile source? skip it osg::ref_ptr< TileSource > source = layer->getTileSource(); if (!source.valid()) continue; //If the tile is blacklisted, it should also be fast. if ( source->getBlacklist()->contains( key.getTileId() ) ) continue; //If no data is available on this tile, we'll be fast if ( !source->hasData( key ) ) continue; if ( !layer->isCached(key) ) return false; } for( ElevationLayerVector::const_iterator i = elevationLayers().begin(); i != elevationLayers().end(); ++i ) { const ElevationLayer* layer = i->get(); if (!layer->getEnabled()) continue; //If we're cache only we should be fast if (layer->isCacheOnly()) continue; // no-cache mode? always high-latency. if (layer->isNoCache()) return false; osg::ref_ptr< TileSource > source = layer->getTileSource(); if (!source.valid()) continue; //If the tile is blacklisted, it should also be fast. if ( source->getBlacklist()->contains( key.getTileId() ) ) continue; if ( !source->hasData( key ) ) continue; if ( !i->get()->isCached( key ) ) return false; } return true; }