// 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; }
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 ); } } }
/** * 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 ); } }
/** * 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; } }