// override
    // Creates an image.
    osg::Image* createImage(const TileKey&    key,
                            ProgressCallback* progress )
    {
        if ( !_imageLayer.valid() || !_featureSource.valid() )
            return 0L;

        // fetch the image for this key:
        GeoImage image = _imageLayer->createImage(key, progress);
        if ( !image.valid() )
            return 0L;

        // fetch a set of features for this key. The features are in their
        // own SRS, so we need to transform:
        const SpatialReference* featureSRS = _featureSource->getFeatureProfile()->getSRS();
        GeoExtent extentInFeatureSRS = key.getExtent().transform( featureSRS );

        // assemble a spatial query. It helps if your features have a spatial index.
        Query query;
        query.bounds() = extentInFeatureSRS.bounds();
        //query.expression() = ... // SQL expression compatible with data source
        osg::ref_ptr<FeatureCursor> cursor = _featureSource->createFeatureCursor(query);

        // create a new image to return.
        osg::Image* output = new osg::Image();
        //output->allocateImage(128, 128, 1, GL_RGB, GL_UNSIGNED_BYTE);

        // do your magic here.

        return output;
    }
Example #2
0
Query
Query::combineWith( const Query& rhs ) const
{
    Query merged;

    // merge the expressions:

    bool lhsEmptyExpr = !_expression.isSet() || _expression->empty();
    bool rhsEmptyExpr = !rhs.expression().isSet() || rhs.expression()->empty();

    if ( !lhsEmptyExpr && !rhsEmptyExpr )
    {
        std::stringstream buf;
        buf << "( " << *_expression << " ) AND ( " << *rhs.expression() << " )";
        std::string str;
        str = buf.str();
        merged.expression() = str;
    }
    else if ( lhsEmptyExpr && !rhsEmptyExpr )
    {
        merged.expression() = *rhs.expression();
    }
    else if ( !lhsEmptyExpr && rhsEmptyExpr )
    {
        merged.expression() = *_expression;
    }

    // tilekey overrides bounds:
    if ( _tileKey.isSet() )
    {
        merged.tileKey() = *_tileKey;
    }
    else if ( rhs._tileKey.isSet() )
    {
        merged.tileKey() = *rhs._tileKey;
    }

    // merge the bounds:
    if ( bounds().isSet() && rhs.bounds().isSet() )
    {
        merged.bounds() = bounds()->intersectionWith( *rhs.bounds() );
    }
    else if ( bounds().isSet() )
    {
        merged.bounds() = *bounds();
    }
    else if ( rhs.bounds().isSet() )
    {
        merged.bounds() = *rhs.bounds();
    }

    return merged;
}
 /**
  * Gets all the features that intersect the extent
  */
 void getFeatures(const GeoExtent& extent, FeatureList& features)
 {
     GeoExtent localExtent = extent.transform( _featureSource->getFeatureProfile()->getSRS() );
     Query query;
     query.bounds() = localExtent.bounds();
     if (localExtent.intersects( _featureSource->getFeatureProfile()->getExtent()))
     {
         osg::ref_ptr< FeatureCursor > cursor = _featureSource->createFeatureCursor( query );
         if (cursor)
         {
             cursor->fill( features );
         }
     }     
 }
Example #4
0
/**
 * Querys the feature source;
 * Visits each feature and uses the Style Expression to resolve its style class;
 * Sorts the features into bins based on style class;
 * Compiles each bin into a separate style group;
 * Adds the resulting style groups to the provided parent.
 */
void
FeatureModelGraph::queryAndSortIntoStyleGroups(const Query&            query,
                                               const StringExpression& styleExpr,
                                               FeatureSourceIndex*     index,
                                               osg::Group*             parent)
{
    // the profile of the features
    const FeatureProfile* featureProfile = _session->getFeatureSource()->getFeatureProfile();

    // get the extent of the full set of feature data:
    const GeoExtent& extent = featureProfile->getExtent();
    
    // query the feature source:
    osg::ref_ptr<FeatureCursor> cursor = _session->getFeatureSource()->createFeatureCursor( query );
    if ( !cursor.valid() )
        return;

    // establish the working bounds and a context:
    Bounds bounds = query.bounds().isSet() ? *query.bounds() : extent.bounds();
    FilterContext context( _session.get(), featureProfile, GeoExtent(featureProfile->getSRS(), bounds), index );
    StringExpression styleExprCopy( styleExpr );

    // visit each feature and run the expression to sort it into a bin.
    std::map<std::string, FeatureList> styleBins;
    while( cursor->hasMore() )
    {
        osg::ref_ptr<Feature> feature = cursor->nextFeature();
        if ( feature.valid() )
        {
            const std::string& styleString = feature->eval( styleExprCopy, &context );
            styleBins[styleString].push_back( feature.get() );
        }
    }

    // next create a style group per bin.
    for( std::map<std::string,FeatureList>::iterator i = styleBins.begin(); i != styleBins.end(); ++i )
    {
        const std::string& styleString = i->first;
        FeatureList&       workingSet  = i->second;

        // resolve the style:
        Style combinedStyle;

        // if the style string begins with an open bracket, it's an inline style definition.
        if ( styleString.length() > 0 && styleString.at(0) == '{' )
        {
            Config conf( "style", styleString );
            conf.set( "type", "text/css" );
            combinedStyle = Style(conf);
        }

        // otherwise, look up the style in the stylesheet:
        else
        {
            const Style* selectedStyle = _session->styles()->getStyle(styleString);
            if ( selectedStyle )
                combinedStyle = *selectedStyle;
        }

        // create the node and add it.
        osg::Group* styleGroup = createStyleGroup(combinedStyle, workingSet, context);
        if ( styleGroup )
            parent->addChild( styleGroup );
    }
}
Example #5
0
/**
 * Builds geometry for feature data at a particular level, and constrained by an extent.
 * The extent is either (a) expressed in "extent" literally, as is the case in a non-tiled
 * data source, or (b) expressed implicitly by a TileKey, which is the case for a tiled
 * data source.
 */
