bool TileSource::hasData(const osgEarth::TileKey& key) const { //sematics: might have data. //If no data extents are provided, just return true if (_dataExtents.size() == 0) return true; const osgEarth::GeoExtent& keyExtent = key.getExtent(); bool intersectsData = false; for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr) { if ((keyExtent.intersects( *itr )) && (!itr->minLevel().isSet() || itr->minLevel() <= key.getLOD()) && (!itr->maxLevel().isSet() || itr->maxLevel() >= key.getLOD())) { intersectsData = true; break; } } return intersectsData; }
bool TileSource::hasData(const osgEarth::TileKey& key) const { //sematics: "might have data" if ( !key.valid() ) return false; // If no data extents are provided, and there's no data level override, // return true because there might be data but there's no way to tell. if (_dataExtents.size() == 0 && !_options.maxDataLevel().isSet()) { return true; } unsigned int lod = key.getLevelOfDetail(); // Remap the lod to an appropriate lod if it's not in the same SRS if (!key.getProfile()->isHorizEquivalentTo( getProfile() ) ) { lod = getProfile()->getEquivalentLOD( key.getProfile(), key.getLevelOfDetail() ); } // If there's an explicit LOD override and we've exceeded it, no data. if (_options.maxDataLevel().isSet() && lod > _options.maxDataLevel().value()) { return false; } // If there are no extents to check, there might be data. if (_dataExtents.size() == 0) { return true; } bool intersectsData = false; const osgEarth::GeoExtent& keyExtent = key.getExtent(); for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr) { if ((keyExtent.intersects( *itr )) && (!itr->minLevel().isSet() || itr->minLevel() <= lod ) && (!itr->maxLevel().isSet() || itr->maxLevel() >= lod )) { intersectsData = true; break; } } return intersectsData; }
Config TerrainLayer::CacheBinMetadata::getConfig() const { Config conf("osgearth_terrainlayer_cachebin"); conf.addIfSet("cachebin_id", _cacheBinId); conf.addIfSet("source_name", _sourceName); conf.addIfSet("source_driver", _sourceDriver); conf.addIfSet("source_tile_size", _sourceTileSize); conf.addObjIfSet("source_profile", _sourceProfile); conf.addObjIfSet("cache_profile", _cacheProfile); conf.addIfSet("cache_create_time", _cacheCreateTime); if (!_dataExtents.empty()) { Config extents; for (DataExtentList::const_iterator i = _dataExtents.begin(); i != _dataExtents.end(); ++i) { Config extent; extent.set("srs", i->getSRS()->getHorizInitString()); extent.set("xmin", i->xMin()); extent.set("ymin", i->yMin()); extent.set("xmax", i->xMax()); extent.set("ymax", i->yMax()); extent.addIfSet("minlevel", i->minLevel()); extent.addIfSet("maxlevel", i->maxLevel()); extents.add("extent", extent); } conf.add("extents", extents); } return conf; }
bool TileSource::hasDataAtLOD( unsigned lod ) const { // the sematics here are really "MIGHT have data at LOD". // If no data extents are provided, just return true if ( _dataExtents.size() == 0 ) return true; for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr) { if ((!itr->minLevel().isSet() || itr->minLevel() <= lod) && (!itr->maxLevel().isSet() || itr->maxLevel() >= lod)) { return true; } } return false; }
void TerrainLayer::initTileSource() { _tileSourceInitAttempted = true; OE_DEBUG << LC << "Initializing tile source ..." << std::endl; // Instantiate it from driver options if it has not already been created. // This will also set a manual "override" profile if the user provided one. if ( !_tileSource.valid() ) { if ( _runtimeOptions->driver().isSet() ) { _tileSource = TileSourceFactory::create(*_runtimeOptions->driver()); } } // Initialize the profile with the context information: if ( _tileSource.valid() ) { // set up the URI options. if ( !_dbOptions.valid() ) { _dbOptions = Registry::instance()->cloneOrCreateOptions(); if ( _cache.valid() ) _cache->apply( _dbOptions.get() ); _initOptions.cachePolicy()->apply( _dbOptions.get() ); URIContext( _runtimeOptions->referrer() ).apply( _dbOptions.get() ); } // report on a manual override profile: if ( _tileSource->getProfile() ) { OE_INFO << LC << "set profile to: " << _tileSource->getProfile()->toString() << std::endl; } // Open the tile source (if it hasn't already been started) TileSource::Status status = _tileSource->getStatus(); if ( status != TileSource::STATUS_OK ) { status = _tileSource->open(TileSource::MODE_READ, _dbOptions.get()); } if ( status == TileSource::STATUS_OK ) { _tileSize = _tileSource->getPixelsPerTile(); #if 0 //debugging // dump out data extents: if ( _tileSource->getDataExtents().size() > 0 ) { OE_INFO << LC << "Data extents reported:" << std::endl; for(DataExtentList::const_iterator i = _tileSource->getDataExtents().begin(); i != _tileSource->getDataExtents().end(); ++i) { const DataExtent& de = *i; OE_INFO << " " << "X(" << i->xMin() << ", " << i->xMax() << ") " << "Y(" << i->yMin() << ", " << i->yMax() << ") " << "Z(" << i->minLevel().get() << ", " << i->maxLevel().get() << ")" << std::endl; } } #endif } else { OE_WARN << LC << "Could not initialize driver" << std::endl; _tileSource = NULL; _tileSourceInitFailed = true; _runtimeOptions->enabled() = true; } } // Set the profile from the TileSource if possible: if ( _tileSource.valid() ) { if ( !_profile.valid() && !_tileSourceInitFailed ) { _profile = _tileSource->getProfile(); } // check for a vertical datum override: if ( _profile.valid() && _runtimeOptions->verticalDatum().isSet() ) { std::string vdatum = *_runtimeOptions->verticalDatum(); if ( !ciEquals(_profile->getSRS()->getVertInitString(), vdatum) ) { OE_INFO << LC << "Overriding vdatum with: " << vdatum << std::endl; ProfileOptions po = _profile->toProfileOptions(); po.vsrsString() = vdatum; _profile = Profile::create(po); } } if ( _profile.valid() ) { OE_INFO << LC << "Profile=" << _profile->toString() << std::endl; } } // Otherwise, force cache-only mode (since there is no tilesource). The layer will try to // establish a profile from the metadata in the cache instead. else if (_cache.valid()) { OE_NOTICE << LC << "Could not initialize TileSource " << _name << ", but a cache exists. Setting layer to cache-only mode." << std::endl; setCachePolicy( CachePolicy::CACHE_ONLY ); } }
/** * Command-line tool that copies the contents of one TileSource * to another. All arguments are Config name/value pairs, so you need * to look in the header file for each driver's Options structure for * options :) * * Example: copy a GDAL file to an MBTiles repo: * * osgearth_conv * --in driver gdal * --in url world.tif * --out driver mbtiles * --out filename world.db * * The "in" properties come from the GDALOptions getConfig method. The * "out" properties come from the MBTilesOptions getConfig method. * * Other arguments: * * --elevation : convert as elevation data (instead of image data) * --profile [profile] : reproject to the target profile, e.g. "wgs84" * --min-level [int] : min level of detail to copy * --max-level [int] : max level of detail to copy * --threads [n] : threads to use (may crash. Careful.) * * --extents [minLat] [minLong] [maxLat] [maxLong] : Lat/Long extends to copy (*) * * Of course, the output driver must support writing (by implementing * the ReadWriteTileSource interface). */ int main(int argc, char** argv) { osg::ArgumentParser args(&argc,argv); if ( argc == 1 ) return usage(argv); typedef std::map<std::string,std::string> KeyValue; std::string key, value; // collect input configuration: Config inConf; while( args.read("--in", key, value) ) inConf.set(key, value); TileSourceOptions inOptions(inConf); osg::ref_ptr<TileSource> input = TileSourceFactory::create(inOptions); if ( !input.valid() ) { OE_WARN << LC << "Failed to open input" << std::endl; return -1; } TileSource::Status inputStatus = input->open(); if ( inputStatus.isError() ) { OE_WARN << LC << "Error initializing input" << std::endl; return -1; } // collect output configuration: Config outConf; while( args.read("--out", key, value) ) outConf.set(key, value); // heightfields? bool heightFields = args.read("--heightfield") || args.read("--hf") || args.read("--elevation"); if ( heightFields ) OE_INFO << LC << "Converting heightfield tiles" << std::endl; else OE_INFO << LC << "Converting image tiles" << std::endl; // are we changing profiles? osg::ref_ptr<const Profile> outputProfile = input->getProfile(); std::string profileString; bool isSameProfile = true; if ( args.read("--profile", profileString) ) { outputProfile = Profile::create(profileString); if ( !outputProfile.valid() || !outputProfile->isOK() ) { OE_WARN << LC << "Output profile is not recognized" << std::endl; return -1; } isSameProfile = outputProfile->isHorizEquivalentTo(input->getProfile()); } // set the output profile. ProfileOptions profileOptions = outputProfile->toProfileOptions(); outConf.add("profile", profileOptions.getConfig()); // open the output tile source: TileSourceOptions outOptions(outConf); osg::ref_ptr<TileSource> output = TileSourceFactory::create(outOptions); if ( !output.valid() ) { OE_WARN << LC << "Failed to open output" << std::endl; return -1; } TileSource::Status outputStatus = output->open(TileSource::MODE_WRITE | TileSource::MODE_CREATE); if ( outputStatus.isError() ) { OE_WARN << LC << "Error initializing output" << std::endl; return -1; } // Dump out some stuff... OE_NOTICE << LC << "FROM:\n" << inConf.toJSON(true) << std::endl; OE_NOTICE << LC << "TO:\n" << outConf.toJSON(true) << std::endl; // create the visitor. osg::ref_ptr<TileVisitor> visitor; unsigned numThreads = 1; if (args.read("--threads", numThreads)) { MultithreadedTileVisitor* mtv = new MultithreadedTileVisitor(); mtv->setNumThreads( numThreads < 1 ? 1 : numThreads ); visitor = mtv; } else { visitor = new TileVisitor(); } // If the profiles are identical, just use a tile copier. if ( isSameProfile ) { OE_NOTICE << LC << "Profiles match - initiating simple tile copy" << std::endl; visitor->setTileHandler( new TileSourceToTileSource(input.get(), output.get(), heightFields) ); } else { OE_NOTICE << LC << "Profiles differ - initiating tile transformation" << std::endl; if (heightFields) { ElevationLayer* layer = new ElevationLayer(ElevationLayerOptions(), input.get()); if ( !layer->getProfile() || !layer->getProfile()->isOK() ) { OE_WARN << LC << "Input profile is not valid" << std::endl; return -1; } visitor->setTileHandler( new ElevationLayerToTileSource(layer, output.get()) ); } else { ImageLayer* layer = new ImageLayer(ImageLayerOptions(), input.get()); if ( !layer->getProfile() || !layer->getProfile()->isOK() ) { OE_WARN << LC << "Input profile is not valid" << std::endl; return -1; } visitor->setTileHandler( new ImageLayerToTileSource(layer, output.get()) ); } } // Set the level limits: unsigned minLevel = ~0; bool minLevelSet = args.read("--min-level", minLevel); unsigned maxLevel = 0; bool maxLevelSet = args.read("--max-level", maxLevel); // figure out the max source level: if ( !minLevelSet || !maxLevelSet ) { for(DataExtentList::const_iterator i = input->getDataExtents().begin(); i != input->getDataExtents().end(); ++i) { if ( !maxLevelSet && i->maxLevel().isSet() && i->maxLevel().value() > maxLevel ) maxLevel = i->maxLevel().value(); if ( !minLevelSet && i->minLevel().isSet() && i->minLevel().value() < minLevel ) minLevel = i->minLevel().value(); } } if ( minLevel < ~0 ) { visitor->setMinLevel( minLevel ); } if ( maxLevel > 0 ) { maxLevel = outputProfile->getEquivalentLOD( input->getProfile(), maxLevel ); visitor->setMaxLevel( maxLevel ); OE_NOTICE << LC << "Calculated max level = " << maxLevel << std::endl; } // set the extents: double minlat, minlon, maxlat, maxlon; while( args.read("--extents", minlat, minlon, maxlat, maxlon) ) { GeoExtent extent(SpatialReference::get("wgs84"), minlon, minlat, maxlon, maxlat); visitor->addExtent( extent ); } // Ready!!! std::cout << "Working..." << std::endl; visitor->setProgressCallback( new ProgressReporter() ); osg::Timer_t t0 = osg::Timer::instance()->tick(); visitor->run( outputProfile.get() ); osg::Timer_t t1 = osg::Timer::instance()->tick(); std::cout << "Time = " << std::fixed << std::setprecision(1) << osg::Timer::instance()->delta_s(t0, t1) << " seconds." << std::endl; return 0; }
static XmlDocument* tileMapToXmlDocument(const TileMap* tileMap) { //Create the root XML document osg::ref_ptr<XmlDocument> doc = new XmlDocument(); doc->setName( ELEM_TILEMAP ); doc->getAttrs()[ ATTR_VERSION ] = tileMap->getVersion(); doc->getAttrs()[ ATTR_TILEMAPSERVICE ] = tileMap->getTileMapService(); doc->addSubElement( ELEM_TITLE, tileMap->getTitle() ); doc->addSubElement( ELEM_ABSTRACT, tileMap->getAbstract() ); doc->addSubElement( ELEM_SRS, tileMap->getSRS() ); doc->addSubElement( ELEM_VERTICAL_SRS, tileMap->getVerticalSRS() ); osg::ref_ptr<XmlElement> e_bounding_box = new XmlElement( ELEM_BOUNDINGBOX ); double minX, minY, maxX, maxY; tileMap->getExtents( minX, minY, maxX, maxY ); e_bounding_box->getAttrs()[ATTR_MINX] = toString(minX); e_bounding_box->getAttrs()[ATTR_MINY] = toString(minY); e_bounding_box->getAttrs()[ATTR_MAXX] = toString(maxX); e_bounding_box->getAttrs()[ATTR_MAXY] = toString(maxY); doc->getChildren().push_back(e_bounding_box.get() ); osg::ref_ptr<XmlElement> e_origin = new XmlElement( ELEM_ORIGIN ); e_origin->getAttrs()[ATTR_X] = toString(tileMap->getOriginX()); e_origin->getAttrs()[ATTR_Y] = toString(tileMap->getOriginY()); doc->getChildren().push_back(e_origin.get()); osg::ref_ptr<XmlElement> e_tile_format = new XmlElement( ELEM_TILE_FORMAT ); e_tile_format->getAttrs()[ ATTR_EXTENSION ] = tileMap->getFormat().getExtension(); e_tile_format->getAttrs()[ ATTR_MIME_TYPE ] = tileMap->getFormat().getMimeType(); e_tile_format->getAttrs()[ ATTR_WIDTH ] = toString<unsigned int>(tileMap->getFormat().getWidth()); e_tile_format->getAttrs()[ ATTR_HEIGHT ] = toString<unsigned int>(tileMap->getFormat().getHeight()); doc->getChildren().push_back(e_tile_format.get()); osg::ref_ptr< const osgEarth::Profile > profile = tileMap->createProfile(); osg::ref_ptr<XmlElement> e_tile_sets = new XmlElement ( ELEM_TILESETS ); std::string profileString = "none"; if (profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalGeodeticProfile())) { profileString = "global-geodetic"; } else if (profile->isEquivalentTo(osgEarth::Registry::instance()->getGlobalMercatorProfile())) { profileString = "global-mercator"; } else { profileString = "local"; } e_tile_sets->getAttrs()[ ATTR_PROFILE ] = profileString; for (TileMap::TileSetList::const_iterator itr = tileMap->getTileSets().begin(); itr != tileMap->getTileSets().end(); ++itr) { osg::ref_ptr<XmlElement> e_tile_set = new XmlElement( ELEM_TILESET ); e_tile_set->getAttrs()[ATTR_HREF] = itr->getHref(); e_tile_set->getAttrs()[ATTR_ORDER] = toString<unsigned int>(itr->getOrder()); e_tile_set->getAttrs()[ATTR_UNITSPERPIXEL] = toString(itr->getUnitsPerPixel()); e_tile_sets->getChildren().push_back( e_tile_set.get() ); } doc->getChildren().push_back(e_tile_sets.get()); //Write out the data areas if (tileMap->getDataExtents().size() > 0) { osg::ref_ptr<XmlElement> e_data_extents = new XmlElement( ELEM_DATA_EXTENTS ); for (DataExtentList::const_iterator itr = tileMap->getDataExtents().begin(); itr != tileMap->getDataExtents().end(); ++itr) { osg::ref_ptr<XmlElement> e_data_extent = new XmlElement( ELEM_DATA_EXTENT ); e_data_extent->getAttrs()[ATTR_MINX] = toString(itr->xMin()); e_data_extent->getAttrs()[ATTR_MINY] = toString(itr->yMin()); e_data_extent->getAttrs()[ATTR_MAXX] = toString(itr->xMax()); e_data_extent->getAttrs()[ATTR_MAXY] = toString(itr->yMax()); if ( itr->minLevel().isSet() ) e_data_extent->getAttrs()[ATTR_MIN_LEVEL] = toString<unsigned int>(*itr->minLevel()); if ( itr->maxLevel().isSet() ) e_data_extent->getAttrs()[ATTR_MAX_LEVEL] = toString<unsigned int>(*itr->maxLevel()); e_data_extents->getChildren().push_back( e_data_extent ); } doc->getChildren().push_back( e_data_extents.get() ); } return doc.release(); }
TileSource* TerrainLayer::createTileSource() { osg::ref_ptr<TileSource> ts; if ( _tileSource.valid() ) { // this will happen if the layer was created with an explicit TileSource instance. ts = _tileSource.get(); } else { // Instantiate it from driver options if it has not already been created. // This will also set a manual "override" profile if the user provided one. if ( _runtimeOptions->driver().isSet() ) { OE_INFO << LC << "Creating TileSource, driver = \"" << _runtimeOptions->driver()->getDriver() << "\"\n"; ts = TileSourceFactory::create( *_runtimeOptions->driver() ); if ( !ts.valid() ) { OE_WARN << LC << "Failed to create TileSource for driver \"" << _runtimeOptions->driver()->getDriver() << "\"\n"; } } } // Initialize the profile with the context information: if ( ts.valid() ) { // set up the URI options. if ( !_dbOptions.valid() ) { _dbOptions = Registry::instance()->cloneOrCreateOptions(); if ( _cache.valid() ) _cache->apply( _dbOptions.get() ); _initOptions.cachePolicy()->apply( _dbOptions.get() ); URIContext( _runtimeOptions->referrer() ).apply( _dbOptions.get() ); } // report on a manual override profile: if ( ts->getProfile() ) { OE_INFO << LC << "Override profile: " << ts->getProfile()->toString() << std::endl; } // Open the tile source (if it hasn't already been started) TileSource::Status status = ts->getStatus(); if ( status != TileSource::STATUS_OK ) { status = ts->open(TileSource::MODE_READ, _dbOptions.get()); } if ( status == TileSource::STATUS_OK ) { _tileSize = ts->getPixelsPerTile(); #if 0 //debugging // dump out data extents: if ( ts->getDataExtents().size() > 0 ) { OE_INFO << LC << "Data extents reported:" << std::endl; for(DataExtentList::const_iterator i = ts->getDataExtents().begin(); i != ts->getDataExtents().end(); ++i) { const DataExtent& de = *i; OE_INFO << " " << "X(" << i->xMin() << ", " << i->xMax() << ") " << "Y(" << i->yMin() << ", " << i->yMax() << ") " << "Z(" << i->minLevel().get() << ", " << i->maxLevel().get() << ")" << std::endl; } } #endif } else { OE_WARN << LC << "Could not initialize driver" << std::endl; ts = NULL; //_tileSourceInitFailed = true; _runtimeOptions->enabled() = true; } } // Set the profile from the TileSource if possible: if ( ts.valid() ) { if ( !_profile.valid() ) { OE_DEBUG << LC << "Get Profile from tile source" << std::endl; _profile = ts->getProfile(); } if ( _profile.valid() ) { // create the final profile from any overrides: applyProfileOverrides(); OE_INFO << LC << "Profile=" << _profile->toString() << std::endl; } } // Otherwise, force cache-only mode (since there is no tilesource). The layer will try to // establish a profile from the metadata in the cache instead. else if (_cache.valid()) { OE_NOTICE << LC << "Could not initialize TileSource " << _name << ", but a cache exists. Setting layer to cache-only mode." << std::endl; setCachePolicy( CachePolicy::CACHE_ONLY ); } return ts.release(); }
TileKey TerrainLayer::getBestAvailableTileKey(const TileKey& key) const { // trivial reject if ( !key.valid() ) return TileKey::INVALID; unsigned MDL = options().maxDataLevel().get(); // We must use the equivalent lod b/c the input key can be in any profile. unsigned localLOD = getProfile() ? getProfile()->getEquivalentLOD(key.getProfile(), key.getLOD()) : key.getLOD(); // Check against level extrema: if (localLOD < options().minLevel().get() || localLOD > options().maxLevel().get()) { return TileKey::INVALID; } // Next, check against resolution limits (based on the source tile size). if (options().minResolution().isSet() || options().maxResolution().isSet()) { const Profile* profile = getProfile(); if ( profile ) { // calculate the resolution in the layer's profile, which can // be different that the key's profile. double resKey = key.getExtent().width() / (double)getTileSize(); double resLayer = key.getProfile()->getSRS()->transformUnits(resKey, profile->getSRS()); if (options().maxResolution().isSet() && options().maxResolution().value() > resLayer) { return TileKey::INVALID; } if (options().minResolution().isSet() && options().minResolution().value() < resLayer) { return TileKey::INVALID; } } } // Next check against the data extents. const DataExtentList& de = getDataExtents(); // If we have mo data extents available, just return the MDL-limited input key. if (de.empty()) { return localLOD > MDL ? key.createAncestorKey(MDL) : key; } // Reject if the extents don't overlap at all. if (!getDataExtentsUnion().intersects(key.getExtent())) { return TileKey::INVALID; } bool intersects = false; unsigned highestLOD = 0; // Check each data extent in turn: for (DataExtentList::const_iterator itr = de.begin(); itr != de.end(); ++itr) { // check for 2D intersection: if (key.getExtent().intersects(*itr)) { // check that the extent isn't higher-resolution than our key: if ( !itr->minLevel().isSet() || localLOD >= (int)itr->minLevel().get() ) { // Got an intersetion; now test the LODs: intersects = true; // Is the high-LOD set? If not, there's not enough information // so just assume our key might be good. if ( itr->maxLevel().isSet() == false ) { return localLOD > MDL ? key.createAncestorKey(MDL) : key; } // Is our key at a lower or equal LOD than the max key in this extent? // If so, our key is good. else if ( localLOD <= (int)itr->maxLevel().get() ) { return localLOD > MDL ? key.createAncestorKey(MDL) : key; } // otherwise, record the highest encountered LOD that // intersects our key. else if ( itr->maxLevel().get() > highestLOD ) { highestLOD = itr->maxLevel().get(); } } } } if ( intersects ) { return key.createAncestorKey(std::min(highestLOD, MDL)); } return TileKey::INVALID; }
bool TileSource::getBestAvailableTileKey(const osgEarth::TileKey& key, osgEarth::TileKey& output) const { // trivial reject if ( !key.valid() ) return false; // trivial accept: no data extents = not enough info. if (_dataExtents.size() == 0) { output = key; return true; } // trivial reject: key doesn't intersect the union of data extents at all. if ( !getDataExtentsUnion().intersects(key.getExtent()) ) { return false; } bool intersects = false; unsigned highestLOD = 0; // We must use the equivalent lod b/c the key can be in any profile. int layerLOD = getProfile()->getEquivalentLOD( key.getProfile(), key.getLOD() ); for (DataExtentList::const_iterator itr = _dataExtents.begin(); itr != _dataExtents.end(); ++itr) { // check for 2D intersection: if (key.getExtent().intersects( *itr )) { // check that the extent isn't higher-resolution than our key: if ( !itr->minLevel().isSet() || layerLOD >= itr->minLevel().get() ) { // Got an intersetion; now test the LODs: intersects = true; // Is the high-LOD set? If not, there's not enough information // so just assume our key might be good. if ( itr->maxLevel().isSet() == false ) { output = key; return true; } // Is our key at a lower or equal LOD than the max key in this extent? // If so, our key is good. else if ( layerLOD <= itr->maxLevel().get() ) { output = key; return true; } // otherwise, record the highest encountered LOD that // intersects our key. else if ( itr->maxLevel().get() > highestLOD ) { highestLOD = itr->maxLevel().get(); } } } } if ( intersects ) { output = key.createAncestorKey( highestLOD ); return true; } else { return false; } }