Example #1
0
GeoLocator::GeoLocator( const osgTerrain::Locator& prototype, const GeoExtent& dataExtent, const GeoExtent& displayExtent ) :
osgTerrain::Locator( prototype ),
_dataExtent( dataExtent ),
_inverseCalculated(false)
{
    // assume they are the same SRS
    _x0 = osg::clampBetween( (displayExtent.xMin()-dataExtent.xMin())/dataExtent.width(), 0.0, 1.0 );
    _x1 = osg::clampBetween( (displayExtent.xMax()-dataExtent.xMin())/dataExtent.width(), 0.0, 1.0 );
    _y0 = osg::clampBetween( (displayExtent.yMin()-dataExtent.yMin())/dataExtent.height(), 0.0, 1.0 );
    _y1 = osg::clampBetween( (displayExtent.yMax()-dataExtent.yMin())/dataExtent.height(), 0.0, 1.0 );
}
Example #2
0
GeoImage
GeoImage::crop( const GeoExtent& extent, bool exact, unsigned int width, unsigned int height  ) const
{
    //Check for equivalence
    if ( extent.getSRS()->isEquivalentTo( getSRS() ) )
    {
        //If we want an exact crop or they want to specify the output size of the image, use GDAL
        if (exact || width != 0 || height != 0 )
        {
            OE_DEBUG << "[osgEarth::GeoImage::crop] Performing exact crop" << std::endl;

            //Suggest an output image size
            if (width == 0 || height == 0)
            {
                double xRes = (getExtent().xMax() - getExtent().xMin()) / (double)_image->s();
                double yRes = (getExtent().yMax() - getExtent().yMin()) / (double)_image->t();

                width =  osg::maximum(1u, (unsigned int)((extent.xMax() - extent.xMin()) / xRes));
                height = osg::maximum(1u, (unsigned int)((extent.yMax() - extent.yMin()) / yRes));

                OE_DEBUG << "[osgEarth::GeoImage::crop] Computed output image size " << width << "x" << height << std::endl;
            }

            //Note:  Passing in the current SRS simply forces GDAL to not do any warping
            return reproject( getSRS(), &extent, width, height);
        }
        else
        {
            OE_DEBUG << "[osgEarth::GeoImage::crop] Performing non-exact crop " << std::endl;
            //If an exact crop is not desired, we can use the faster image cropping code that does no resampling.
            double destXMin = extent.xMin();
            double destYMin = extent.yMin();
            double destXMax = extent.xMax();
            double destYMax = extent.yMax();

            osg::Image* new_image = ImageUtils::cropImage(
                _image.get(),
                _extent.xMin(), _extent.yMin(), _extent.xMax(), _extent.yMax(),
                destXMin, destYMin, destXMax, destYMax );

            //The destination extents may be different than the input extents due to not being able to crop along pixel boundaries.
            return new_image?
                GeoImage( new_image, GeoExtent( getSRS(), destXMin, destYMin, destXMax, destYMax ) ) :
                GeoImage::INVALID;
        }
    }
    else
    {
        //TODO: just reproject the image before cropping
        OE_NOTICE << "[osgEarth::GeoImage::crop] Cropping extent does not have equivalent SpatialReference" << std::endl;
        return GeoImage::INVALID;
    }
}
Example #3
0
osg::BoundingSphere SimplePager::getBounds(const TileKey& key) const
{
    int samples = 6;

    GeoExtent extent = key.getExtent();

    double xSample = extent.width() / (double)samples;
    double ySample = extent.height() / (double)samples;

    osg::BoundingSphere bs;
    for (int c = 0; c < samples+1; c++)
    {
        double x = extent.xMin() + (double)c * xSample;
        for (int r = 0; r < samples+1; r++)
        {
            double y = extent.yMin() + (double)r * ySample;
            osg::Vec3d world;

            GeoPoint samplePoint(extent.getSRS(), x, y, 0, ALTMODE_ABSOLUTE);

            GeoPoint wgs84 = samplePoint.transform(osgEarth::SpatialReference::create("epsg:4326"));
            wgs84.toWorld(world);
            bs.expandBy(world);
        }
    }
    return bs;
}
osg::Node*
SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary)
{
    // compile the model into a node:
    TileNode* tileNode = _modelCompiler->compile( model, _frame );

    // see if this tile might have children.
    bool prepareForChildren =
        setupChildrenIfNecessary &&
        model->_tileKey.getLOD() < *_options.maxLOD();

    osg::Node* result = 0L;

    if ( prepareForChildren )
    {
        //Compute the min range based on the 2D size of the tile
        osg::BoundingSphere bs = tileNode->getBound();
        GeoExtent extent = model->_tileKey.getExtent();
        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
        osg::Vec3d ll, ur;
        lowerLeft.toWorld( ll );
        upperRight.toWorld( ur );
        double radius = (ur - ll).length() / 2.0;
        float minRange = (float)(radius * _options.minTileRangeFactor().value());

        TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles );
        plod->setCenter  ( bs.center() );
        plod->addChild   ( tileNode );
        plod->setRange   ( 0, minRange, FLT_MAX );
        plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" );
        plod->setRange   ( 1, 0, minRange );

#if USE_FILELOCATIONCALLBACK
        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
        options->setFileLocationCallback( new FileLocationCallback() );
        plod->setDatabaseOptions( options );
#endif
        
        result = plod;

        // this one rejects back-facing tiles:
        if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
        {
            osg::HeightField* hf =
                model->_elevationData.getHeightField();

            result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
                hf,
                tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
                *_options.verticalScale() ) );
        }
    }
    else
    {
        result = tileNode;
    }

    return result;
}
osg::BoundingSphered
FeatureModelGraph::getBoundInWorldCoords( const GeoExtent& extent ) const
{
    osg::Vec3d center, corner;

    GeoExtent workingExtent;

    if ( extent.getSRS()->isEquivalentTo( _usableMapExtent.getSRS() ) )
    {
        workingExtent = extent;
    }
    else
    {
        workingExtent = extent.transform( _usableMapExtent.getSRS() ); // safe.
    }

    workingExtent.getCentroid( center.x(), center.y() );
    corner.x() = workingExtent.xMin();
    corner.y() = workingExtent.yMin();

    if ( _session->getMapInfo().isGeocentric() )
    {
        workingExtent.getSRS()->transformToECEF( center, center );
        workingExtent.getSRS()->transformToECEF( corner, corner );
    }

    return osg::BoundingSphered( center, (center-corner).length() );
}
Example #6
0
GeoImage*
GeoImage::reproject(const SpatialReference* to_srs, const GeoExtent* to_extent, unsigned int width, unsigned int height) const
{  
    GeoExtent destExtent;
    if (to_extent)
    {
        destExtent = *to_extent;
    }
    else
    {
         destExtent = getExtent().transform(to_srs);    
    }

    osg::Image* resultImage = 0L;

    if ( getSRS()->isUserDefined() || to_srs->isUserDefined() )
    {
        // if either of the SRS is a custom projection, we have to do a manual reprojection since
        // GDAL will not recognize the SRS.
        resultImage = manualReproject(getImage(), getExtent(), *to_extent, width, height);
    }
    else
    {
        // otherwise use GDAL.
        resultImage = reprojectImage(getImage(),
            getSRS()->getWKT(),
            getExtent().xMin(), getExtent().yMin(), getExtent().xMax(), getExtent().yMax(),
            to_srs->getWKT(),
            destExtent.xMin(), destExtent.yMin(), destExtent.xMax(), destExtent.yMax(),
            width, height);
    }   
    return new GeoImage(resultImage, destExtent);
}
osg::Node*
SerialKeyNodeFactory::createTile(TileModel* model,
                                 bool       tileHasRealData)
{
    // compile the model into a node:
    TileNode* tileNode = _modelCompiler->compile( model );

    // see if this tile might have children.
    bool prepareForChildren =
        (tileHasRealData || (_options.minLOD().isSet() && model->_tileKey.getLOD() < *_options.minLOD())) &&
        model->_tileKey.getLOD() < *_options.maxLOD();

    osg::Node* result = 0L;

    if ( prepareForChildren )
    {
        //Compute the min range based on the 2D size of the tile
        osg::BoundingSphere bs = tileNode->getBound();
        GeoExtent extent = model->_tileKey.getExtent();
        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
        osg::Vec3d ll, ur;
        lowerLeft.toWorld( ll );
        upperRight.toWorld( ur );
        double radius = (ur - ll).length() / 2.0;
        float minRange = (float)(radius * _options.minTileRangeFactor().value());

        osgDB::Options* dbOptions = Registry::instance()->cloneOrCreateOptions();

        TileGroup* plod = new TileGroup(tileNode, _engineUID, _liveTiles.get(), _deadTiles.get(), dbOptions);
        plod->setSubtileRange( minRange );


#if USE_FILELOCATIONCALLBACK
        dbOptions->setFileLocationCallback( new FileLocationCallback() );
#endif
        
        result = plod;
    }
    else
    {
        result = tileNode;
    }

    // this one rejects back-facing tiles:
    if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true )
    {
        osg::HeightField* hf =
            model->_elevationData.getHeightField();

        result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
            hf,
            tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
            *_options.verticalScale() ) );
    }

    return result;
}
Example #8
0
void
Profile::addIntersectingTiles(const GeoExtent& key_ext, unsigned localLOD, std::vector<TileKey>& out_intersectingKeys) const
{
    // assume a non-crossing extent here.
    if ( key_ext.crossesAntimeridian() )
    {
        OE_WARN << "Profile::addIntersectingTiles cannot process date-line cross" << std::endl;
        return;
    }

    int tileMinX, tileMaxX;
    int tileMinY, tileMaxY;

    double destTileWidth, destTileHeight;
    getTileDimensions(localLOD, destTileWidth, destTileHeight);

    //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
    //OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;

    double east = key_ext.xMax() - _extent.xMin();
    bool xMaxOnTileBoundary = fmod(east, destTileWidth) == 0.0;

    double south = _extent.yMax() - key_ext.yMin();
    bool yMaxOnTileBoundary = fmod(south, destTileHeight) == 0.0;

    tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
    tileMaxX = (int)(east / destTileWidth) - (xMaxOnTileBoundary ? 1 : 0);

    tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); 
    tileMaxY = (int)(south / destTileHeight) - (yMaxOnTileBoundary ? 1 : 0);

    unsigned int numWide, numHigh;
    getNumTiles(localLOD, numWide, numHigh);

    // bail out if the tiles are out of bounds.
    if ( tileMinX >= (int)numWide || tileMinY >= (int)numHigh ||
         tileMaxX < 0 || tileMaxY < 0 )
    {
        return;
    }

    tileMinX = osg::clampBetween(tileMinX, 0, (int)numWide-1);
    tileMaxX = osg::clampBetween(tileMaxX, 0, (int)numWide-1);
    tileMinY = osg::clampBetween(tileMinY, 0, (int)numHigh-1);
    tileMaxY = osg::clampBetween(tileMaxY, 0, (int)numHigh-1);

    OE_DEBUG << std::fixed << "  Dest Tiles: " << tileMinX << "," << tileMinY << " => " << tileMaxX << "," << tileMaxY << std::endl;

    for (int i = tileMinX; i <= tileMaxX; ++i)
    {
        for (int j = tileMinY; j <= tileMaxY; ++j)
        {
            //TODO: does not support multi-face destination keys.
            out_intersectingKeys.push_back( TileKey(localLOD, i, j, this) );
        }
    }
}
Example #9
0
osgTerrain::ImageLayer* 
MapEngine::createImageLayer(Map* map, 
                            MapLayer* layer,
                            const TileKey* key,
                            ProgressCallback* progress)
{
    Threading::ScopedReadLock lock( map->getMapDataMutex() );

    osg::ref_ptr< GeoImage > geoImage;
    
    //If the key is valid, try to get the image from the MapLayer
    if (layer->isKeyValid( key ) )
    {
        geoImage = layer->createImage(key, progress);
    }
    else
    {
        //If the key is not valid, simply make a transparent tile
        geoImage = new GeoImage(ImageUtils::createEmptyImage(), key->getGeoExtent());
    }

    if (geoImage.valid())
    {
        bool isProjected = map->getCoordinateSystemType() == Map::CSTYPE_PROJECTED;
        bool isPlateCarre = isProjected && map->getProfile()->getSRS()->isGeographic();
        bool isGeocentric = !isProjected;

        osg::ref_ptr<GeoLocator> imgLocator;

        // Use a special locator for mercator images (instead of reprojecting)
        if (map->getProfile()->getSRS()->isGeographic() &&
            geoImage->getSRS()->isMercator() &&
            layer->useMercatorFastPath() == true )
        {
            GeoExtent gx = geoImage->getExtent().transform( geoImage->getExtent().getSRS()->getGeographicSRS() );
            imgLocator = key->getProfile()->getSRS()->createLocator( gx.xMin(), gx.yMin(), gx.xMax(), gx.yMax() );
            imgLocator = new MercatorLocator( *imgLocator.get(), geoImage->getExtent() );
        }
        else
        {
            const GeoExtent& gx = geoImage->getExtent();
            imgLocator = GeoLocator::createForKey( key, map );
        }

        if ( isGeocentric )
            imgLocator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC );

        //osgTerrain::ImageLayer* imgLayer = new osgTerrain::ImageLayer( geoImage->getImage() );
        TransparentLayer* imgLayer = new TransparentLayer(geoImage->getImage(), layer);
        imgLayer->setLocator( imgLocator.get() );
        imgLayer->setLevelOfDetail( key->getLevelOfDetail() );
		imgLayer->setMinFilter( layer->getMinFilter().value());
		imgLayer->setMagFilter( layer->getMagFilter().value());
        return imgLayer;
    }
    return NULL;
}
Example #10
0
TileMap*
TileMap::create(const std::string& url,
                const Profile*     profile,
                const DataExtentList& dataExtents,
                const std::string& format,
                int                tile_width,
                int                tile_height)
{
    const GeoExtent& ex = profile->getExtent();

    TileMap* tileMap = new TileMap();
    tileMap->setProfileType(profile->getProfileType());
    tileMap->setExtents(ex.xMin(), ex.yMin(), ex.xMax(), ex.yMax());
    tileMap->setOrigin(ex.xMin(), ex.yMin());
    tileMap->_filename = url;
    tileMap->_srs = getHorizSRSString(profile->getSRS());
    tileMap->_vsrs = profile->getSRS()->getVertInitString();
    tileMap->_format.setWidth( tile_width );
    tileMap->_format.setHeight( tile_height );
    profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh );

    // format can be a mime-type or an extension:
    std::string::size_type p = format.find('/');
    if ( p == std::string::npos )
    {
        tileMap->_format.setExtension(format);
        tileMap->_format.setMimeType( Registry::instance()->getMimeTypeForExtension(format) );
    }
    else
    {
        tileMap->_format.setMimeType(format);
        tileMap->_format.setExtension( Registry::instance()->getExtensionForMimeType(format) );
    }

    //Add the data extents
    tileMap->getDataExtents().insert(tileMap->getDataExtents().end(), dataExtents.begin(), dataExtents.end());

    // If we have some data extents specified then make a nicer bounds than the 
    if (!tileMap->getDataExtents().empty())
    {
        // Get the union of all the extents
        GeoExtent e(tileMap->getDataExtents()[0]);
        for (unsigned int i = 1; i < tileMap->getDataExtents().size(); i++)
        {
            e.expandToInclude(tileMap->getDataExtents()[i]);
        }

        // Convert the bounds to the output profile
        GeoExtent bounds = e.transform(profile->getSRS());
        tileMap->setExtents(bounds.xMin(), bounds.yMin(), bounds.xMax(), bounds.yMax());
    }

    tileMap->generateTileSets();
    tileMap->computeMinMaxLevel();

    return tileMap;
}
Example #11
0
osg::BoundingSphered
FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
                                         const MapFrame*  mapf ) const
{
    osg::Vec3d center, corner;
    GeoExtent workingExtent;

    if ( !extent.isValid() )
    {
        return osg::BoundingSphered();
    }

    if ( extent.getSRS()->isEquivalentTo( _usableMapExtent.getSRS() ) )
    {
        workingExtent = extent;
    }
    else
    {
        workingExtent = extent.transform( _usableMapExtent.getSRS() ); // safe.
    }

    workingExtent.getCentroid( center.x(), center.y() );
    
    double centerZ = 0.0;    
    if ( mapf )
    {
        // Use an appropriate resolution for this extents width
        double resolution = workingExtent.width();
        ElevationQuery query( *mapf );
        GeoPoint p( mapf->getProfile()->getSRS(), center, ALTMODE_ABSOLUTE );
        query.getElevation( p, center.z(), resolution );
        centerZ = center.z();
    }    

    corner.x() = workingExtent.xMin();
    corner.y() = workingExtent.yMin();
    corner.z() = 0;

    if ( _session->getMapInfo().isGeocentric() )
    {
        const SpatialReference* ecefSRS = workingExtent.getSRS()->getECEF();
        workingExtent.getSRS()->transform( center, ecefSRS, center );
        workingExtent.getSRS()->transform( corner, ecefSRS, corner );
        //workingExtent.getSRS()->transformToECEF( center, center );
        //workingExtent.getSRS()->transformToECEF( corner, corner );
    }

    if (workingExtent.getSRS()->isGeographic() &&
        ( workingExtent.width() >= 90 || workingExtent.height() >= 90 ) )
    {
        return osg::BoundingSphered( osg::Vec3d(0,0,0), 2*center.length() );
    }

    return osg::BoundingSphered( center, (center-corner).length() );
}
Example #12
0
GeoExtent
GeoExtent::intersectionSameSRS( const GeoExtent& rhs ) const
{
    Bounds b(
        osg::maximum( xMin(), rhs.xMin() ),
        osg::maximum( yMin(), rhs.yMin() ),
        osg::minimum( xMax(), rhs.xMax() ),
        osg::minimum( yMax(), rhs.yMax() ) );

    return b.width() > 0 && b.height() > 0 ? GeoExtent( getSRS(), b ) : GeoExtent::INVALID;
}
Example #13
0
GeoExtent
UnifiedCubeProfile::transformGcsExtentOnFace( const GeoExtent& gcsExtent, int face ) const
{
    if ( face < 4 )
    {
        const GeoExtent& fex = _faceExtent_gcs[face];

        return GeoExtent(
            getSRS(),
            (double)face + (gcsExtent.xMin()-fex.xMin()) / fex.width(),
            (gcsExtent.yMin()-fex.yMin()) / fex.height(),
            (double)face + (gcsExtent.xMax()-fex.xMin()) / fex.width(),
            (gcsExtent.yMax()-fex.yMin()) / fex.height() );
    }
    else
    {
        // transform all 4 corners; then do the min/max for x/y.
        double lon[4] = { gcsExtent.xMin(), gcsExtent.xMax(), gcsExtent.xMax(), gcsExtent.xMin() };
        double lat[4] = { gcsExtent.yMin(), gcsExtent.yMin(), gcsExtent.yMax(), gcsExtent.yMax() };
        double x[4], y[4];

        for( int i=0; i<4; ++i )
        {
            int dummy;
            if ( ! CubeUtils::latLonToFaceCoords( lat[i], lon[i], x[i], y[i], dummy, face ) )
            {
                OE_WARN << LC << "transformGcsExtentOnFace, ll2fc failed" << std::endl;
            }
        }

        double xmin = SMALLEST( x[0], x[1], x[2], x[3] );
        double xmax = LARGEST( x[0], x[1], x[2], x[3] );
        double ymin = SMALLEST( y[0], y[1], y[2], y[3] );
        double ymax = LARGEST( y[0], y[1], y[2], y[3] );

        CubeUtils::faceToCube( xmin, ymin, face );
        CubeUtils::faceToCube( xmax, ymax, face );

        return GeoExtent( getSRS(), xmin, ymin, xmax, ymax );
    }
}
Example #14
0
GeoHeightField
GeoHeightField::createSubSample( const GeoExtent& destEx, ElevationInterpolation interpolation) const
{
    double div = destEx.width()/_extent.width();
    if ( div >= 1.0f )
        return GeoHeightField::INVALID;

    int w = _heightField->getNumColumns();
    int h = _heightField->getNumRows();
    //double dx = _heightField->getXInterval() * div;
    //double dy = _heightField->getYInterval() * div;
    double xInterval = _extent.width() / (double)(_heightField->getNumColumns()-1);
    double yInterval = _extent.height() / (double)(_heightField->getNumRows()-1);
    double dx = xInterval * div;
    double dy = yInterval * div;

    osg::HeightField* dest = new osg::HeightField();
    dest->allocate( w, h );
    dest->setXInterval( dx );
    dest->setYInterval( dy );

    // copy over the skirt height, adjusting it for tile size.
    dest->setSkirtHeight( _heightField->getSkirtHeight() * div );

    double x, y;
    int col, row;

    for( x = destEx.xMin(), col=0; col < w; x += dx, col++ )
    {
        for( y = destEx.yMin(), row=0; row < h; y += dy, row++ )
        {
            float height = HeightFieldUtils::getHeightAtLocation( _heightField.get(), x, y, _extent.xMin(), _extent.yMin(), xInterval, yInterval, interpolation);
            dest->setHeight( col, row, height );
        }
    }

    osg::Vec3d orig( destEx.xMin(), destEx.yMin(), _heightField->getOrigin().z() );
    dest->setOrigin( orig );

    return GeoHeightField( dest, destEx, _vsrs.get() );
}
Example #15
0
bool
GeoExtent::intersects( const GeoExtent& rhs ) const
{
    bool valid = isValid();
    if ( !valid ) return false;
    bool exclusive =
        _xmin > rhs.xMax() ||
        _xmax < rhs.xMin() ||
        _ymin > rhs.yMax() ||
        _ymax < rhs.yMin();
    return !exclusive;
}
Example #16
0
bool
GeoLocator::createScaleBiasMatrix(const GeoExtent& window, osg::Matrixf& out) const
{
    float scalex = window.width() / _dataExtent.width();
    float scaley = window.height() / _dataExtent.height();
    float biasx  = (window.xMin()-_dataExtent.xMin()) / _dataExtent.width();
    float biasy  = (window.yMin()-_dataExtent.yMin()) / _dataExtent.height();

    out(0,0) = scalex;
    out(1,1) = scaley;
    out(3,0) = biasx;
    out(3,1) = biasy;

    return true;
}
osg::HeightField*
HeightFieldUtils::createReferenceHeightField(const GeoExtent& ex,
                                             unsigned         numCols,
                                             unsigned         numRows,
                                             bool             expressAsHAE)
{
    osg::HeightField* hf = new osg::HeightField();
    hf->allocate( numCols, numRows );
    hf->setOrigin( osg::Vec3d( ex.xMin(), ex.yMin(), 0.0 ) );
    hf->setXInterval( (ex.xMax() - ex.xMin())/(double)(numCols-1) );
    hf->setYInterval( (ex.yMax() - ex.yMin())/(double)(numRows-1) );

    const VerticalDatum* vdatum = ex.isValid() ? ex.getSRS()->getVerticalDatum() : 0L;

    if ( vdatum && expressAsHAE )
    {
        // need the lat/long extent for geoid queries:
        GeoExtent geodeticExtent = ex.getSRS()->isGeographic() ? ex : ex.transform( ex.getSRS()->getGeographicSRS() );
        double latMin = geodeticExtent.yMin();
        double lonMin = geodeticExtent.xMin();
        double lonInterval = geodeticExtent.width() / (double)(numCols-1);
        double latInterval = geodeticExtent.height() / (double)(numRows-1);

        for( unsigned r=0; r<numRows; ++r )
        {            
            double lat = latMin + latInterval*(double)r;
            for( unsigned c=0; c<numCols; ++c )
            {
                double lon = lonMin + lonInterval*(double)c;
                double offset = vdatum->msl2hae(lat, lon, 0.0);
                hf->setHeight( c, r, offset );
            }
        }
    }
    else
    {
        for(unsigned int i=0; i<hf->getHeightList().size(); i++ )
        {
            hf->getHeightList()[i] = 0.0;
        }
    }

    hf->setBorderWidth( 0 );
    return hf;    
}
osg::BoundingSphered
FeatureModelGraph::getBoundInWorldCoords(const GeoExtent& extent,
                                         const MapFrame*  mapf ) const
{
    osg::Vec3d center, corner;
    //double z = 0.0;
    GeoExtent workingExtent;

    if ( extent.getSRS()->isEquivalentTo( _usableMapExtent.getSRS() ) )
    {
        workingExtent = extent;
    }
    else
    {
        workingExtent = extent.transform( _usableMapExtent.getSRS() ); // safe.
    }

    workingExtent.getCentroid( center.x(), center.y() );
    
    double centerZ = 0.0;    
    if ( mapf )
    {
        // Use an appropriate resolution for this extents width
        double resolution = workingExtent.width();             
        ElevationQuery query( *mapf );
        query.getElevation( GeoPoint(mapf->getProfile()->getSRS(),center), center.z(), resolution );
        centerZ = center.z();
    }    

    corner.x() = workingExtent.xMin();
    corner.y() = workingExtent.yMin();
    corner.z() = 0;

    if ( _session->getMapInfo().isGeocentric() )
    {
        workingExtent.getSRS()->transformToECEF( center, center );
        workingExtent.getSRS()->transformToECEF( corner, corner );
    }

    return osg::BoundingSphered( center, (center-corner).length() );
}
void
HeightFieldUtils::resolveInvalidHeights(osg::HeightField* grid,
                                        const GeoExtent&  ex,
                                        float             invalidValue,
                                        const Geoid*      geoid)
{
    if ( geoid )
    {
        // need the lat/long extent for geoid queries:
        unsigned numRows = grid->getNumRows();
        unsigned numCols = grid->getNumColumns();
        GeoExtent geodeticExtent = ex.getSRS()->isGeographic() ? ex : ex.transform( ex.getSRS()->getGeographicSRS() );
        double latMin = geodeticExtent.yMin();
        double lonMin = geodeticExtent.xMin();
        double lonInterval = geodeticExtent.width() / (double)(numCols-1);
        double latInterval = geodeticExtent.height() / (double)(numRows-1);

        for( unsigned r=0; r<numRows; ++r )
        {
            double lat = latMin + latInterval*(double)r;
            for( unsigned c=0; c<numCols; ++c )
            {
                double lon = lonMin + lonInterval*(double)c;
                if ( grid->getHeight(c, r) == invalidValue )
                {
                    grid->setHeight( c, r, geoid->getHeight(lat, lon) );
                }
            }
        }
    }
    else
    {
        for(unsigned int i=0; i<grid->getHeightList().size(); i++ )
        {
            if ( grid->getHeightList()[i] == invalidValue )
            {
                grid->getHeightList()[i] = 0.0;
            }
        }
    }
}
osg::Node*
SingleKeyNodeFactory::createTile(TileModel*        model,
                                 bool              setupChildrenIfNecessary,
                                 ProgressCallback* progress)
{
#ifdef EXPERIMENTAL_TILE_NODE_CACHE
    osg::ref_ptr<TileNode> tileNode;
    TileNodeCache::Record rec;
    cache.get(model->_tileKey, rec);
    if ( rec.valid() )
    {
        tileNode = rec.value().get();
    }
    else
    {
        tileNode = _modelCompiler->compile( model, _frame );
        cache.insert(model->_tileKey, tileNode);
    }
#else
    // compile the model into a node:
    TileNode* tileNode = _modelCompiler->compile(model, _frame, progress);
    tileNode->setEngineUID( _engineUID );
#endif

    // see if this tile might have children.
    bool prepareForChildren =
        setupChildrenIfNecessary &&
        model->_tileKey.getLOD() < *_options.maxLOD();

    osg::Node* result = 0L;

    if ( prepareForChildren )
    {
        osg::BoundingSphere bs = tileNode->getBound();
        TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles );
        plod->setCenter  ( bs.center() );
        plod->addChild   ( tileNode );
        plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" );
        plod->setDebug   ( _debug );

        if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT )
        {
            //Compute the min range based on the 2D size of the tile
            GeoExtent extent = model->_tileKey.getExtent();
            double radius = 0.0;
            
#if 0
            // Test code to use the equitorial radius so that all of the tiles at the same level
            // have the same range.  This will make the poles page in more appropriately.
            if (_frame.getMapInfo().isGeocentric())
            {
                GeoExtent equatorialExtent(
                extent.getSRS(),
                extent.west(),
                -extent.height()/2.0,
                extent.east(),
                extent.height()/2.0 );
                radius = equatorialExtent.getBoundingGeoCircle().getRadius();
            }
            else
#endif
            {
                GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
                GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
                osg::Vec3d ll, ur;
                lowerLeft.toWorld( ll );
                upperRight.toWorld( ur );
                radius = (ur - ll).length() / 2.0;
            }
          
            float minRange = (float)(radius * _options.minTileRangeFactor().value());

            plod->setRange( 0, minRange, FLT_MAX );
            plod->setRange( 1, 0, minRange );
            plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT );
        }
        else
        {
            // the *2 is because we page in 4-tile sets, not individual tiles.
            float size = 2.0f * _options.tilePixelSize().value();
            plod->setRange( 0, 0.0f, size );
            plod->setRange( 1, size, FLT_MAX );
            plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN );
        }
        
        // Install a tile-aligned bounding box in the pager node itself so we can do
        // visibility testing before paging in subtiles.
        plod->setChildBoundingBoxAndMatrix(
            1,
            tileNode->getTerrainBoundingBox(),
            tileNode->getMatrix() );

        // DBPager will set a priority based on the ratio range/maxRange.
        // This will offset that number with a full LOD #, giving LOD precedence.
        // Experimental.
        //plod->setPriorityScale( 1, model->_tileKey.getLOD()+1 );

