bool BuildGeometryFilter::pushTextAnnotation( TextAnnotation* anno, const FilterContext& context ) { // find the centroid osg::Vec3d centroid = anno->getGeometry()->getBounds().center(); osgText::Text* t = new osgText::Text(); t->setText( anno->text() ); t->setFont( "fonts/arial.ttf" ); t->setAutoRotateToScreen( true ); t->setCharacterSizeMode( osgText::TextBase::SCREEN_COORDS ); t->setCharacterSize( 32.0f ); //t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT ); //t->setCharacterSize( 300000.0f ); t->setPosition( centroid ); t->setAlignment( osgText::TextBase::CENTER_CENTER ); t->getOrCreateStateSet()->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS), osg::StateAttribute::ON ); t->getOrCreateStateSet()->setRenderBinDetails( 99999, "RenderBin" ); // apply styling as appropriate: osg::Vec4f textColor(1,1,1,1); osg::Vec4f haloColor(0,0,0,1); const TextSymbol* textSymbolizer = getStyle().getSymbol<TextSymbol>(); if ( textSymbolizer ) { textColor = textSymbolizer->fill()->color(); if ( textSymbolizer->halo().isSet() ) { haloColor = textSymbolizer->halo()->color(); } } t->setColor( textColor ); t->setBackdropColor( haloColor ); t->setBackdropType( osgText::Text::OUTLINE ); if ( context.isGeocentric() ) { // install a cluster culler: note that the CCC control point and normal must be // in world coordinates const osg::EllipsoidModel* ellip = context.profile()->getSRS()->getEllipsoid(); osg::Vec3d cp = centroid * context.inverseReferenceFrame(); osg::Vec3d normal = ellip->computeLocalUpVector( cp.x(), cp.y(), cp.z() ); osg::ClusterCullingCallback* ccc = new osg::ClusterCullingCallback( cp, normal, 0.0f ); t->setCullCallback( ccc ); } _geode->addDrawable( t ); return true; }
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(); }
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; }
FilterContext BuildGeometryFilter::push( FeatureList& input, const FilterContext& context ) { reset(); OE_DEBUG << LC << context.toString() << std::endl; bool ok = true; for( FeatureList::iterator i = input.begin(); i != input.end(); i++ ) if ( !push( i->get(), context ) ) ok = false; // In a feature class with one point-per-feature, you end up with one geometry per point, // which results is (a) very bad performance and (b) geometries with a zero bbox that therefore // don't draw. This is not a total solution (won't work for a single point, isn't friendly for // doing feature-selection, etc.) but is a workable temporary fix. In the future we're going // to replace this filter anyway with something more highly optimized (a la osgGIS). // // however...seems that MERGE_GEOMETRY destroys almost everything except for points!! if ( _mergeGeometry == true ) { osgUtil::Optimizer optimizer; optimizer.optimize( _geode.get(), osgUtil::Optimizer::MERGE_GEOMETRY ); } if ( ok ) { if ( !_style.empty() && _geode.valid() ) { // could optimize this to only happen is lines or points were created .. const LineSymbol* lineSymbol = _style.getSymbol<LineSymbol>(); float size = 1.0; if (lineSymbol) size = lineSymbol->stroke()->width().value(); _geode->getOrCreateStateSet()->setAttribute( new osg::Point(size), osg::StateAttribute::ON ); _geode->getOrCreateStateSet()->setAttribute( new osg::LineWidth(size), osg::StateAttribute::ON ); const PointSymbol* pointSymbol = _style.getSymbol<PointSymbol>(); if ( pointSymbol && pointSymbol->size().isSet() ) _geode->getOrCreateStateSet()->setAttribute( new osg::Point( *pointSymbol->size() ), osg::StateAttribute::ON ); } _result = _geode.release(); if ( context.hasReferenceFrame() ) { osg::MatrixTransform* delocalizer = new osg::MatrixTransform( context.inverseReferenceFrame() ); delocalizer->addChild( _result.get() ); _result = delocalizer; } } else { _result = 0L; } FilterContext outCx( context ); outCx.setReferenceFrame( osg::Matrixd::identity() ); // clear the ref frame. return outCx; }
bool BuildGeometryFilter::pushRegularFeature( Feature* input, const FilterContext& context ) { GeometryIterator parts( input->getGeometry(), false ); while( parts.hasMore() ) { Geometry* part = parts.next(); osg::PrimitiveSet::Mode primMode = osg::PrimitiveSet::POINTS; const Style& myStyle = input->style().isSet() ? *input->style() : _style; //const Style* myStyle = input->style().isSet() ? input->style()->get() : _style.get(); osg::Vec4f color = osg::Vec4(1,1,1,1); bool tessellatePolys = true; bool setWidth = input->style().isSet(); // otherwise it will be set globally, we assume float width = 1.0f; switch( part->getType() ) { case Geometry::TYPE_POINTSET: { _hasPoints = true; primMode = osg::PrimitiveSet::POINTS; const PointSymbol* point = myStyle.getSymbol<PointSymbol>(); if (point) { color = point->fill()->color(); } } break; case Geometry::TYPE_LINESTRING: { _hasLines = true; primMode = osg::PrimitiveSet::LINE_STRIP; const LineSymbol* lineSymbol = myStyle.getSymbol<LineSymbol>(); if (lineSymbol) { color = lineSymbol->stroke()->color(); width = lineSymbol->stroke()->width().isSet() ? *lineSymbol->stroke()->width() : 1.0f; } } break; case Geometry::TYPE_RING: { _hasLines = true; primMode = osg::PrimitiveSet::LINE_LOOP; const LineSymbol* lineSymbol = myStyle.getSymbol<LineSymbol>(); if (lineSymbol) { color = lineSymbol->stroke()->color(); width = lineSymbol->stroke()->width().isSet() ? *lineSymbol->stroke()->width() : 1.0f; } } break; case Geometry::TYPE_POLYGON: { primMode = osg::PrimitiveSet::LINE_LOOP; // loop will tessellate into polys const PolygonSymbol* poly = myStyle.getSymbol<PolygonSymbol>(); if (poly) { _hasPolygons = true; color = poly->fill()->color(); } else { // if we have a line symbol and no polygon symbol, draw as an outline. _hasLines = true; const LineSymbol* line = myStyle.getSymbol<LineSymbol>(); if ( line ) { color = line->stroke()->color(); width = line->stroke()->width().isSet() ? *line->stroke()->width() : 1.0f; tessellatePolys = false; } } } break; } osg::Geometry* osgGeom = new osg::Geometry(); if ( _featureNameExpr.isSet() ) { const std::string& name = input->eval( _featureNameExpr.mutable_value() ); osgGeom->setName( name ); } osgGeom->setUseVertexBufferObjects( true ); osgGeom->setUseDisplayList( false ); if ( setWidth && width != 1.0f ) { osgGeom->getOrCreateStateSet()->setAttributeAndModes( new osg::LineWidth( width ), osg::StateAttribute::ON ); } if (part->getType() == Geometry::TYPE_POLYGON && static_cast<Polygon*>(part)->getHoles().size() > 0 ) { Polygon* poly = static_cast<Polygon*>(part); int totalPoints = poly->getTotalPointCount(); osg::Vec3Array* allPoints = new osg::Vec3Array( totalPoints ); std::copy( part->begin(), part->end(), allPoints->begin() ); osgGeom->addPrimitiveSet( new osg::DrawArrays( primMode, 0, part->size() ) ); int offset = part->size(); for( RingCollection::const_iterator h = poly->getHoles().begin(); h != poly->getHoles().end(); ++h ) { Geometry* hole = h->get(); if ( hole->isValid() ) { std::copy( hole->begin(), hole->end(), allPoints->begin() + offset ); osgGeom->addPrimitiveSet( new osg::DrawArrays( primMode, offset, hole->size() ) ); offset += hole->size(); } } osgGeom->setVertexArray( allPoints ); } else { osgGeom->setVertexArray( part->toVec3Array() ); osgGeom->addPrimitiveSet( new osg::DrawArrays( primMode, 0, part->size() ) ); } // tessellate all polygon geometries. Tessellating each geometry separately // with TESS_TYPE_GEOMETRY is much faster than doing the whole bunch together // using TESS_TYPE_DRAWABLE. if ( part->getType() == Geometry::TYPE_POLYGON && tessellatePolys ) { osgUtil::Tessellator tess; //tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_DRAWABLE ); //tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_ODD ); tess.setTessellationType( osgUtil::Tessellator::TESS_TYPE_GEOMETRY ); tess.setWindingType( osgUtil::Tessellator::TESS_WINDING_POSITIVE ); tess.retessellatePolygons( *osgGeom ); // the tessellator results in a collection of trifans, strips, etc. This step will // consolidate those into one (or more if necessary) GL_TRIANGLES primitive. MeshConsolidator::run( *osgGeom ); // mark this geometry as DYNAMIC because otherwise the OSG optimizer will destroy it. //osgGeom->setDataVariance( osg::Object::DYNAMIC ); } if ( context.isGeocentric() && part->getType() != Geometry::TYPE_POINTSET ) { double threshold = osg::DegreesToRadians( *_maxAngle_deg ); MeshSubdivider ms( context.referenceFrame(), context.inverseReferenceFrame() ); //ms.setMaxElementsPerEBO( INT_MAX ); ms.run( threshold, *osgGeom ); } // set the color array. We have to do this last, otherwise it screws up any modifications // make by the MeshSubdivider. No idea why. gw //osg::Vec4Array* colors = new osg::Vec4Array( osgGeom->getVertexArray()->getNumElements() ); //for( unsigned c = 0; c < colors->size(); ++c ) // (*colors)[c] = color; //osgGeom->setColorArray( colors ); //osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); // NOTE! per-vertex colors makes the optimizer destroy the geometry.... osg::Vec4Array* colors = new osg::Vec4Array(1); (*colors)[0] = color; osgGeom->setColorArray( colors ); osgGeom->setColorBinding( osg::Geometry::BIND_OVERALL ); // add the part to the geode. _geode->addDrawable( osgGeom ); } return true; }
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; }