osg::Group*
FeatureModelGraph::buildLevel( const FeatureLevel& level, const GeoExtent& extent, const TileKey* key )
{
    // set up for feature indexing if appropriate:
    osg::ref_ptr<osg::Group> group;
    FeatureSourceIndexNode* index = 0L;
    if ( _session->getFeatureSource() && (_options.featureIndexing() == true) )
    {
        index = new FeatureSourceIndexNode( _session->getFeatureSource() );
        group = index;
    }
    else
    {
        group = new osg::Group();
    }

    // form the baseline query, which does a spatial query based on the working extent.
    Query query;
    if ( extent.isValid() )
        query.bounds() = extent.bounds();

    // add a tile key to the query if there is one, to support TFS-style queries
    if ( key )
        query.tileKey() = *key;

    // does the level have a style name set?
    if ( level.styleName().isSet() )
    {
        osg::Node* node = 0L;
        const Style* style = _session->styles()->getStyle( *level.styleName(), false );
        if ( style )
        {
            // found a specific style to use.
            node = createStyleGroup( *style, query, index );
            if ( node )
                group->addChild( node );
        }
        else
        {
            const StyleSelector* selector = _session->styles()->getSelector( *level.styleName() );
            if ( selector )
            {
                buildStyleGroups( selector, query, index, group.get() );
            }
        }
    }

    else
    {
        // attempt to glean the style from the feature source name:
        const Style style = *_session->styles()->getStyle( 
            *_session->getFeatureSource()->getFeatureSourceOptions().name() );

        osg::Node* node = build( style, query, extent, index );
        if ( node )
            group->addChild( node );
    }

    if ( group->getNumChildren() > 0 )
    {
        
        // account for a min-range here. Do not address the max-range here; that happens
        // above when generating paged LOD nodes, etc.        
        float minRange = level.minRange();

        /*
        if ( _options.minRange().isSet() ) 
            minRange = std::max(minRange, *_options.minRange());

        if ( _options.layout().isSet() && _options.layout()->minRange().isSet() )
            minRange = std::max(minRange, *_options.layout()->minRange());
            */

        if ( minRange > 0.0f )
        {
            // minRange can't be less than the tile geometry's radius.
            minRange = std::max(minRange, group->getBound().radius());
            osg::LOD* lod = new osg::LOD();
            lod->addChild( group.get(), minRange, FLT_MAX );
            group = lod;
        }        

        if ( _session->getMapInfo().isGeocentric() && _options.clusterCulling() == true )
        {
            const FeatureProfile* featureProfile = _session->getFeatureSource()->getFeatureProfile();
            const GeoExtent& ccExtent = extent.isValid() ? extent : featureProfile->getExtent();
            if ( ccExtent.isValid() )
            {
                // if the extent is more than 90 degrees, bail
                GeoExtent geodeticExtent = ccExtent.transform( ccExtent.getSRS()->getGeographicSRS() );
                if ( geodeticExtent.width() < 90.0 && geodeticExtent.height() < 90.0 )
                {
                    // get the geocentric tile center:
                    osg::Vec3d tileCenter;
                    ccExtent.getCentroid( tileCenter.x(), tileCenter.y() );
                    osg::Vec3d centerECEF;
                    ccExtent.getSRS()->transformToECEF( tileCenter, centerECEF );

                    osg::NodeCallback* ccc = ClusterCullingFactory::create2( group.get(), centerECEF );
                    if ( ccc )
                        group->addCullCallback( ccc );
                }
            }
        }

        // if indexing is enabled, build the index now.
        if ( index )
            index->reindex();

        return group.release();
    }

    else
    {
        return 0L;
    }
}
    osg::HeightField* createHeightField( const TileKey&        key,
                                         ProgressCallback*     progress)
    {
        if (key.getLevelOfDetail() > _maxDataLevel)
        {
            //OE_NOTICE << "Reached maximum data resolution key=" << key.getLevelOfDetail() << " max=" << _maxDataLevel <<  std::endl;
            return NULL;
        }

        int tileSize = _options.tileSize().value();

        //Allocate the heightfield
        osg::ref_ptr<osg::HeightField> hf = new osg::HeightField;
        hf->allocate(tileSize, tileSize);
        for (unsigned int i = 0; i < hf->getHeightList().size(); ++i) hf->getHeightList()[i] = NO_DATA_VALUE;

	    if (intersects(key))
        {
            //Get the extents of the tile
            double xmin, ymin, xmax, ymax;
            key.getExtent().getBounds(xmin, ymin, xmax, ymax);

            const SpatialReference* featureSRS = _features->getFeatureProfile()->getSRS();
            GeoExtent extentInFeatureSRS = key.getExtent().transform( featureSRS );

            const SpatialReference* keySRS = key.getProfile()->getSRS();
            
            // populate feature list
            // assemble a spatial query. It helps if your features have a spatial index.
            Query query;
            query.bounds() = extentInFeatureSRS.bounds();

		    FeatureList featureList;
            osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor(query);
            while ( cursor.valid() && cursor->hasMore() )
            {
                Feature* f = cursor->nextFeature();
                if ( f && f->getGeometry() )
                    featureList.push_back(f);
            }

            // We now have a feature list in feature SRS.

            bool transformRequired = !keySRS->isHorizEquivalentTo(featureSRS);
		    
			if (!featureList.empty())
			{
				// Iterate over the output heightfield and sample the data that was read into it.
				double dx = (xmax - xmin) / (tileSize-1);
				double dy = (ymax - ymin) / (tileSize-1);

				for (int c = 0; c < tileSize; ++c)
				{
					double geoX = xmin + (dx * (double)c);
					for (int r = 0; r < tileSize; ++r)
					{
						double geoY = ymin + (dy * (double)r);

						float h = NO_DATA_VALUE;

						for (FeatureList::iterator f = featureList.begin(); f != featureList.end(); ++f)
						{
							osgEarth::Symbology::Polygon* boundary = dynamic_cast<osgEarth::Symbology::Polygon*>((*f)->getGeometry());

							if (!boundary)
							{
								OE_WARN << LC << "NOT A POLYGON" << std::endl;
							}
							else
							{
								GeoPoint geo(keySRS, geoX, geoY, 0.0, ALTMODE_ABSOLUTE);

                                if ( transformRequired )
                                    geo = geo.transform(featureSRS);

								if ( boundary->contains2D(geo.x(), geo.y()) )
								{
                                    h = (*f)->getDouble(_options.attr().value());

                                    if ( keySRS->isGeographic() )
                                    {                              
                                        // for a round earth, must adjust the final elevation accounting for the
                                        // curvature of the earth; so we have to adjust it in the feature boundary's
                                        // local tangent plane.
                                        Bounds bounds = boundary->getBounds();
                                        GeoPoint anchor( featureSRS, bounds.center().x(), bounds.center().y(), h, ALTMODE_ABSOLUTE );
                                        if ( transformRequired )
                                            anchor = anchor.transform(keySRS);

                                        // For transforming between ECEF and local tangent plane:
                                        osg::Matrix localToWorld, worldToLocal;
                                        anchor.createLocalToWorld(localToWorld);
                                        worldToLocal.invert( localToWorld );

                                        // Get the ECEF location of the anchor point:
                                        osg::Vec3d ecef;
                                        geo.toWorld( ecef );

                                        // Move it into Local Tangent Plane coordinates:
                                        osg::Vec3d local = ecef * worldToLocal;

                                        // Reset the Z to zero, since the LTP is centered on the "h" elevation:
                                        local.z() = 0.0;

                                        // Back into ECEF:
                                        ecef = local * localToWorld;

                                        // And back into lat/long/alt:
                                        geo.fromWorld( geo.getSRS(), ecef);

                                        h = geo.z();
                                    }
									break;
								}
							}
						}

						hf->setHeight(c, r, h-0.1);
					}
				}
			}	
        }
        return hf.release();
    }