#if USE_FILELOCATIONCALLBACK
        osgDB::Options* options = plod->getOrCreateDBOptions();
        options->setFileLocationCallback( new FileLocationCallback() );
#endif
        
        result = plod;

        // this one rejects back-facing tiles:
        if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
        {

#if 1
            osg::HeightField* hf =
                model->_elevationData.getHeightField();

            result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
                hf,
                tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
                *_options.verticalScale() ) );
#else
            // This works, but isn't quite as tight at the cluster culler.
            // Re-evaluate down the road.
            result->addCullCallback( new HorizonTileCuller(
                _frame.getMapInfo().getSRS(),
                tileNode->getTerrainBoundingBox(),
                tileNode->getMatrix(), tileNode->getKey() ) );
#endif
        }
    }
    else
    {
        result = tileNode;
    }

    return result;
}
    //override
    bool renderFeaturesForStyle(
        const Style&       style,
        const FeatureList& features,
        osg::Referenced*   buildData,
        const GeoExtent&   imageExtent,
        osg::Image*        image )
    {
        // A processing context to use with the filters:
        FilterContext context;
        context.setProfile( getFeatureSource()->getFeatureProfile() );

        const LineSymbol*    masterLine = style.getSymbol<LineSymbol>();
        const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>();

        // sort into bins, making a copy for lines that require buffering.
        FeatureList polygons;
        FeatureList lines;

        for(FeatureList::const_iterator f = features.begin(); f != features.end(); ++f)
        {
            if ( f->get()->getGeometry() )
            {
                if ( masterPoly || f->get()->style()->has<PolygonSymbol>() )
                {
                    polygons.push_back( f->get() );
                }

                if ( masterLine || f->get()->style()->has<LineSymbol>() )
                {
                    Feature* newFeature = new Feature( *f->get() );
                    if ( !newFeature->getGeometry()->isLinear() )
                    {
                        newFeature->setGeometry( newFeature->getGeometry()->cloneAs(Geometry::TYPE_RING) );
                    }
                    lines.push_back( newFeature );
                }
            }
        }

        // initialize:
        RenderFrame frame;
        frame.xmin = imageExtent.xMin();
        frame.ymin = imageExtent.yMin();
        frame.xf   = (double)image->s() / imageExtent.width();
        frame.yf   = (double)image->t() / imageExtent.height();

        if ( lines.size() > 0 )
        {
            // We are buffering in the features native extent, so we need to use the
            // transformed extent to get the proper "resolution" for the image
            const SpatialReference* featureSRS = context.profile()->getSRS();
            GeoExtent transformedExtent = imageExtent.transform(featureSRS);

            double trans_xf = (double)image->s() / transformedExtent.width();
            double trans_yf = (double)image->t() / transformedExtent.height();

            // resolution of the image (pixel extents):
            double xres = 1.0/trans_xf;
            double yres = 1.0/trans_yf;

            // downsample the line data so that it is no higher resolution than to image to which
            // we intend to rasterize it. If you don't do this, you run the risk of the buffer 
            // operation taking forever on very high-res input data.
            if ( _options.optimizeLineSampling() == true )
            {
                ResampleFilter resample;
                resample.minLength() = osg::minimum( xres, yres );
                context = resample.push( lines, context );
            }

            // now run the buffer operation on all lines:
            BufferFilter buffer;
            double lineWidth = 1.0;
            if ( masterLine )
            {
                buffer.capStyle() = masterLine->stroke()->lineCap().value();

                if ( masterLine->stroke()->width().isSet() )
                {
                    lineWidth = masterLine->stroke()->width().value();

                    GeoExtent imageExtentInFeatureSRS = imageExtent.transform(featureSRS);
                    double pixelWidth = imageExtentInFeatureSRS.width() / (double)image->s();

                    // if the width units are specified, process them:
                    if (masterLine->stroke()->widthUnits().isSet() &&
                        masterLine->stroke()->widthUnits().get() != Units::PIXELS)
                    {
                        const Units& featureUnits = featureSRS->getUnits();
                        const Units& strokeUnits  = masterLine->stroke()->widthUnits().value();

                        // if the units are different than those of the feature data, we need to
                        // do a units conversion.
                        if ( featureUnits != strokeUnits )
                        {
                            if ( Units::canConvert(strokeUnits, featureUnits) )
                            {
                                // linear to linear, no problem
                                lineWidth = strokeUnits.convertTo( featureUnits, lineWidth );
                            }
                            else if ( strokeUnits.isLinear() && featureUnits.isAngular() )
                            {
                                // linear to angular? approximate degrees per meter at the 
                                // latitude of the tile's centroid.
                                lineWidth = masterLine->stroke()->widthUnits()->convertTo(Units::METERS, lineWidth);
                                double circ = featureSRS->getEllipsoid()->getRadiusEquator() * 2.0 * osg::PI;
                                double x, y;
                                context.profile()->getExtent().getCentroid(x, y);
                                double radians = (lineWidth/circ) * cos(osg::DegreesToRadians(y));
                                lineWidth = osg::RadiansToDegrees(radians);
                            }
                        }

                        // enfore a minimum width of one pixel.
                        float minPixels = masterLine->stroke()->minPixels().getOrUse( 1.0f );
                        lineWidth = osg::clampAbove(lineWidth, pixelWidth*minPixels);
                    }

                    else // pixels
                    {
                        lineWidth *= pixelWidth;
                    }
                }
            }

            buffer.distance() = lineWidth * 0.5;   // since the distance is for one side
            buffer.push( lines, context );
        }

        // Transform the features into the map's SRS:
        TransformFilter xform( imageExtent.getSRS() );
        xform.setLocalizeCoordinates( false );
        FilterContext polysContext = xform.push( polygons, context );
        FilterContext linesContext = xform.push( lines, context );

        // set up the AGG renderer:
        agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 );

        // Create the renderer and the rasterizer
        agg::renderer<agg::span_abgr32> ren(rbuf);
        agg::rasterizer ras;

        // Setup the rasterizer
        ras.gamma(1.3);
        ras.filling_rule(agg::fill_even_odd);

        // construct an extent for cropping the geometry to our tile.
        // extend just outside the actual extents so we don't get edge artifacts:
        GeoExtent cropExtent = GeoExtent(imageExtent);
        cropExtent.scale(1.1, 1.1);

        osg::ref_ptr<Symbology::Polygon> cropPoly = new Symbology::Polygon( 4 );
        cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMin(), 0 ));
        cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMin(), 0 ));
        cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 ));
        cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 ));

        // render the polygons
        for(FeatureList::iterator i = polygons.begin(); i != polygons.end(); i++)
        {
            Feature*  feature  = i->get();
            Geometry* geometry = feature->getGeometry();

            osg::ref_ptr<Geometry> croppedGeometry;
            if ( geometry->crop( cropPoly.get(), croppedGeometry ) )
            {
                const PolygonSymbol* poly =
                    feature->style().isSet() && feature->style()->has<PolygonSymbol>() ? feature->style()->get<PolygonSymbol>() :
                    masterPoly;
                
                const osg::Vec4 color = poly ? static_cast<osg::Vec4>(poly->fill()->color()) : osg::Vec4(1,1,1,1);
                rasterize(croppedGeometry.get(), color, frame, ras, ren);
            }
        }

        // render the lines
        for(FeatureList::iterator i = lines.begin(); i != lines.end(); i++)
        {
            Feature*  feature  = i->get();
            Geometry* geometry = feature->getGeometry();

            osg::ref_ptr<Geometry> croppedGeometry;
            if ( geometry->crop( cropPoly.get(), croppedGeometry ) )
            {
                const LineSymbol* line =
                    feature->style().isSet() && feature->style()->has<LineSymbol>() ? feature->style()->get<LineSymbol>() :
                    masterLine;
                
                const osg::Vec4 color = line ? static_cast<osg::Vec4>(line->stroke()->color()) : osg::Vec4(1,1,1,1);
                rasterize(croppedGeometry.get(), color, frame, ras, ren);
            }
        }

        return true;
    }
    //override
    bool renderFeaturesForStyle(
        const Style&       style,
        const FeatureList& inFeatures,
        osg::Referenced*   buildData,
        const GeoExtent&   imageExtent,
        osg::Image*        image )
    {
        // local copy of the features that we can process
        FeatureList features = inFeatures;

        BuildData* bd = static_cast<BuildData*>( buildData );

        // A processing context to use with the filters:
        FilterContext context;
        context.profile() = getFeatureSource()->getFeatureProfile();

        const LineSymbol* masterLine = style.getSymbol<LineSymbol>();
        const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>();

        //bool embeddedStyles = getFeatureSource()->hasEmbeddedStyles();

        // if only a line symbol exists, and there are polygons in the mix, draw them
        // as outlines (line rings).
        //OE_INFO << LC << "Line Symbol = " << (masterLine == 0L ? "null" : masterLine->getConfig().toString()) << std::endl;
        //OE_INFO << LC << "Poly SYmbol = " << (masterPoly == 0L ? "null" : masterPoly->getConfig().toString()) << std::endl;

        //bool convertPolysToRings = poly == 0L && line != 0L;
        //if ( convertPolysToRings )
        //    OE_INFO << LC << "No PolygonSymbol; will draw polygons to rings" << std::endl;

        // initialize:
        double xmin = imageExtent.xMin();
        double ymin = imageExtent.yMin();
        //double s = (double)image->s();
        //double t = (double)image->t();
        double xf = (double)image->s() / imageExtent.width();
        double yf = (double)image->t() / imageExtent.height();

        // strictly speaking we should iterate over the features and buffer each one that's a line,
        // rather then checking for the existence of a LineSymbol.
        FeatureList linesToBuffer;
        for(FeatureList::iterator i = features.begin(); i != features.end(); i++)
        {
            Feature* feature = i->get();
            Geometry* geom = feature->getGeometry();

            if ( geom )
            {
                // check for an embedded style:
                const LineSymbol* line = feature->style().isSet() ? 
                    feature->style()->getSymbol<LineSymbol>() : masterLine;

                const PolygonSymbol* poly =
                    feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly;

                // if we have polygons but only a LineSymbol, draw the poly as a line.
                if ( geom->getComponentType() == Geometry::TYPE_POLYGON )
                {
                    if ( !poly && line )
                    {
                        Feature* outline = new Feature( *feature );
                        geom = geom->cloneAs( Geometry::TYPE_RING );
                        outline->setGeometry( geom );
                        *i = outline;
                        feature = outline;
                    }
                    //TODO: fix to enable outlined polys. doesn't work, not sure why -gw
                    //else if ( poly && line )
                    //{
                    //    Feature* outline = new Feature();
                    //    geom = geom->cloneAs( Geometry::TYPE_LINESTRING );
                    //    outline->setGeometry( geom );
                    //    features.push_back( outline );
                    //}
                }

                bool needsBuffering =
                    geom->getComponentType() == Geometry::TYPE_LINESTRING || 
                    geom->getComponentType() == Geometry::TYPE_RING;

                if ( needsBuffering )
                {
                    linesToBuffer.push_back( feature );
                }
            }
        }

        if ( linesToBuffer.size() > 0 )
        {
            //We are buffering in the features native extent, so we need to use the transform extent to get the proper "resolution" for the image
            GeoExtent transformedExtent = imageExtent.transform(context.profile()->getSRS());

            double trans_xf = (double)image->s() / transformedExtent.width();
            double trans_yf = (double)image->t() / transformedExtent.height();

            // resolution of the image (pixel extents):
            double xres = 1.0/trans_xf;
            double yres = 1.0/trans_yf;

            // downsample the line data so that it is no higher resolution than to image to which
            // we intend to rasterize it. If you don't do this, you run the risk of the buffer 
            // operation taking forever on very high-res input data.
            if ( _options.optimizeLineSampling() == true )
            {
                ResampleFilter resample;
                resample.minLength() = osg::minimum( xres, yres );
                context = resample.push( linesToBuffer, context );
            }

            // now run the buffer operation on all lines:
            BufferFilter buffer;
            float lineWidth = 0.5;
            if ( masterLine )
            {
                buffer.capStyle() = masterLine->stroke()->lineCap().value();

                if ( masterLine->stroke()->width().isSet() )
                    lineWidth = masterLine->stroke()->width().value();
            }

            // "relative line size" means that the line width is expressed in (approx) pixels
            // rather than in map units
            if ( _options.relativeLineSize() == true )
                buffer.distance() = xres * lineWidth;
            else
                buffer.distance() = lineWidth;

            buffer.push( linesToBuffer, context );
        }

        // First, transform the features into the map's SRS:
        TransformFilter xform( imageExtent.getSRS() );
        xform.setLocalizeCoordinates( false );
        context = xform.push( features, context );

        // set up the AGG renderer:
        agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 );

        // Create the renderer and the rasterizer
        agg::renderer<agg::span_abgr32> ren(rbuf);
        agg::rasterizer ras;

        // Setup the rasterizer
        ras.gamma(1.3);
        ras.filling_rule(agg::fill_even_odd);

        GeoExtent cropExtent = GeoExtent(imageExtent);
        cropExtent.scale(1.1, 1.1);

        osg::ref_ptr<Symbology::Polygon> cropPoly = new Symbology::Polygon( 4 );
        cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMin(), 0 ));
        cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMin(), 0 ));
        cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 ));
        cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 ));

        double lineWidth = 1.0;
        if ( masterLine )
            lineWidth = (double)masterLine->stroke()->width().value();

        osg::Vec4 color = osg::Vec4(1, 1, 1, 1);
        if ( masterLine )
            color = masterLine->stroke()->color();

        // render the features
        for(FeatureList::iterator i = features.begin(); i != features.end(); i++)
        {
            Feature* feature = i->get();
            //bool first = bd->_pass == 0 && i == features.begin();

            Geometry* geometry = feature->getGeometry();

            osg::ref_ptr< Geometry > croppedGeometry;
            if ( ! geometry->crop( cropPoly.get(), croppedGeometry ) )
                continue;

            // set up a default color:
            osg::Vec4 c = color;
            unsigned int a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up
            agg::rgba8 fgColor( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a );

            GeometryIterator gi( croppedGeometry.get() );
            while( gi.hasMore() )
            {
                c = color;
                Geometry* g = gi.next();
            
                const LineSymbol* line = feature->style().isSet() ? 
                    feature->style()->getSymbol<LineSymbol>() : masterLine;

                const PolygonSymbol* poly =
                    feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly;

                if (g->getType() == Geometry::TYPE_RING || g->getType() == Geometry::TYPE_LINESTRING)
                {
                    if ( line )
                        c = line->stroke()->color();
                    else if ( poly )
                        c = poly->fill()->color();
                }

                else if ( g->getType() == Geometry::TYPE_POLYGON )
                {
                    if ( poly )
                        c = poly->fill()->color();
                    else if ( line )
                        c = line->stroke()->color();
                }

                a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up
                fgColor = agg::rgba8( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a );

                ras.filling_rule( agg::fill_even_odd );
                for( Geometry::iterator p = g->begin(); p != g->end(); p++ )
                {
                    const osg::Vec3d& p0 = *p;
                    double x0 = xf*(p0.x()-xmin);
                    double y0 = yf*(p0.y()-ymin);

                    //const osg::Vec3d& p1 = p+1 != g->end()? *(p+1) : g->front();
                    //double x1 = xf*(p1.x()-xmin);
                    //double y1 = yf*(p1.y()-ymin);

                    if ( p == g->begin() )
                        ras.move_to_d( x0, y0 );
                    else
                        ras.line_to_d( x0, y0 );
                }
            }
            ras.render(ren, fgColor);
            ras.reset();
        }

        bd->_pass++;
        return true;            
    }
