osg::Node* GeomCompiler::compile(FeatureCursor* cursor, const Style& style, const FilterContext& context) { if ( !context.profile() ) { OE_WARN << LC << "Valid feature profile required" << std::endl; return 0L; } if ( style.empty() ) { OE_WARN << LC << "Non-empty style required" << std::endl; return 0L; } osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // start by making a working copy of the feature set FeatureList workingSet; cursor->fill( workingSet ); // create a filter context that will track feature data through the process FilterContext cx = context; if ( !cx.extent().isSet() ) cx.extent() = cx.profile()->getExtent(); // only localize coordinates if the map if geocentric AND the extent is // less than 180 degrees. const MapInfo& mi = cx.getSession()->getMapInfo(); GeoExtent workingExtent = cx.extent()->transform( cx.profile()->getSRS()->getGeographicSRS() ); bool localize = mi.isGeocentric() && workingExtent.width() < 180.0; // go through the Style and figure out which filters to use. const MarkerSymbol* marker = style.get<MarkerSymbol>(); const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); // transform the features into the map profile TransformFilter xform( mi.getProfile()->getSRS(), mi.isGeocentric() ); xform.setLocalizeCoordinates( localize ); if ( altitude && altitude->verticalOffset().isSet() ) xform.setMatrix( osg::Matrixd::translate(0, 0, *altitude->verticalOffset()) ); cx = xform.push( workingSet, cx ); bool clampRequired = altitude && altitude->clamping() != AltitudeSymbol::CLAMP_NONE; // model substitution if ( marker ) { if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); cx = scatter.push( workingSet, cx ); } if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); cx = clamp.push( workingSet, cx ); clampRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); if ( marker->scale().isSet() ) sub.setModelMatrix( osg::Matrixd::scale( *marker->scale() ) ); cx = sub.push( workingSet, cx ); osg::Node* node = sub.getNode(); if ( node ) resultGroup->addChild( node ); } // extruded geometry if ( extrusion && ( line || polygon ) ) { if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); if ( extrusion->heightReference() == ExtrusionSymbol::HEIGHT_REFERENCE_MSL ) clamp.setMaxZAttributeName( "__max_z"); cx = clamp.push( workingSet, cx ); clampRequired = false; } ExtrudeGeometryFilter extrude; if ( extrusion ) { if ( extrusion->height().isSet() ) extrude.setExtrusionHeight( *extrusion->height() ); if ( extrusion->heightExpression().isSet() ) extrude.setExtrusionExpr( *extrusion->heightExpression() ); //extrude.setHeightReferenceFrame( *extrusion->heightReference() ); if ( extrusion->heightReference() == ExtrusionSymbol::HEIGHT_REFERENCE_MSL ) extrude.setHeightOffsetExpression( NumericExpression("[__max_z]") ); extrude.setFlatten( *extrusion->flatten() ); } if ( polygon ) { extrude.setColor( polygon->fill()->color() ); } osg::Node* node = extrude.push( workingSet, cx ); if ( node ) resultGroup->addChild( node ); } // simple geometry else if ( point || line || polygon ) { if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); cx = clamp.push( workingSet, cx ); clampRequired = false; } BuildGeometryFilter filter( style ); if ( _options.maxGranularity().isSet() ) filter.maxGranularity() = *_options.maxGranularity(); if ( _options.mergeGeometry().isSet() ) filter.mergeGeometry() = *_options.mergeGeometry(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); cx = filter.push( workingSet, cx ); osg::Node* node = filter.getNode(); if ( node ) resultGroup->addChild( node ); } if ( text ) { if ( clampRequired ) { ClampFilter clamp; clamp.setIgnoreZ( altitude->clamping() == AltitudeSymbol::CLAMP_TO_TERRAIN ); cx = clamp.push( workingSet, cx ); clampRequired = false; } BuildTextFilter filter( style ); cx = filter.push( workingSet, cx ); osg::Node* node = filter.takeNode(); if ( node ) resultGroup->addChild( node ); } //else // insufficient symbology //{ // OE_WARN << LC << "Insufficient symbology; no geometry created" << std::endl; //} // install the localization transform if necessary. if ( cx.hasReferenceFrame() ) { osg::MatrixTransform* delocalizer = new osg::MatrixTransform( cx.inverseReferenceFrame() ); delocalizer->addChild( resultGroup.get() ); resultGroup = delocalizer; } resultGroup->getOrCreateStateSet()->setMode( GL_BLEND, 1 ); //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); return resultGroup.release(); }
// reads a chunk of features into a memory cache; do this for performance // and to avoid needing the OGR Mutex every time void FeatureCursorOGR::readChunk() { if ( !_resultSetHandle ) return; FeatureList preProcessList; OGR_SCOPED_LOCK; if ( _nextHandleToQueue ) { Feature* f = createFeature( _nextHandleToQueue ); if ( f ) { _queue.push( f ); if ( _filters.size() > 0 ) preProcessList.push_back( f ); } OGR_F_Destroy( _nextHandleToQueue ); _nextHandleToQueue = 0L; } int handlesToQueue = _chunkSize - _queue.size(); for( int i=0; i<handlesToQueue; i++ ) { OGRFeatureH handle = OGR_L_GetNextFeature( _resultSetHandle ); if ( handle ) { Feature* f = createFeature( handle ); if ( f ) { _queue.push( f ); if ( _filters.size() > 0 ) preProcessList.push_back( f ); } OGR_F_Destroy( handle ); } else break; } // preprocess the features using the filter list: if ( preProcessList.size() > 0 ) { FilterContext cx; cx.profile() = _profile.get(); for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i ) { FeatureFilter* filter = i->get(); cx = filter->push( preProcessList, cx ); } } // read one more for "more" detection: _nextHandleToQueue = OGR_L_GetNextFeature( _resultSetHandle ); //OE_NOTICE << "read " << _queue.size() << " features ... " << std::endl; }
void AltitudeFilter::pushAndClamp( FeatureList& features, FilterContext& cx ) { const Session* session = cx.getSession(); // the map against which we'll be doing elevation clamping //MapFrame mapf = session->createMapFrame( Map::ELEVATION_LAYERS ); MapFrame mapf = session->createMapFrame( Map::TERRAIN_LAYERS ); const SpatialReference* mapSRS = mapf.getProfile()->getSRS(); osg::ref_ptr<const SpatialReference> featureSRS = cx.profile()->getSRS(); // establish an elevation query interface based on the features' SRS. ElevationQuery eq( mapf ); NumericExpression scaleExpr; if ( _altitude->verticalScale().isSet() ) scaleExpr = *_altitude->verticalScale(); NumericExpression offsetExpr; if ( _altitude->verticalOffset().isSet() ) offsetExpr = *_altitude->verticalOffset(); // whether to record the min/max height-above-terrain values. bool collectHATs = _altitude->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN || _altitude->clamping() == AltitudeSymbol::CLAMP_ABSOLUTE; // whether to clamp every vertex (or just the centroid) bool perVertex = _altitude->binding() == AltitudeSymbol::BINDING_VERTEX; // whether the SRS's have a compatible vertical datum. bool vertEquiv = featureSRS->isVertEquivalentTo( mapSRS ); for( FeatureList::iterator i = features.begin(); i != features.end(); ++i ) { Feature* feature = i->get(); double maxTerrainZ = -DBL_MAX; double minTerrainZ = DBL_MAX; double minHAT = DBL_MAX; double maxHAT = -DBL_MAX; double scaleZ = 1.0; if ( _altitude.valid() && _altitude->verticalScale().isSet() ) scaleZ = feature->eval( scaleExpr, &cx ); double offsetZ = 0.0; if ( _altitude.valid() && _altitude->verticalOffset().isSet() ) offsetZ = feature->eval( offsetExpr, &cx ); GeometryIterator gi( feature->getGeometry() ); while( gi.hasMore() ) { Geometry* geom = gi.next(); // Absolute heights in Z. Only need to collect the HATs; the geometry // remains unchanged. if ( _altitude->clamping() == AltitudeSymbol::CLAMP_ABSOLUTE ) { if ( perVertex ) { std::vector<double> elevations; elevations.reserve( geom->size() ); if ( eq.getElevations( geom->asVector(), featureSRS, elevations, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double z = p.z(); if ( !vertEquiv ) { osg::Vec3d tempgeo; if ( !featureSRS->transform(p, mapSRS->getGeographicSRS(), tempgeo) ) z = tempgeo.z(); } double hat = z - elevations[i]; if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; if ( elevations[i] > maxTerrainZ ) maxTerrainZ = elevations[i]; if ( elevations[i] < minTerrainZ ) minTerrainZ = elevations[i]; } } } else // per centroid { osgEarth::Bounds bounds = geom->getBounds(); const osg::Vec2d& center = bounds.center2d(); GeoPoint centroid(featureSRS, center.x(), center.y()); double centroidElevation; if ( eq.getElevation( centroid, centroidElevation, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double z = p.z(); if ( !vertEquiv ) { osg::Vec3d tempgeo; if ( !featureSRS->transform(p, mapSRS->getGeographicSRS(), tempgeo) ) z = tempgeo.z(); } double hat = z - centroidElevation; if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; } if ( centroidElevation > maxTerrainZ ) maxTerrainZ = centroidElevation; if ( centroidElevation < minTerrainZ ) minTerrainZ = centroidElevation; } } } // Heights-above-ground in Z. Need to resolve this to an absolute number // and record HATs along the way. else if ( _altitude->clamping() == AltitudeSymbol::CLAMP_RELATIVE_TO_TERRAIN ) { osg::ref_ptr<const SpatialReference> featureSRSwithMapVertDatum = !vertEquiv ? SpatialReference::create(featureSRS->getHorizInitString(), mapSRS->getVertInitString()) : 0L; if ( perVertex ) { std::vector<double> elevations; elevations.reserve( geom->size() ); if ( eq.getElevations( geom->asVector(), featureSRS, elevations, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double hat = p.z(); p.z() = elevations[i] + p.z(); // if necessary, convert the Z value (which is now in the map's SRS) back to // the feature's SRS. if ( !vertEquiv ) { featureSRSwithMapVertDatum->transform(p, featureSRS, p); } if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; if ( elevations[i] > maxTerrainZ ) maxTerrainZ = elevations[i]; if ( elevations[i] < minTerrainZ ) minTerrainZ = elevations[i]; } } } else // per-centroid { osgEarth::Bounds bounds = geom->getBounds(); const osg::Vec2d& center = bounds.center2d(); GeoPoint centroid(featureSRS, center.x(), center.y()); double centroidElevation; if ( eq.getElevation( centroid, centroidElevation, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() *= scaleZ; p.z() += offsetZ; double hat = p.z(); p.z() = centroidElevation + p.z(); // if necessary, convert the Z value (which is now in the map's SRS) back to // the feature's SRS. if ( !vertEquiv ) { featureSRSwithMapVertDatum->transform(p, featureSRS, p); } if ( hat > maxHAT ) maxHAT = hat; if ( hat < minHAT ) minHAT = hat; } if ( centroidElevation > maxTerrainZ ) maxTerrainZ = centroidElevation; if ( centroidElevation < minTerrainZ ) minTerrainZ = centroidElevation; } } } // Clamp - replace the geometry's Z with the terrain height. else // CLAMP_TO_TERRAIN { if ( perVertex ) { eq.getElevations( geom->asVector(), featureSRS, true, _maxRes ); // if necessary, transform the Z values (which are now in the map SRS) back // into the feature's SRS. if ( !vertEquiv ) { osg::ref_ptr<const SpatialReference> featureSRSwithMapVertDatum = SpatialReference::create(featureSRS->getHorizInitString(), mapSRS->getVertInitString()); osg::Vec3d tempgeo; for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; featureSRSwithMapVertDatum->transform(p, featureSRS, p); } } } else // per-centroid { osgEarth::Bounds bounds = geom->getBounds(); const osg::Vec2d& center = bounds.center2d(); GeoPoint centroid(featureSRS, center.x(), center.y()); double centroidElevation; osg::ref_ptr<const SpatialReference> featureSRSWithMapVertDatum; if ( !vertEquiv ) featureSRSWithMapVertDatum = SpatialReference::create(featureSRS->getHorizInitString(), mapSRS->getVertInitString()); if ( eq.getElevation( centroid, centroidElevation, _maxRes ) ) { for( unsigned i=0; i<geom->size(); ++i ) { osg::Vec3d& p = (*geom)[i]; p.z() = centroidElevation; if ( !vertEquiv ) { featureSRSWithMapVertDatum->transform(p, featureSRS, p); } } } } } if ( !collectHATs ) { for( Geometry::iterator i = geom->begin(); i != geom->end(); ++i ) { i->z() *= scaleZ; i->z() += offsetZ; } } } if ( minHAT != DBL_MAX ) { feature->set( "__min_hat", minHAT ); feature->set( "__max_hat", maxHAT ); } if ( minTerrainZ != DBL_MAX ) { feature->set( "__min_terrain_z", minTerrainZ ); feature->set( "__max_terrain_z", maxTerrainZ ); } } }
void ScatterFilter::polyScatter(const Geometry* input, const SpatialReference* inputSRS, const FilterContext& context, PointSet* output ) { Bounds bounds; double areaSqKm = 0.0; ConstGeometryIterator iter( input, false ); while( iter.hasMore() ) { const Polygon* polygon = dynamic_cast<const Polygon*>( iter.next() ); if ( !polygon ) continue; if ( /*context.isGeocentric() ||*/ context.profile()->getSRS()->isGeographic() ) { bounds = polygon->getBounds(); double avglat = bounds.yMin() + 0.5*bounds.height(); double h = bounds.height() * 111.32; double w = bounds.width() * 111.32 * sin( 1.57079633 + osg::DegreesToRadians(avglat) ); areaSqKm = w * h; } else if ( context.profile()->getSRS()->isProjected() ) { bounds = polygon->getBounds(); areaSqKm = (0.001*bounds.width()) * (0.001*bounds.height()); } double zMin = 0.0; unsigned numInstancesInBoundingRect = (unsigned)(areaSqKm * (double)osg::clampAbove( 0.1f, _density )); if ( numInstancesInBoundingRect == 0 ) continue; if ( _random ) { // Random scattering. Note, we try to place as many instances as would // fit in the bounding rectangle; The real placed number will be less since // we only place models inside the actual polygon. But the density will // be correct. for( unsigned j=0; j<numInstancesInBoundingRect; ++j ) { double x = bounds.xMin() + _prng.next() * bounds.width(); double y = bounds.yMin() + _prng.next() * bounds.height(); bool include = true; if ( include && polygon->contains2D( x, y ) ) output->push_back( osg::Vec3d(x, y, zMin) ); } } else { // regular interval scattering: double numInst1D = sqrt((double)numInstancesInBoundingRect); double ar = bounds.width() / bounds.height(); unsigned cols = (unsigned)( numInst1D * ar ); unsigned rows = (unsigned)( numInst1D / ar ); double colInterval = bounds.width() / (double)(cols-1); double rowInterval = bounds.height() / (double)(rows-1); double interval = 0.5*(colInterval+rowInterval); for( double cy=bounds.yMin(); cy<=bounds.yMax(); cy += interval ) { for( double cx = bounds.xMin(); cx <= bounds.xMax(); cx += interval ) { bool include = true; if ( include && polygon->contains2D( cx, cy ) ) output->push_back( osg::Vec3d(cx, cy, zMin) ); } } } } }
//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; }
bool SubstituteModelFilter::process(const FeatureList& features, const InstanceSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { // Establish SRS information: bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); const SpatialReference* targetSRS = context.getSession()->getMapInfo().getSRS(); // first, go through the features and build the model cache. Apply the model matrix' scale // factor to any AutoTransforms directly (cloning them as necessary) std::map< std::pair<URI, float>, osg::ref_ptr<osg::Node> > uniqueModels; // URI cache speeds up URI creation since it can be slow. osgEarth::fast_map<std::string, URI> uriCache; // keep track of failed URIs so we don't waste time or warning messages on them std::set< URI > missing; StringExpression uriEx = *symbol->url(); NumericExpression scaleEx = *symbol->scale(); const ModelSymbol* modelSymbol = dynamic_cast<const ModelSymbol*>(symbol); const IconSymbol* iconSymbol = dynamic_cast<const IconSymbol*> (symbol); NumericExpression headingEx; if ( modelSymbol ) headingEx = *modelSymbol->heading(); for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // Run a feature pre-processing script. if ( symbol->script().isSet() ) { StringExpression scriptExpr(symbol->script().get()); input->eval( scriptExpr, &context ); } // evaluate the instance URI expression: const std::string& st = input->eval(uriEx, &context); URI& instanceURI = uriCache[st]; if(instanceURI.empty()) // Create a map, to reuse URI's, since they take a long time to create { instanceURI = URI( st, uriEx.uriContext() ); } // find the corresponding marker in the cache osg::ref_ptr<InstanceResource> instance; if ( !findResource(instanceURI, symbol, context, missing, instance) ) continue; // evalute the scale expression (if there is one) float scale = 1.0f; osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); if ( scale == 0.0 ) scale = 1.0; if ( scale != 1.0 ) _normalScalingRequired = true; scaleMatrix = osg::Matrix::scale( scale, scale, scale ); } osg::Matrixd rotationMatrix; if ( modelSymbol && modelSymbol->heading().isSet() ) { float heading = input->eval(headingEx, &context); rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) ); } // how that we have a marker source, create a node for it std::pair<URI,float> key( instanceURI, iconSymbol? scale : 1.0f );//use 1.0 for models, since we don't want unique models based on scaling // cache nodes per instance. osg::ref_ptr<osg::Node>& model = uniqueModels[key]; if ( !model.valid() ) { // Always clone the cached instance so we're not processing data that's // already in the scene graph. -gw context.resourceCache()->cloneOrCreateInstanceNode(instance.get(), model); // if icon decluttering is off, install an AutoTransform. if ( iconSymbol ) { if ( iconSymbol->declutter() == true ) { Decluttering::setEnabled( model->getOrCreateStateSet(), true ); } else if ( dynamic_cast<osg::AutoTransform*>(model.get()) == 0L ) { osg::AutoTransform* at = new osg::AutoTransform(); at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN ); at->setAutoScaleToScreen( true ); at->addChild( model ); model = at; } } } if ( model.valid() ) { GeometryIterator gi( input->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); // if necessary, transform the points to the target SRS: if ( !makeECEF && !targetSRS->isEquivalentTo(context.profile()->getSRS()) ) { context.profile()->getSRS()->transform( geom->asVector(), targetSRS ); } for( unsigned i=0; i<geom->size(); ++i ) { osg::Matrixd mat; // need to recalcluate expression-based data per-point, not just per-feature! if ( symbol->scale().isSet() ) { scale = input->eval(scaleEx, &context); if ( scale == 0.0 ) scale = 1.0; if ( scale != 1.0 ) _normalScalingRequired = true; scaleMatrix = osg::Matrix::scale( scale, scale, scale ); } if ( modelSymbol->heading().isSet() ) { float heading = input->eval(headingEx, &context); rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) ); } osg::Vec3d point = (*geom)[i]; if ( makeECEF ) { // the "rotation" element lets us re-orient the instance to ensure it's pointing up. We // could take a shortcut and just use the current extent's local2world matrix for this, // but if the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, context.profile()->getSRS(), point, targetSRS, rotation ); mat = rotationMatrix * rotation * scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } else { mat = rotationMatrix * scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->setDataVariance( osg::Object::STATIC ); xform->addChild( model.get() ); attachPoint->addChild( xform ); if ( context.featureIndex() ) // && !_useDrawInstanced ) { context.featureIndex()->tagNode( xform, input ); } // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr, &context); if ( !name.empty() ) xform->setName( name ); } } } } } if ( iconSymbol ) { // activate decluttering for icons if requested if ( iconSymbol->declutter() == true ) { Decluttering::setEnabled( attachPoint->getOrCreateStateSet(), true ); } // activate horizon culling if we are in geocentric space if ( context.getSession() && context.getSession()->getMapInfo().isGeocentric() ) { //TODO: re-evaluate this; use Horizon? HorizonCullingProgram::install( attachPoint->getOrCreateStateSet() ); } } // active DrawInstanced if required: if ( _useDrawInstanced && Registry::capabilities().supportsDrawInstanced() ) { DrawInstanced::convertGraphToUseDrawInstanced( attachPoint ); // install a shader program to render draw-instanced. DrawInstanced::install( attachPoint->getOrCreateStateSet() ); } return true; }
osg::Node* GeometryCompiler::compile(FeatureList& workingSet, const Style& style, const FilterContext& context) { #ifdef PROFILING osg::Timer_t p_start = osg::Timer::instance()->tick(); unsigned p_features = workingSet.size(); #endif osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // create a filter context that will track feature data through the process FilterContext sharedCX = context; if ( !sharedCX.extent().isSet() && sharedCX.profile() ) { sharedCX.extent() = sharedCX.profile()->getExtent(); } // ref_ptr's to hold defaults in case we need them. osg::ref_ptr<PointSymbol> defaultPoint; osg::ref_ptr<LineSymbol> defaultLine; osg::ref_ptr<PolygonSymbol> defaultPolygon; // go through the Style and figure out which filters to use. const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); const MarkerSymbol* marker = style.get<MarkerSymbol>(); // to be deprecated const IconSymbol* icon = style.get<IconSymbol>(); const ModelSymbol* model = style.get<ModelSymbol>(); // check whether we need tessellation: if ( line && line->tessellation().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setNumPartitions( *line->tessellation() ); sharedCX = filter.push( workingSet, sharedCX ); } // if the style was empty, use some defaults based on the geometry type of the // first feature. if ( !point && !line && !polygon && !marker && !extrusion && !text && !model && !icon && workingSet.size() > 0 ) { Feature* first = workingSet.begin()->get(); Geometry* geom = first->getGeometry(); if ( geom ) { switch( geom->getComponentType() ) { case Geometry::TYPE_LINESTRING: case Geometry::TYPE_RING: defaultLine = new LineSymbol(); line = defaultLine.get(); break; case Geometry::TYPE_POINTSET: defaultPoint = new PointSymbol(); point = defaultPoint.get(); break; case Geometry::TYPE_POLYGON: defaultPolygon = new PolygonSymbol(); polygon = defaultPolygon.get(); break; case Geometry::TYPE_MULTI: case Geometry::TYPE_UNKNOWN: break; } } } // resample the geometry if necessary: if (_options.resampleMode().isSet()) { ResampleFilter resample; resample.resampleMode() = *_options.resampleMode(); if (_options.resampleMaxLength().isSet()) { resample.maxLength() = *_options.resampleMaxLength(); } sharedCX = resample.push( workingSet, sharedCX ); } // check whether we need to do elevation clamping: bool altRequired = _options.ignoreAltitudeSymbol() != true && altitude && ( altitude->clamping() != AltitudeSymbol::CLAMP_NONE || altitude->verticalOffset().isSet() || altitude->verticalScale().isSet() || altitude->script().isSet() ); // marker substitution -- to be deprecated in favor of model/icon if ( marker ) { // use a separate filter context since we'll be munging the data FilterContext markerCX = sharedCX; if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); markerCX = scatter.push( workingSet, markerCX ); } else if ( marker->placement() == MarkerSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; centroid.push( workingSet, markerCX ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); markerCX = clamp.push( workingSet, markerCX ); // don't set this; we changed the input data. //altRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); sub.setUseDrawInstanced( *_options.instancing() ); if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, markerCX ); if ( node ) { resultGroup->addChild( node ); } } // instance substitution (replaces marker) else if ( model ) { const InstanceSymbol* instance = model ? (const InstanceSymbol*)model : (const InstanceSymbol*)icon; // use a separate filter context since we'll be munging the data FilterContext localCX = sharedCX; if ( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM || instance->placement() == InstanceSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *instance->density() ); scatter.setRandom( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *instance->randomSeed() ); localCX = scatter.push( workingSet, localCX ); } else if ( instance->placement() == InstanceSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; centroid.push( workingSet, localCX ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); localCX = clamp.push( workingSet, localCX ); } SubstituteModelFilter sub( style ); // activate clustering sub.setClustering( *_options.clustering() ); // activate draw-instancing sub.setUseDrawInstanced( *_options.instancing() ); // activate feature naming if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, localCX ); if ( node ) { resultGroup->addChild( node ); // enable auto scaling on the group? if ( model && model->autoScale() == true ) { resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN ); } } } // extruded geometry if ( extrusion ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } ExtrudeGeometryFilter extrude; extrude.setStyle( style ); // Activate texture arrays if the GPU supports them and if the user // hasn't disabled them. extrude.useTextureArrays() = Registry::capabilities().supportsTextureArrays() && _options.useTextureArrays() == true; // apply per-feature naming if requested. if ( _options.featureName().isSet() ) extrude.setFeatureNameExpr( *_options.featureName() ); if ( _options.useVertexBufferObjects().isSet()) extrude.useVertexBufferObjects() = *_options.useVertexBufferObjects(); osg::Node* node = extrude.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } // simple geometry else if ( point || line || polygon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } BuildGeometryFilter filter( style ); if ( _options.maxGranularity().isSet() ) filter.maxGranularity() = *_options.maxGranularity(); if ( _options.geoInterp().isSet() ) filter.geoInterp() = *_options.geoInterp(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } if ( text || icon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } BuildTextFilter filter( style ); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } if (Registry::capabilities().supportsGLSL()) { if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE ) { // no ss cache because we will optimize later. Registry::shaderGenerator().run( resultGroup.get(), "osgEarth.GeomCompiler" ); } else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE ) { resultGroup->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE ); } } // Optimize stateset sharing. if ( _options.optimizeStateSharing() == true ) { // Common state set cache? osg::ref_ptr<StateSetCache> sscache; if ( sharedCX.getSession() ) { // with a shared cache, don't combine statesets. They may be // in the live graph sscache = sharedCX.getSession()->getStateSetCache(); sscache->consolidateStateAttributes( resultGroup.get() ); } else { // isolated: perform full optimization sscache = new StateSetCache(); sscache->optimize( resultGroup.get() ); } } //test: dump the tile to disk //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); #ifdef PROFILING osg::Timer_t p_end = osg::Timer::instance()->tick(); OE_INFO << LC << "features = " << p_features << ", time = " << osg::Timer::instance()->delta_s(p_start, p_end) << " s." << std::endl; #endif return resultGroup.release(); }
osg::Node* Graticule::createGridLevel( unsigned int levelNum ) const { if ( !_map->isGeocentric() ) { OE_WARN << "Graticule: only supports geocentric maps" << std::endl; return 0L; } Graticule::Level level; if ( !getLevel( levelNum, level ) ) return 0L; OE_DEBUG << "Graticule: creating grid level " << levelNum << std::endl; osg::Group* group = new osg::Group(); const Profile* mapProfile = _map->getProfile(); const GeoExtent& pex = mapProfile->getExtent(); double tw = pex.width() / (double)level._cellsX; double th = pex.height() / (double)level._cellsY; for( unsigned int x=0; x<level._cellsX; ++x ) { for( unsigned int y=0; y<level._cellsY; ++y ) { GeoExtent tex( mapProfile->getSRS(), pex.xMin() + tw * (double)x, pex.yMin() + th * (double)y, pex.xMin() + tw * (double)(x+1), pex.yMin() + th * (double)(y+1) ); Geometry* geom = createCellGeometry( tex, level._lineWidth, pex, _map->isGeocentric() ); Feature* feature = new Feature(); feature->setGeometry( geom ); FeatureList features; features.push_back( feature ); FilterContext cx; cx.profile() = new FeatureProfile( tex ); cx.isGeocentric() = _map->isGeocentric(); if ( _map->isGeocentric() ) { // We need to make sure that on a round globe, the points are sampled such that // long segments follow the curvature of the earth. ResampleFilter resample; resample.maxLength() = tex.width() / 10.0; cx = resample.push( features, cx ); } TransformFilter xform( mapProfile->getSRS() ); xform.setMakeGeocentric( _map->isGeocentric() ); xform.setLocalizeCoordinates( true ); cx = xform.push( features, cx ); osg::ref_ptr<osg::Node> output; BuildGeometryFilter bg; bg.setStyle( _lineStyle ); cx = bg.push( features, cx ); output = bg.getNode(); if ( cx.isGeocentric() ) { // get the geocentric control point: double cplon, cplat, cpx, cpy, cpz; tex.getCentroid( cplon, cplat ); tex.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ( osg::DegreesToRadians( cplat ), osg::DegreesToRadians( cplon ), 0.0, cpx, cpy, cpz ); osg::Vec3 controlPoint(cpx, cpy, cpz); // get the horizon point: tex.getSRS()->getEllipsoid()->convertLatLongHeightToXYZ( osg::DegreesToRadians( tex.yMin() ), osg::DegreesToRadians( tex.xMin() ), 0.0, cpx, cpy, cpz ); osg::Vec3 horizonPoint(cpx, cpy, cpz); // the deviation is the dot product of the control vector and the vector from the // control point to the horizon point. osg::Vec3 controlPointNorm = controlPoint; controlPointNorm.normalize(); osg::Vec3 horizonVecNorm = horizonPoint - controlPoint; horizonVecNorm.normalize(); float deviation = controlPointNorm * horizonVecNorm; // construct the culling callback using the deviation. osg::ClusterCullingCallback* ccc = new osg::ClusterCullingCallback(); ccc->set( controlPoint, controlPointNorm, deviation, (controlPoint-horizonPoint).length() ); // need a new group, because never put a cluster culler on a matrixtransform (doesn't work) osg::Group* me = new osg::Group(); me->setCullCallback( ccc ); me->addChild( output.get() ); output = me; } group->addChild( output.get() ); } } // organize it for better culling osgUtil::Optimizer opt; opt.optimize( group, osgUtil::Optimizer::SPATIALIZE_GROUPS ); osg::Node* result = group; if ( levelNum < getNumLevels() ) { Graticule::Level nextLevel; if ( getLevel( levelNum+1, nextLevel ) ) { osg::PagedLOD* plod = new osg::PagedLOD(); plod->addChild( group, nextLevel._maxRange, level._maxRange ); std::stringstream buf; buf << levelNum+1 << "_" << getID() << "." << GRID_MARKER << "." << GRATICLE_EXTENSION; std::string bufStr = buf.str(); plod->setFileName( 1, bufStr ); plod->setRange( 1, 0, nextLevel._maxRange ); result = plod; } } return result; }
FilterContext push(FeatureList& input, FilterContext& context) { if (_featureSource.valid()) { // Get any features that intersect this query. FeatureList boundaries; getFeatures(context.extent().get(), boundaries ); // The list of output features FeatureList output; if (boundaries.empty()) { // No intersecting features. If contains is false, then just the output to the input. if (contains() == false) { output = input; } } else { // Transform the boundaries into the coordinate system of the features for (FeatureList::iterator itr = boundaries.begin(); itr != boundaries.end(); ++itr) { itr->get()->transform( context.profile()->getSRS() ); } for(FeatureList::const_iterator f = input.begin(); f != input.end(); ++f) { Feature* feature = f->get(); if ( feature && feature->getGeometry() ) { osg::Vec2d c = feature->getGeometry()->getBounds().center2d(); if ( contains() == true ) { // coarsest: if (_featureSource->getFeatureProfile()->getExtent().contains(GeoPoint(feature->getSRS(), c.x(), c.y()))) { for (FeatureList::iterator itr = boundaries.begin(); itr != boundaries.end(); ++itr) { Ring* ring = dynamic_cast< Ring*>(itr->get()->getGeometry()); if (ring && ring->contains2D(c.x(), c.y())) { output.push_back( feature ); } } } } else { bool contained = false; // coarsest: if (_featureSource->getFeatureProfile()->getExtent().contains(GeoPoint(feature->getSRS(), c.x(), c.y()))) { for (FeatureList::iterator itr = boundaries.begin(); itr != boundaries.end(); ++itr) { Ring* ring = dynamic_cast< Ring*>(itr->get()->getGeometry()); if (ring && ring->contains2D(c.x(), c.y())) { contained = true; break; } } } if ( !contained ) { output.push_back( feature ); } } } } } OE_INFO << LC << "Allowed " << output.size() << " out of " << input.size() << " features\n"; input = output; } return context; }
FeatureCursor* createFeatureCursor( const Symbology::Query& query ) { FeatureCursor* result = 0L; std::string url = createURL( query ); if (url.empty()) return 0; // check the blacklist: if ( Registry::instance()->isBlacklisted(url) ) return 0L; OE_DEBUG << LC << url << std::endl; URI uri(url); // read the data: ReadResult r = uri.readString( _dbOptions.get() ); const std::string& buffer = r.getString(); const Config& meta = r.metadata(); bool dataOK = false; FeatureList features; if ( !buffer.empty() ) { // Get the mime-type from the metadata record if possible std::string mimeType = r.metadata().value( IOMetadata::CONTENT_TYPE ); //If the mimetype is empty then try to set it from the format specification if (mimeType.empty()) { if (_options.format().value() == "json") mimeType = "json"; else if (_options.format().value().compare("gml") == 0) mimeType = "text/xml"; } dataOK = getFeatures( buffer, mimeType, features ); } if ( dataOK ) { OE_DEBUG << LC << "Read " << features.size() << " features" << std::endl; } //If we have any filters, process them here before the cursor is created if (!_options.filters().empty()) { // preprocess the features using the filter list: if ( features.size() > 0 ) { FilterContext cx; cx.profile() = getFeatureProfile(); for( FeatureFilterList::const_iterator i = _options.filters().begin(); i != _options.filters().end(); ++i ) { FeatureFilter* filter = i->get(); cx = filter->push( features, cx ); } } } //result = new FeatureListCursor(features); result = dataOK ? new FeatureListCursor( features ) : 0L; if ( !result ) Registry::instance()->blacklist( url ); return result; }
//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; }
osg::Node* Graticule::createGridLevel( unsigned int levelNum ) const { if ( !_map->isGeocentric() ) { OE_WARN << "Graticule: only supports geocentric maps" << std::endl; return 0L; } Graticule::Level level; if ( !getLevel( levelNum, level ) ) return 0L; OE_DEBUG << "Graticule: creating grid level " << levelNum << std::endl; osg::Group* group = new osg::Group(); const Profile* mapProfile = _map->getProfile(); const GeoExtent& pex = mapProfile->getExtent(); double tw = pex.width() / (double)level._cellsX; double th = pex.height() / (double)level._cellsY; for( unsigned int x=0; x<level._cellsX; ++x ) { for( unsigned int y=0; y<level._cellsY; ++y ) { GeoExtent tex( mapProfile->getSRS(), pex.xMin() + tw * (double)x, pex.yMin() + th * (double)y, pex.xMin() + tw * (double)(x+1), pex.yMin() + th * (double)(y+1) ); double ox = level._lineWidth; double oy = level._lineWidth; Geometry* geom = createCellGeometry( tex, level._lineWidth, pex, _map->isGeocentric() ); Feature* feature = new Feature(); feature->setGeometry( geom ); FeatureList features; features.push_back( feature ); FilterContext cx; cx.profile() = new FeatureProfile( tex ); cx.isGeocentric() = _map->isGeocentric(); if ( _map->isGeocentric() ) { // We need to make sure that on a round globe, the points are sampled such that // long segments follow the curvature of the earth. ResampleFilter resample; resample.maxLength() = tex.width() / 10.0; resample.perturbationThreshold() = level._lineWidth/1000.0; cx = resample.push( features, cx ); } TransformFilter xform( mapProfile->getSRS() ); xform.setMakeGeocentric( _map->isGeocentric() ); cx = xform.push( features, cx ); Bounds bounds = feature->getGeometry()->getBounds(); double exDist = bounds.radius()/2.0; osg::Node* cellVolume = createVolume( feature->getGeometry(), -exDist, exDist*2, cx ); osg::Node* child = cellVolume; if ( cx.hasReferenceFrame() ) { osg::MatrixTransform* xform = new osg::MatrixTransform( cx.inverseReferenceFrame() ); xform->addChild( child ); // the transform matrix here does NOT include a rotation, so we need to get the normal // for the cull plane callback. osg::Vec3d normal = xform->getBound().center(); xform->setCullCallback( new CullPlaneCallback( normal ) ); child = xform; } group->addChild( child ); } } // organize it for better culling osgUtil::Optimizer opt; opt.optimize( group, osgUtil::Optimizer::SPATIALIZE_GROUPS ); osg::Node* result = group; if ( levelNum+1 < getNumLevels() ) { Graticule::Level nextLevel; if ( getLevel( levelNum+1, nextLevel ) ) { osg::PagedLOD* plod = new osg::PagedLOD(); plod->addChild( group, nextLevel._maxRange, level._maxRange ); std::stringstream buf; buf << levelNum+1 << "_" << getID() << "." << GRID_MARKER << "." << GRATICLE_EXTENSION; std::string bufStr = buf.str(); plod->setFileName( 1, bufStr ); plod->setRange( 1, 0, nextLevel._maxRange ); result = plod; } } return result; }
bool SubstituteModelFilter::process(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); GeometryIterator gi( input->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); for( unsigned i=0; i<geom->size(); ++i ) { osg::Matrixd mat; osg::Vec3d point = (*geom)[i]; if ( makeECEF ) { // the "rotation" element lets us re-orient the instance to ensure it's pointing up. We // could take a shortcut and just use the current extent's local2world matrix for this, // but it the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( context.profile()->getSRS(), point, point, rotation ); mat = rotation * _modelMatrix * osg::Matrixd::translate( point ) * _world2local; } else { mat = _modelMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->setDataVariance( osg::Object::STATIC ); MarkerFactory factory( session); osg::ref_ptr< osg::Node > model = factory.getOrCreateNode( input, symbol ); if (model.get()) { xform->addChild( model.get() ); } attachPoint->addChild( xform ); // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr ); if ( !name.empty() ) xform->setName( name ); } } } } return true; }
FilterContext ScatterFilter::push(FeatureList& features, const FilterContext& context ) { if ( !isSupported() ) { OE_WARN << LC << "support for this filter is not enabled" << std::endl; return context; } // seed the random number generator so the randomness is the same each time // todo: control this seeding based on the feature source name, perhaps? ::srand( _randomSeed ); for( FeatureList::iterator i = features.begin(); i != features.end(); ++i ) { Feature* f = i->get(); Geometry* geom = f->getGeometry(); if ( !geom ) continue; const SpatialReference* geomSRS = context.profile()->getSRS(); // first, undo the localization frame if there is one. context.toWorld( geom ); // convert to geodetic if necessary, and compute the approximate area in sq km if ( context.isGeocentric() ) { GeometryIterator gi( geom ); while( gi.hasMore() ) geomSRS->getGeographicSRS()->transformFromECEF( gi.next(), true ); geomSRS = geomSRS->getGeographicSRS(); } PointSet* points = new PointSet(); if ( geom->getComponentType() == Geometry::TYPE_POLYGON ) { polyScatter( geom, geomSRS, context, points ); } else if ( geom->getComponentType() == Geometry::TYPE_LINESTRING || geom->getComponentType() == Geometry::TYPE_RING ) { lineScatter( geom, geomSRS, context, points ); } else { OE_WARN << LC << "Sorry, don't know how to scatter a PointSet yet" << std::endl; } // convert back to geocentric if necessary. if ( context.isGeocentric() ) context.profile()->getSRS()->getGeographicSRS()->transformToECEF( points, true ); // re-apply the localization frame. context.toLocal( points ); // replace the source geometry with the scattered points. f->setGeometry( points ); } return context; }
virtual osg::Node* createNodeForStyle( const Symbology::Style* style, const FeatureList& features, FeatureSymbolizerContext* context, osg::Node** out_newNode) { // A processing context to use with the filters: FilterContext contextFilter; contextFilter.profile() = context->getModelSource()->getFeatureSource()->getFeatureProfile(); // Transform them into the map's SRS: TransformFilter xform( context->getModelSource()->getMap()->getProfile()->getSRS() ); xform.setMakeGeocentric( context->getModelSource()->getMap()->isGeocentric() ); xform.setLocalizeCoordinates( true ); const FeatureLabelModelOptions* options = dynamic_cast<const FeatureLabelModelOptions*>( context->getModelSource()->getFeatureModelOptions()); FeatureList featureList; for (FeatureList::const_iterator it = features.begin(); it != features.end(); ++it) featureList.push_back(osg::clone((*it).get(),osg::CopyOp::DEEP_COPY_ALL)); xform.setHeightOffset( options->heightOffset().value() ); contextFilter = xform.push( featureList, contextFilter ); //Make some labels osg::ref_ptr<const TextSymbol> textSymbol = style->getSymbol<TextSymbol>(); //Use a default symbol if we have no text symbol if (!textSymbol) textSymbol = new TextSymbol(); osg::Node* labels = NULL; if (textSymbol.valid()) { BuildTextOperator textOperator; labels = textOperator(featureList, textSymbol.get(), contextFilter); } osg::Node* result = labels; // If the context specifies a reference frame, apply it to the resulting model. // Q: should this be here, or should the reference frame matrix be passed to the Symbolizer? // ...probably the latter. if ( contextFilter.hasReferenceFrame() ) { osg::MatrixTransform* delocalizer = new osg::MatrixTransform( contextFilter.inverseReferenceFrame() ); delocalizer->addChild( labels ); result = delocalizer; } // Apply an LOD if required: if ( options->minRange().isSet() || options->maxRange().isSet() ) { osg::LOD* lod = new osg::LOD(); lod->addChild( result, options->minRange().value(), options->maxRange().value() ); result = lod; } // set the output node if necessary: if ( out_newNode ) *out_newNode = result; return result; }