bool
FeatureTileSource::queryAndRenderFeaturesForStyle(const Style&     style,
                                                  const Query&     query,
                                                  osg::Referenced* data,
                                                  const GeoExtent& imageExtent,
                                                  osg::Image*      out_image)
{   
    // first we need the overall extent of the layer:
    const GeoExtent& featuresExtent = getFeatureSource()->getFeatureProfile()->getExtent();
    
    // convert them both to WGS84, intersect the extents, and convert back.
    GeoExtent featuresExtentWGS84 = featuresExtent.transform( featuresExtent.getSRS()->getGeographicSRS() );
    GeoExtent imageExtentWGS84 = imageExtent.transform( featuresExtent.getSRS()->getGeographicSRS() );
    GeoExtent queryExtentWGS84 = featuresExtentWGS84.intersectionSameSRS( imageExtentWGS84 );
    if ( queryExtentWGS84.isValid() )
    {
        GeoExtent queryExtent = queryExtentWGS84.transform( featuresExtent.getSRS() );

        // incorporate the image extent into the feature query for this style:
        Query localQuery = query;
        localQuery.bounds() = 
            query.bounds().isSet() ? query.bounds()->unionWith( queryExtent.bounds() ) :
            queryExtent.bounds();

        // query the feature source:
        osg::ref_ptr<FeatureCursor> cursor = _features->createFeatureCursor( localQuery );

        // now copy the resulting feature set into a list, converting the data
        // types along the way if a geometry override is in place:
        FeatureList cellFeatures;
        while( cursor.valid() && cursor->hasMore() )
        {
            Feature* feature = cursor->nextFeature();
            Geometry* geom = feature->getGeometry();
            if ( geom )
            {
                // apply a type override if requested:
                if (_options.geometryTypeOverride().isSet() &&
                    _options.geometryTypeOverride() != geom->getComponentType() )
                {
                    geom = geom->cloneAs( _options.geometryTypeOverride().value() );
                    if ( geom )
                        feature->setGeometry( geom );
                }
            }
            if ( geom )
            {
                cellFeatures.push_back( feature );
            }
        }

        //OE_NOTICE
        //    << "Rendering "
        //    << cellFeatures.size()
        //    << " features in ("
        //    << queryExtent.toString() << ")"
        //    << std::endl;

        return renderFeaturesForStyle( style, cellFeatures, data, imageExtent, out_image );
    }
    else
    {
        return false;
    }
}
osg::Group*
FeatureModelGraph::build( const FeatureLevel& level, const GeoExtent& extent, const TileKey* key )
{
    osg::ref_ptr<osg::Group> group = new osg::Group();

    // form the baseline query, which does a spatial query based on the working extent.
    Query query;
    if ( extent.isValid() )
        query.bounds() = extent.bounds();

    // add a tile key to the query if there is one, to support TFS-style queries
    if ( key )
        query.tileKey() = *key;

    // now, go through any level-based selectors.
    const StyleSelectorVector& levelSelectors = level.selectors();
    
    // if there are none, just build once with the default style and query.
    if ( levelSelectors.size() == 0 )
    {
        osg::Node* node = build( Style(), query, extent );
        if ( node )
            group->addChild( node );
    }

    else
    {
        for( StyleSelectorVector::const_iterator i = levelSelectors.begin(); i != levelSelectors.end(); ++i )
        {
            const StyleSelector& selector = *i;

            // fetch the selector's style:
            Style selectorStyle;
            _styles.getStyle( selector.getSelectedStyleName(), selectorStyle );

            // combine the selector's query, if it has one:
            Query selectorQuery = 
                selector.query().isSet() ? query.combineWith( *selector.query() ) : query;

            osg::Node* node = build( selectorStyle, selectorQuery, extent );
            if ( node )
                group->addChild( node );
        }
    }

    if ( group->getNumChildren() > 0 )
    {
        // account for a min-range here.
        if ( level.minRange() > 0.0f )
        {
            osg::LOD* lod = new osg::LOD();
            lod->addChild( group.get(), level.minRange(), FLT_MAX );
            group = lod;
        }

        if ( _session->getMapInfo().isGeocentric() && _options.clusterCulling() == true )
        {
            const GeoExtent& ccExtent = extent.isValid() ? extent : _source->getFeatureProfile()->getExtent();
            if ( ccExtent.isValid() )
            {
                // if the extent is more than 90 degrees, bail
                GeoExtent geodeticExtent = ccExtent.transform( ccExtent.getSRS()->getGeographicSRS() );
                if ( geodeticExtent.width() < 90.0 && geodeticExtent.height() < 90.0 )
                {
                    // get the geocentric tile center:
                    osg::Vec3d tileCenter;
                    ccExtent.getCentroid( tileCenter.x(), tileCenter.y() );
                    osg::Vec3d centerECEF;
                    ccExtent.getSRS()->transformToECEF( tileCenter, centerECEF );

                    osg::NodeCallback* ccc = ClusterCullerFactory::create( group.get(), centerECEF );
                    if ( ccc )
                        group->addCullCallback( ccc );
                }
            }
        }

        return group.release();
    }

    else
    {
        return 0L;
    }
}