void
SerialKeyNodeFactory::addTile(TileModel* model, bool tileHasRealData, bool tileHasLodBlending, osg::Group* parent )
{
    // create a node:
    TileNode* tileNode = new TileNode( model->_tileKey, model->_tileLocator );

    // install the tile model and compile it:
    tileNode->setTileModel( model );
    tileNode->compile( _modelCompiler.get() );

    // assemble a URI for this tile's child group:
    std::string uri = Stringify() << model->_tileKey.str() << "." << _engineUID << ".osgearth_engine_mp_tile";

    osg::Node* result = 0L;

    // Only add the next tile if all the following are true:
    // 1. Either there's real tile data, or a minLOD is explicity set in the options;
    // 2. The tile isn't blacklisted; and
    // 3. We are still below the maximim LOD.
    bool wrapInPagedLOD =
        (tileHasRealData || (_options.minLOD().isSet() && model->_tileKey.getLOD() < *_options.minLOD())) &&
        !osgEarth::Registry::instance()->isBlacklisted( uri ) &&
        model->_tileKey.getLOD() < *_options.maxLOD();

    if ( wrapInPagedLOD )
    {
        osg::BoundingSphere bs = tileNode->getBound();
      
        float maxRange = FLT_MAX;
        
        //Compute the min range based on the 2D size of the tile
        GeoExtent extent = model->_tileKey.getExtent();
        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
        osg::Vec3d ll, ur;
        lowerLeft.toWorld( ll );
        upperRight.toWorld( ur );
        double radius = (ur - ll).length() / 2.0;
        float minRange = (float)(radius * _options.minTileRangeFactor().value());

        
        // create a PLOD so we can keep subdividing:
        osg::PagedLOD* plod = new CustomPagedLOD( _liveTiles.get(), _deadTiles.get() );
        plod->setCenter( bs.center() );
        plod->addChild( tileNode );
        plod->setRangeMode( *_options.rangeMode() );
        plod->setFileName( 1, uri );
  

        if (plod->getRangeMode() == osg::LOD::PIXEL_SIZE_ON_SCREEN)
        {
            static const float sqrt2 = sqrt(2.0f);

            minRange = 0;
            maxRange = (*_options.tilePixelSize()) * sqrt2;
            plod->setRange( 0, minRange, maxRange  );
            plod->setRange( 1, maxRange, FLT_MAX );            
        }
        else
        {
            plod->setRange( 0, minRange, maxRange );                
            plod->setRange( 1, 0, minRange );        
        }        
                        

        plod->setUserData( new MapNode::TileRangeData(minRange, maxRange) );

#if USE_FILELOCATIONCALLBACK
        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
        options->setFileLocationCallback( new FileLocationCallback() );
        plod->setDatabaseOptions( options );

#endif
        result = plod;
        
        if ( tileHasLodBlending )
        {
            // Make the LOD transition distance, and a measure of how
            // close the tile is to an LOD change, to shaders.
            result->addCullCallback(new LODFactorCallback);
        }
    }
    else
    {
        result = tileNode;
    }

    // this cull callback dynamically adjusts the LOD scale based on distance-to-camera:
    if ( _options.lodFallOff().isSet() && *_options.lodFallOff() > 0.0 )
    {
        result->addCullCallback( new DynamicLODScaleCallback(*_options.lodFallOff()) );
    }

    // this one rejects back-facing tiles:
    if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true )
    {
        osg::HeightField* hf =
            model->_elevationData.getHFLayer()->getHeightField();

        result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
            hf,
            tileNode->getLocator()->getEllipsoidModel(),
            *_options.verticalScale() ) );
    }

    parent->addChild( result );
}
void
SerialKeyNodeFactory::addTile(Tile* tile, bool tileHasRealData, bool tileHasLodBlending, osg::Group* parent )
{
    // associate this tile with the terrain:
    tile->setTerrainTechnique( _terrain->cloneTechnique() );
    tile->attachToTerrain( _terrain );

    // assemble a URI for this tile's child group:
    std::stringstream buf;
    buf << tile->getKey().str() << "." << _engineUID << ".osgearth_osgterrain_tile";
    std::string uri; uri = buf.str();

    osg::Node* result = 0L;

    // Only add the next tile if all the following are true:
    // 1. Either there's real tile data, or a maxLOD is explicity set in the options;
    // 2. The tile isn't blacklisted; and
    // 3. We are still below the max LOD.
    bool wrapInPagedLOD =
        (tileHasRealData || _options.maxLOD().isSet()) &&
        !osgEarth::Registry::instance()->isBlacklisted( uri ) &&
        tile->getKey().getLevelOfDetail() < (unsigned)*_options.maxLOD();

    if ( wrapInPagedLOD )
    {
        osg::BoundingSphere bs = tile->getBound();
        double maxRange = 1e10;        
        
#if 0
        //Compute the min range based on the actual bounds of the tile.  This can break down if you have very high resolution
        //data with elevation variations and you can run out of memory b/c the elevation change is greater than the actual size of the tile so you end up
        //inifinitely subdividing (or at least until you run out of data or memory)
        double minRange = bs.radius() * _options.minTileRangeFactor().value();
#else        
        //double origMinRange = bs.radius() * _options.minTileRangeFactor().value();        
        //Compute the min range based on the 2D size of the tile
        GeoExtent extent = tile->getKey().getExtent();        
        GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin());
        GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax());
        osg::Vec3d ll, ur;
        lowerLeft.toWorld( ll );
        upperRight.toWorld( ur );
        double radius = (ur - ll).length() / 2.0;
        double minRange = radius * _options.minTileRangeFactor().value();        
