Exemple #1
0
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;
}
Exemple #3
0
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;
}
Exemple #4
0
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;
}
Exemple #7
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();
}
Exemple #8
0
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();
}
Exemple #9
0
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;
    }
}