#endif

        // create a PLOD so we can keep subdividing:
        osg::PagedLOD* plod = new osg::PagedLOD();
        plod->setCenter( bs.center() );
        plod->addChild( tile, minRange, maxRange );

        plod->setFileName( 1, uri );
        plod->setRange   ( 1, 0, minRange );

        plod->setUserData( new MapNode::TileRangeData(minRange, maxRange) );

#if USE_FILELOCATIONCALLBACK
        osgDB::Options* options = Registry::instance()->cloneOrCreateOptions();
        options->setFileLocationCallback( new FileLocationCallback() );
        plod->setDatabaseOptions( options );

#endif
        result = plod;
        
        if ( tileHasLodBlending )
        {
            // Make the LOD transition distance, and a measure of how
            // close the tile is to an LOD change, to shaders.
            result->addCullCallback(new Drivers::LODFactorCallback);
        }
    }
    else
    {
        result = tile;
    }

    // this cull callback dynamically adjusts the LOD scale based on distance-to-camera:
    if ( _options.lodFallOff().isSet() && *_options.lodFallOff() > 0.0 )
    {
        result->addCullCallback( new DynamicLODScaleCallback(*_options.lodFallOff()) );
    }

    // this one rejects back-facing tiles:
    if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true )
    {
        result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
            static_cast<osgTerrain::HeightFieldLayer*>(tile->getElevationLayer())->getHeightField(),
            tile->getLocator()->getEllipsoidModel(),
            tile->getVerticalScale() ) );
    }

    parent->addChild( result );
}
Example #25
0
void
Profile::addIntersectingTiles(const GeoExtent& key_ext, unsigned localLOD, std::vector<TileKey>& out_intersectingKeys) const
{
    // assume a non-crossing extent here.
    if ( key_ext.crossesAntimeridian() )
    {
        OE_WARN << "Profile::addIntersectingTiles cannot process date-line cross" << std::endl;
        return;
    }

    int tileMinX, tileMaxX;
    int tileMinY, tileMaxY;

    // Special path for mercator (does NOT work for cube, e.g.)
    if ( key_ext.getSRS()->isMercator() )
    {
        int precision = 5;
        double eps = 0.001;

        double keyWidth = round(key_ext.width(), precision);
        int destLOD = 0;
        double w, h;
        getTileDimensions(0, w, h);
        for(; (round(w,precision) - keyWidth) > eps; w*=0.5, h*=0.5, destLOD++ );

        double destTileWidth, destTileHeight;
        getTileDimensions( destLOD, destTileWidth, destTileHeight );
        destTileWidth = round(destTileWidth, precision);
        destTileHeight = round(destTileHeight, precision);

        tileMinX = quantize( ((key_ext.xMin() - _extent.xMin()) / destTileWidth), eps );
        tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);

        tileMinY = quantize( ((_extent.yMax() - key_ext.yMax()) / destTileHeight), eps );
        tileMaxY = (int) ((_extent.yMax() - key_ext.yMin()) / destTileHeight);
    }

    else
    {
        double destTileWidth, destTileHeight;
        getTileDimensions(localLOD, destTileWidth, destTileHeight);

        //OE_DEBUG << std::fixed << "  Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl;
        //OE_DEBUG << std::fixed << "  Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl;

        tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth);
        tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth);

        tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight);
        tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight);
    }

    unsigned int numWide, numHigh;
    getNumTiles(localLOD, numWide, numHigh);

    // bail out if the tiles are out of bounds.
    if ( tileMinX >= (int)numWide || tileMinY >= (int)numHigh ||
            tileMaxX < 0 || tileMaxY < 0 )
    {
        return;
    }

    tileMinX = osg::clampBetween(tileMinX, 0, (int)numWide-1);
    tileMaxX = osg::clampBetween(tileMaxX, 0, (int)numWide-1);
    tileMinY = osg::clampBetween(tileMinY, 0, (int)numHigh-1);
    tileMaxY = osg::clampBetween(tileMaxY, 0, (int)numHigh-1);

    OE_DEBUG << std::fixed << "  Dest Tiles: " << tileMinX << "," << tileMinY << " => " << tileMaxX << "," << tileMaxY << std::endl;

    for (int i = tileMinX; i <= tileMaxX; ++i)
    {
        for (int j = tileMinY; j <= tileMaxY; ++j)
        {
            //TODO: does not support multi-face destination keys.
            out_intersectingKeys.push_back( TileKey(localLOD, i, j, this) );
        }
    }
}
Example #26
0
void
    TFSPackager::package( FeatureSource* features, const std::string& destination, const std::string& layername, const std::string& description )
{   
    if (!_destSRSString.empty())
    {
        _srs = SpatialReference::create( _destSRSString );
    }

    //Get the destination SRS from the feature source if it's not already set
    if (!_srs.valid())
    {
        _srs = features->getFeatureProfile()->getSRS();
    }
	
    //Get the extent of the dataset, or use the custom extent value
    GeoExtent srsExtent = _customExtent;
    if (!srsExtent.isValid())
        srsExtent = features->getFeatureProfile()->getExtent();

    //Transform to lat/lon extents
    GeoExtent extent = srsExtent.transform( _srs.get() );

    osg::ref_ptr< const osgEarth::Profile > profile = osgEarth::Profile::create(extent.getSRS(), extent.xMin(), extent.yMin(), extent.xMax(), extent.yMax(), 1, 1);


    TileKey rootKey = TileKey(0, 0, 0, profile );    


    osg::ref_ptr< FeatureTile > root = new FeatureTile( rootKey );
    //Loop through all the features and try to insert them into the quadtree
    osg::ref_ptr< FeatureCursor > cursor = features->createFeatureCursor( _query );
    int added = 0;
    int failed = 0;
    int skipped = 0;
    int highestLevel = 0;

    while (cursor.valid() && cursor->hasMore())
    {        
        osg::ref_ptr< Feature > feature = cursor->nextFeature();

        //Reproject the feature to the dest SRS if it's not already
        if (!feature->getSRS()->isEquivalentTo( _srs ) )
        {
            feature->transform( _srs );
        }

        if (feature->getGeometry() && feature->getGeometry()->getBounds().valid() && feature->getGeometry()->isValid())
        {

            AddFeatureVisitor v(feature.get(), _maxFeatures, _firstLevel, _maxLevel, _method);
            root->accept( &v );
            if (!v._added)
            {
                OE_NOTICE << "Failed to add feature " << feature->getFID() << std::endl;
                failed++;
            }
            else
            {                
                if (highestLevel < v._levelAdded)
                {
                    highestLevel = v._levelAdded;
                }
                added++;
                OE_DEBUG << "Added " << added << std::endl;
            }   
        }
        else
        {
            OE_NOTICE << "Skipping feature " << feature->getFID() << " with null or invalid geometry" << std::endl;
            skipped++;
        }
    }   
    OE_NOTICE << "Added=" << added << " Skipped=" << skipped << " Failed=" << failed << std::endl;

#if 1
    // Print the width of tiles at each level
    for (int i = 0; i <= highestLevel; ++i)
    {
        TileKey tileKey(i, 0, 0, profile);
        GeoExtent tileExtent = tileKey.getExtent();
        OE_NOTICE << "Level " << i << " tile size: " << tileExtent.width() << std::endl;
    }
#endif

    WriteFeaturesVisitor write(features, destination, _method, _srs);
    root->accept( &write );

    //Write out the meta doc
    TFSLayer layer;
    layer.setTitle( layername );
    layer.setAbstract( description );
    layer.setFirstLevel( _firstLevel );
    layer.setMaxLevel( highestLevel );
    layer.setExtent( profile->getExtent() );
    layer.setSRS( _srs.get() );
    TFSReaderWriter::write( layer, osgDB::concatPaths( destination, "tfs.xml"));

}
Example #27
0
osg::Node*
MGRSGraticule::buildSQIDTiles( const std::string& gzd )
{
    const GeoExtent& extent = _gzd[gzd];

    // parse the GZD into its components:
    unsigned zone;
    char letter;
    sscanf( gzd.c_str(), "%u%c", &zone, &letter );

    TextSymbol* textSym = _options->secondaryStyle()->get<TextSymbol>();
    if ( !textSym )
        textSym = _options->primaryStyle()->getOrCreate<TextSymbol>();

    AltitudeSymbol* alt = _options->secondaryStyle()->get<AltitudeSymbol>();
    double h = 0.0;

    TextSymbolizer ts( textSym );
    MGRSFormatter mgrs(MGRSFormatter::PRECISION_100000M);
    osg::Geode* textGeode = new osg::Geode();
    textGeode->getOrCreateStateSet()->setRenderBinDetails( 9999, "DepthSortedBin" );
    textGeode->getOrCreateStateSet()->setAttributeAndModes( _depthAttribute, 1 );

    const SpatialReference* ecefSRS = extent.getSRS()->getECEF();
    osg::Vec3d centerMap, centerECEF;
    extent.getCentroid(centerMap.x(), centerMap.y());
    extent.getSRS()->transform(centerMap, ecefSRS, centerECEF);
    //extent.getSRS()->transformToECEF(centerMap, centerECEF);

    osg::Matrix local2world;
    ecefSRS->createLocalToWorld( centerECEF, local2world ); //= ECEF::createLocalToWorld(centerECEF);
    osg::Matrix world2local;
    world2local.invert(local2world);

    FeatureList features;

    std::vector<GeoExtent> sqidExtents;

    // UTM:
    if ( letter > 'B' && letter < 'Y' )
    {
        // grab the SRS for the current UTM zone:
        // TODO: AL/AA designation??
        const SpatialReference* utm = SpatialReference::create(
                                          Stringify() << "+proj=utm +zone=" << zone << " +north +units=m" );

        // transform the four corners of the tile to UTM.
        osg::Vec3d gzdUtmSW, gzdUtmSE, gzdUtmNW, gzdUtmNE;
        extent.getSRS()->transform( osg::Vec3d(extent.xMin(),extent.yMin(),h), utm, gzdUtmSW );
        extent.getSRS()->transform( osg::Vec3d(extent.xMin(),extent.yMax(),h), utm, gzdUtmNW );
        extent.getSRS()->transform( osg::Vec3d(extent.xMax(),extent.yMin(),h), utm, gzdUtmSE );
        extent.getSRS()->transform( osg::Vec3d(extent.xMax(),extent.yMax(),h), utm, gzdUtmNE );

        // find the southern boundary of the first full SQID tile in the GZD tile.
        double southSQIDBoundary = gzdUtmSW.y(); //extentUTM.yMin();
        double remainder = fmod( southSQIDBoundary, 100000.0 );
        if ( remainder > 0.0 )
            southSQIDBoundary += (100000.0 - remainder);

        // find the min/max X for this cell in UTM:
        double xmin = extent.yMin() >= 0.0 ? gzdUtmSW.x() : gzdUtmNW.x();
        double xmax = extent.yMin() >= 0.0 ? gzdUtmSE.x() : gzdUtmNE.x();

        // Record the UTM extent of each SQID cell in this tile.
        // Go from the south boundary northwards:
        for( double y = southSQIDBoundary; y < gzdUtmNW.y(); y += 100000.0 )
        {
            // start at the central meridian (500K) and go west:
            for( double x = 500000.0; x > xmin; x -= 100000.0 )
            {
                sqidExtents.push_back( GeoExtent(utm, x-100000.0, y, x, y+100000.0) );
            }

            // then start at the central meridian and go east:
            for( double x = 500000.0; x < xmax; x += 100000.0 )
            {
                sqidExtents.push_back( GeoExtent(utm, x, y, x+100000.0, y+100000.0) );
            }
        }

        for( std::vector<GeoExtent>::iterator i = sqidExtents.begin(); i != sqidExtents.end(); ++i )
        {
            GeoExtent utmEx = *i;

            // now, clamp each of the points in the UTM SQID extent to the map-space
            // boundaries of the GZD tile. (We only need to clamp in the X dimension,
            // Y geometry is allowed to overflow.) Also, skip NE, we don't need it.
            double r, xlimit;

            osg::Vec3d sw(utmEx.xMin(), utmEx.yMin(), 0);
            r = (sw.y()-gzdUtmSW.y())/(gzdUtmNW.y()-gzdUtmSW.y());
            xlimit = gzdUtmSW.x() + r * (gzdUtmNW.x() - gzdUtmSW.x());
            if ( sw.x() < xlimit ) sw.x() = xlimit;

            osg::Vec3d nw(utmEx.xMin(), utmEx.yMax(), 0);
            r = (nw.y()-gzdUtmSW.y())/(gzdUtmNW.y()-gzdUtmSW.y());
            xlimit = gzdUtmSW.x() + r * (gzdUtmNW.x() - gzdUtmSW.x());
            if ( nw.x() < xlimit ) nw.x() = xlimit;

            osg::Vec3d se(utmEx.xMax(), utmEx.yMin(), 0);
            r = (se.y()-gzdUtmSE.y())/(gzdUtmNE.y()-gzdUtmSE.y());
            xlimit = gzdUtmSE.x() + r * (gzdUtmNE.x() - gzdUtmSE.x());
            if ( se.x() > xlimit ) se.x() = xlimit;

            // at the northernmost GZD (lateral band X), clamp the northernmost SQIDs to the upper latitude.
            if ( letter == 'X' && nw.y() > gzdUtmNW.y() )
                nw.y() = gzdUtmNW.y();

            // need this in order to calculate the font size:
            double utmWidth = se.x() - sw.x();

            // now transform the corner points back into the map SRS:
            utm->transform( sw, extent.getSRS(), sw );
            utm->transform( nw, extent.getSRS(), nw );
            utm->transform( se, extent.getSRS(), se );

            // and draw valid sqid geometry.
            if ( sw.x() < se.x() )
            {
                Feature* lat = new Feature(new LineString(2), extent.getSRS());
                lat->geoInterp() = GEOINTERP_RHUMB_LINE;
                lat->getGeometry()->push_back( sw );
                lat->getGeometry()->push_back( se );
                features.push_back(lat);

                Feature* lon = new Feature(new LineString(2), extent.getSRS());
                lon->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                lon->getGeometry()->push_back( sw );
                lon->getGeometry()->push_back( nw );
                features.push_back(lon);

                // and the text label:
                osg::Vec3d sqidTextMap = (nw + se) * 0.5;
                sqidTextMap.z() += 1000.0;
                osg::Vec3d sqidTextECEF;
                extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
                //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                osg::Vec3d sqidLocal;
                sqidLocal = sqidTextECEF * world2local;

                MGRSCoord mgrsCoord;
                if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) )
                {
                    textSym->size() = utmWidth/3.0;
                    osgText::Text* d = ts.create( mgrsCoord.sqid );

                    osg::Matrixd textLocal2World;
                    ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World );

                    d->setPosition( sqidLocal );
                    textGeode->addDrawable( d );
                }
            }
        }
    }

    else if ( letter == 'A' || letter == 'B' )
    {
        // SRS for south polar region UPS projection. This projection has (0,0) at the
        // south pole, with +X extending towards 90 degrees E longitude and +Y towards
        // 0 degrees longitude.
        const SpatialReference* ups = SpatialReference::create(
                                          "+proj=stere +lat_ts=-90 +lat_0=-90 +lon_0=0 +k_0=1 +x_0=0 +y_0=0");

        osg::Vec3d gtemp;
        double r = GeoMath::distance(-osg::PI_2, 0.0, -1.3962634, 0.0); // -90 => -80 latitude
        double r2 = r*r;

        if ( letter == 'A' )
        {
            for( double x = 0.0; x < 1200000.0; x += 100000.0 )
            {
                double yminmax = sqrt( r2 - x*x );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(-x, -yminmax, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d(-x,  yminmax, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double y = -1100000.0; y < 1200000.0; y += 100000.0 )
            {
                double xmax = sqrt( r2 - y*y );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(-xmax, y, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d(    0, y, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double x = -1200000.0; x < 0.0; x += 100000.0 )
            {
                for( double y = -1200000.0; y < 1200000.0; y += 100000.0 )
                {
                    osg::Vec3d sqidTextMap;
                    ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap);
                    if ( sqidTextMap.y() < -80.0 )
                    {
                        sqidTextMap.z() += 1000.0;
                        osg::Vec3d sqidTextECEF;
                        extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
                        //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                        osg::Vec3d sqidLocal = sqidTextECEF * world2local;

                        MGRSCoord mgrsCoord;
                        if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) )
                        {
                            textSym->size() = 33000.0;
                            osgText::Text* d = ts.create( mgrsCoord.sqid );
                            osg::Matrixd textLocal2World;
                            ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World );
                            d->setPosition( sqidLocal );
                            textGeode->addDrawable( d );
                        }
                    }
                }
            }
        }

        else if ( letter == 'B' )
        {
            for( double x = 100000.0; x < 1200000.0; x += 100000.0 )
            {
                double yminmax = sqrt( r2 - x*x );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(x, -yminmax, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d(x,  yminmax, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double y = -1100000.0; y < 1200000.0; y += 100000.0 )
            {
                double xmax = sqrt( r2 - y*y );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(    0, y, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d( xmax, y, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double x = 0.0; x < 1200000.0; x += 100000.0 )
            {
                for( double y = -1200000.0; y < 1200000.0; y += 100000.0 )
                {
                    osg::Vec3d sqidTextMap;
                    ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap);
                    if ( sqidTextMap.y() < -80.0 )
                    {
                        sqidTextMap.z() += 1000.0;
                        osg::Vec3d sqidTextECEF;
                        extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
                        //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                        osg::Vec3d sqidLocal = sqidTextECEF * world2local;

                        MGRSCoord mgrsCoord;
                        if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) )
                        {
                            textSym->size() = 33000.0;
                            osgText::Text* d = ts.create( mgrsCoord.sqid );
                            osg::Matrixd textLocal2World;
                            ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World );
                            d->setPosition( sqidLocal );
                            textGeode->addDrawable( d );
                        }
                    }
                }
            }
        }
    }

    else if ( letter == 'Y' || letter == 'Z' )
    {
        // SRS for north polar region UPS projection. This projection has (0,0) at the
        // south pole, with +X extending towards 90 degrees E longitude and +Y towards
        // 180 degrees longitude.
        const SpatialReference* ups = SpatialReference::create(
                                          "+proj=stere +lat_ts=90 +lat_0=90 +lon_0=0 +k_0=1 +x_0=0 +y_0=0");

        osg::Vec3d gtemp;
        double r = GeoMath::distance(osg::PI_2, 0.0, 1.46607657, 0.0); // 90 -> 84 latitude
        double r2 = r*r;

        if ( letter == 'Y' )
        {
            for( double x = 0.0; x < 700000.0; x += 100000.0 )
            {
                double yminmax = sqrt( r2 - x*x );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(-x, -yminmax, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d(-x,  yminmax, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double y = -600000.0; y < 700000.0; y += 100000.0 )
            {
                double xmax = sqrt( r2 - y*y );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(-xmax, y, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d(    0, y, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double x = -700000.0; x < 0.0; x += 100000.0 )
            {
                for( double y = -700000.0; y < 700000.0; y += 100000.0 )
                {
                    osg::Vec3d sqidTextMap;
                    ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap);
                    if ( sqidTextMap.y() > 84.0 )
                    {
                        sqidTextMap.z() += 1000.0;
                        osg::Vec3d sqidTextECEF;
                        extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
                        //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                        osg::Vec3d sqidLocal = sqidTextECEF * world2local;

                        MGRSCoord mgrsCoord;
                        if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) )
                        {
                            textSym->size() = 33000.0;
                            osgText::Text* d = ts.create( mgrsCoord.sqid );
                            osg::Matrixd textLocal2World;
                            ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World );
                            d->setPosition( sqidLocal );
                            textGeode->addDrawable( d );
                        }
                    }
                }
            }
        }

        else if ( letter == 'Z' )
        {
            for( double x = 100000.0; x < 700000.0; x += 100000.0 )
            {
                double yminmax = sqrt( r2 - x*x );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(x, -yminmax, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d(x,  yminmax, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double y = -600000.0; y < 700000.0; y += 100000.0 )
            {
                double xmax = sqrt( r2 - y*y );
                Feature* f = new Feature( new LineString(2), extent.getSRS() );
                f->geoInterp() = GEOINTERP_GREAT_CIRCLE;
                osg::Vec3d p0, p1;
                ups->transform( osg::Vec3d(    0, y, 0), extent.getSRS(), p0 );
                ups->transform( osg::Vec3d( xmax, y, 0), extent.getSRS(), p1 );
                f->getGeometry()->push_back( p0 );
                f->getGeometry()->push_back( p1 );
                features.push_back( f );
            }

            for( double x = 0.0; x < 700000.0; x += 100000.0 )
            {
                for( double y = -700000.0; y < 700000.0; y += 100000.0 )
                {
                    osg::Vec3d sqidTextMap;
                    ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap);
                    if ( sqidTextMap.y() > 84.0 )
                    {
                        sqidTextMap.z() += 1000.0;
                        osg::Vec3d sqidTextECEF;
                        extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF);
                        //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF);
                        osg::Vec3d sqidLocal = sqidTextECEF * world2local;

                        MGRSCoord mgrsCoord;
                        if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) )
                        {
                            textSym->size() = 33000.0;
                            osgText::Text* d = ts.create( mgrsCoord.sqid );
                            osg::Matrixd textLocal2World;
                            ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World );
                            d->setPosition( sqidLocal );
                            textGeode->addDrawable( d );
                        }
                    }
                }
            }
        }
    }

    osg::Group* group = new osg::Group();

    Style lineStyle;
    lineStyle.add( _options->secondaryStyle()->get<LineSymbol>() );
    lineStyle.add( _options->secondaryStyle()->get<AltitudeSymbol>() );

    GeometryCompiler compiler;
    osg::ref_ptr<Session> session = new Session( getMapNode()->getMap() );
    FilterContext context( session.get(), _featureProfile.get(), extent );

    // make sure we get sufficient tessellation:
    compiler.options().maxGranularity() = 0.25;

    osg::Node* geomNode = compiler.compile(features, lineStyle, context);
    if ( geomNode )
        group->addChild( geomNode );

    osg::MatrixTransform* mt = new osg::MatrixTransform(local2world);
    mt->addChild(textGeode);
    group->addChild( mt );

    // prep for depth offset:
    DepthOffsetUtils::prepareGraph( group );
    group->getOrCreateStateSet()->addUniform( _minDepthOffset.get() );

    return group;
}
Example #28
0
void 
GeodeticGraticule::updateLabels()
{
    const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("wgs84");
    
    Threading::ScopedMutexLock lock(_cameraDataMapMutex);
    for (CameraDataMap::iterator i = _cameraDataMap.begin(); i != _cameraDataMap.end(); ++i)
    {
        CameraData& cdata = i->second;

        std::vector< GeoExtent > extents;
        if (cdata._viewExtent.crossesAntimeridian())
        {
            GeoExtent first, second;
            cdata._viewExtent.splitAcrossAntimeridian(first, second);
            extents.push_back(first);
            extents.push_back(second);
        }
        else
        {
            extents.push_back( cdata._viewExtent );
        }

        double resDegrees = cdata._resolution * 180.0;
        // We want half the resolution so the labels don't appear as often as the grid lines
        resDegrees *= 2.0;

    
        // Hide all the labels
        for (unsigned int i = 0; i < cdata._labelPool.size(); i++)
        {
            cdata._labelPool[i]->setNodeMask(0);
        }

        // Approximate offset in degrees
        double degOffset = cdata._metersPerPixel / 111000.0;
     
        unsigned int labelIndex = 0;


        bool done = false;
        for (unsigned int extentIndex = 0; extentIndex < extents.size() && !done; extentIndex++)
        {
            GeoExtent extent = extents[extentIndex];

            int minLonIndex = floor(((extent.xMin() + 180.0)/resDegrees));
            int maxLonIndex = ceil(((extent.xMax() + 180.0)/resDegrees));

            int minLatIndex = floor(((extent.yMin() + 90)/resDegrees));
            int maxLatIndex = ceil(((extent.yMax() + 90)/resDegrees));

            // Generate horizontal labels
            for (int i = minLonIndex; i <= maxLonIndex && !done; i++)
            {
                GeoPoint point(srs, -180.0 + (double)i * resDegrees, cdata._lat + (_centerOffset.y() * degOffset), 0, ALTMODE_ABSOLUTE);
                LabelNode* label = cdata._labelPool[labelIndex++].get();

                label->setNodeMask(~0u);
                label->setPosition(point);
                std::string text = getText( point, false);
                label->setText( text );
                if (labelIndex == cdata._labelPool.size() - 1)
                {
                    done = true;
                }
            }

            // Generate the vertical labels
            for (int i = minLatIndex; i <= maxLatIndex && !done; i++)
            {
                GeoPoint point(srs, cdata._lon + (_centerOffset.x() * degOffset), -90.0 + (double)i * resDegrees, 0, ALTMODE_ABSOLUTE);
                // Skip drawing labels at the poles
                if (osg::equivalent(osg::absolute( point.y()), 90.0, 0.1))
                {
                    continue;
                }
                LabelNode* label = cdata._labelPool[labelIndex++].get();
                label->setNodeMask(~0u);
                label->setPosition(point);
                std::string text = getText( point, true);
                label->setText( text );
                if (labelIndex == cdata._labelPool.size() - 1)
                {
                    done = true;
                }
            }
        }
    }
}
osg::Node*
SingleKeyNodeFactory::createTile(TileModel*        model,
                                 bool              setupChildrenIfNecessary,
                                 ProgressCallback* progress)
{
#ifdef EXPERIMENTAL_TILE_NODE_CACHE
    osg::ref_ptr<TileNode> tileNode;
    TileNodeCache::Record rec;
    cache.get(model->_tileKey, rec);
    if ( rec.valid() )
    {
        tileNode = rec.value().get();
    }
    else
    {
        tileNode = _modelCompiler->compile( model, _frame );
        cache.insert(model->_tileKey, tileNode);
    }
#else
    // compile the model into a node:
    TileNode* tileNode = _modelCompiler->compile(model, _frame, progress);
#endif

    // see if this tile might have children.
    bool prepareForChildren =
        setupChildrenIfNecessary &&
        model->_tileKey.getLOD() < *_options.maxLOD();

    osg::Node* result = 0L;

    if ( prepareForChildren )
    {
        osg::BoundingSphere bs = tileNode->getBound();
        TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles );
        plod->setCenter  ( bs.center() );
        plod->addChild   ( tileNode );
        plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" );

        if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT )
        {
            //Compute the min range based on the 2D size of the tile
            GeoExtent extent = model->_tileKey.getExtent();
            GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE);
            GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE);
            osg::Vec3d ll, ur;
            lowerLeft.toWorld( ll );
            upperRight.toWorld( ur );
            double radius = (ur - ll).length() / 2.0;
            float minRange = (float)(radius * _options.minTileRangeFactor().value());

            plod->setRange( 0, minRange, FLT_MAX );
            plod->setRange( 1, 0, minRange );
            plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT );
        }
        else
        {
            plod->setRange( 0, 0.0f, _options.tilePixelSize().value() );
            plod->setRange( 1, _options.tilePixelSize().value(), FLT_MAX );
            plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN );
        }



        // DBPager will set a priority based on the ratio range/maxRange.
        // This will offset that number with a full LOD #, giving LOD precedence.
        // Experimental.
        //plod->setPriorityScale( 1, model->_tileKey.getLOD()+1 );

#if USE_FILELOCATIONCALLBACK
        osgDB::Options* options = plod->getOrCreateDBOptions();
        options->setFileLocationCallback( new FileLocationCallback() );
#endif
        
        result = plod;

        // this one rejects back-facing tiles:
        if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true )
        {
            osg::HeightField* hf =
                model->_elevationData.getHeightField();

            result->addCullCallback( HeightFieldUtils::createClusterCullingCallback(
                hf,
                tileNode->getKey().getProfile()->getSRS()->getEllipsoid(),
                *_options.verticalScale() ) );
        }
    }
    else
    {
        result = tileNode;
    }

    return result;
}
Example #30
0
osg::Node*
GeodeticGraticule::buildTile( const TileKey& key, Map* map ) const
{
    if ( _options->levels().size() <= key.getLevelOfDetail() )
    {
        OE_WARN << LC << "Tried to create cell at non-existant level " << key.getLevelOfDetail() << std::endl;
        return 0L;
    }

    const GeodeticGraticuleOptions::Level& level = _options->levels()[key.getLevelOfDetail()]; //_levels[key.getLevelOfDetail()];


    // the "-2" here is because normal tile paging gives you one subdivision already,
    // so we only need to account for > 1 subdivision factor.
    unsigned cellsPerTile = level._subdivisionFactor <= 2u ? 1u : 1u << (level._subdivisionFactor-2u);
    unsigned cellsPerTileX = std::max(1u, cellsPerTile);
    unsigned cellsPerTileY = std::max(1u, cellsPerTile);


    GeoExtent tileExtent = key.getExtent();

    FeatureList latLines;
    FeatureList lonLines;

    static LatLongFormatter s_llf(LatLongFormatter::FORMAT_DECIMAL_DEGREES);

    double cellWidth = tileExtent.width() / cellsPerTileX;
    double cellHeight = tileExtent.height() / cellsPerTileY;

    const Style& lineStyle = level._lineStyle.isSet() ? *level._lineStyle : *_options->lineStyle();
    const Style& textStyle = level._textStyle.isSet() ? *level._textStyle : *_options->textStyle();

    bool hasText = textStyle.get<TextSymbol>() != 0L;

    osg::ref_ptr<osg::Group> labels;
    if ( hasText )
    {
        labels = new osg::Group();
        //TODO:  This is a bug, if you don't turn on decluttering the text labels are giant.  Need to determine what is wrong with LabelNodes without decluttering.
        Decluttering::setEnabled( labels->getOrCreateStateSet(), true );
    }

    // spatial ref for features:
    const SpatialReference* geoSRS = tileExtent.getSRS()->getGeographicSRS();

    // longitude lines
    for( unsigned cx = 0; cx < cellsPerTileX; ++cx )
    {
        double clon = tileExtent.xMin() + cellWidth * (double)cx;
        LineString* lon = new LineString(2);
        lon->push_back( osg::Vec3d(clon, tileExtent.yMin(), 0) );
        lon->push_back( osg::Vec3d(clon, tileExtent.yMax(), 0) );
        lonLines.push_back( new Feature(lon, geoSRS) );

        if ( hasText )
        {
            for( unsigned cy = 0; cy < cellsPerTileY; ++cy )
            {
                double clat = tileExtent.yMin() + (0.5*cellHeight) + cellHeight*(double)cy;
                LabelNode* label = new LabelNode(
                    _mapNode.get(),
                    GeoPoint(geoSRS, clon, clat),
                    s_llf.format(clon),
                    textStyle );
                labels->addChild( label );
            }
        }
    }

    // latitude lines
    for( unsigned cy = 0; cy < cellsPerTileY; ++cy )
    {
        double clat = tileExtent.yMin() + cellHeight * (double)cy;
        if ( clat == key.getProfile()->getExtent().yMin() )
            continue;

        LineString* lat = new LineString(2);
        lat->push_back( osg::Vec3d(tileExtent.xMin(), clat, 0) );
        lat->push_back( osg::Vec3d(tileExtent.xMax(), clat, 0) );
        latLines.push_back( new Feature(lat, geoSRS) );

        if ( hasText )
        {
            for( unsigned cx = 0; cx < cellsPerTileX; ++cx )
            {
                double clon = tileExtent.xMin() + (0.5*cellWidth) + cellWidth*(double)cy;
                LabelNode* label = new LabelNode(
                    _mapNode.get(),
                    GeoPoint(geoSRS, clon, clat),
                    s_llf.format(clat),
                    textStyle );
                labels->addChild( label );
            }
        }
    }

    osg::Group* group = new osg::Group();

    GeometryCompiler compiler;
    osg::ref_ptr<Session> session = new Session( map );
    FilterContext context( session.get(), _featureProfile.get(), tileExtent );

    // make sure we get sufficient tessellation:
    compiler.options().maxGranularity() = std::min(cellWidth, cellHeight) / 16.0;

    compiler.options().geoInterp() = GEOINTERP_GREAT_CIRCLE;
    osg::Node* lonNode = compiler.compile(lonLines, lineStyle, context);
    if ( lonNode )
        group->addChild( lonNode );

    compiler.options().geoInterp() = GEOINTERP_RHUMB_LINE;
    osg::Node* latNode = compiler.compile(latLines, lineStyle, context);
    if ( latNode )
        group->addChild( latNode );

    // add the labels.
    if ( labels.valid() )
        group->addChild( labels.get() );

    // get the geocentric tile center:
    osg::Vec3d tileCenter;
    tileExtent.getCentroid( tileCenter.x(), tileCenter.y() );

    const SpatialReference* ecefSRS = tileExtent.getSRS()->getECEF();
    osg::Vec3d centerECEF;
    tileExtent.getSRS()->transform( tileCenter, ecefSRS, centerECEF );
    //tileExtent.getSRS()->transformToECEF( tileCenter, centerECEF );

    osg::NodeCallback* ccc = 0L;
    // set up cluster culling.
    if ( tileExtent.getSRS()->isGeographic() && tileExtent.width() < 90.0 && tileExtent.height() < 90.0 )
    {
        ccc = ClusterCullingFactory::create( group, centerECEF );
    }

    // add a paging node for higher LODs:
    if ( key.getLevelOfDetail() + 1 < _options->levels().size() )
    {
        const GeodeticGraticuleOptions::Level& nextLevel = _options->levels()[key.getLevelOfDetail()+1];

        osg::BoundingSphere bs = group->getBound();

        std::string uri = Stringify() << key.str() << "_" << getID() << "." << GRID_MARKER << "." << GRATICULE_EXTENSION;

        osg::PagedLOD* plod = new osg::PagedLOD();
        plod->setCenter( bs.center() );
        plod->addChild( group, std::max(level._minRange,nextLevel._maxRange), FLT_MAX );
        plod->setFileName( 1, uri );
        plod->setRange( 1, 0, nextLevel._maxRange );
        group = plod;
    }

    // or, if this is the deepest level and there's a minRange set, we need an LOD:
    else if ( level._minRange > 0.0f )
    {
        osg::LOD* lod = new osg::LOD();
        lod->addChild( group, level._minRange, FLT_MAX );
        group = lod;
    }

    if ( ccc )
    {
        osg::Group* cccGroup = new osg::Group();
        cccGroup->addCullCallback( ccc );
        cccGroup->addChild( group );
        group = cccGroup;
    }

    return group;
}