void CollectionFilterState::push( const FeatureList& input ) { for( FeatureList::const_iterator i = input.begin(); i != input.end(); i++ ) if ( i->get()->hasShapeData() ) features.push_back( i->get() ); }
// 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; OGR_SCOPED_LOCK; while( _queue.size() < _chunkSize && !_resultSetEndReached ) { FeatureList filterList; while( filterList.size() < _chunkSize && !_resultSetEndReached ) { OGRFeatureH handle = OGR_L_GetNextFeature( _resultSetHandle ); if ( handle ) { osg::ref_ptr<Feature> feature = OgrUtils::createFeature( handle, _profile.get() ); if (feature.valid() && !_source->isBlacklisted( feature->getFID() ) && validateGeometry( feature->getGeometry() )) { filterList.push_back( feature.release() ); } OGR_F_Destroy( handle ); } else { _resultSetEndReached = true; } } // preprocess the features using the filter list: if ( !_filters.empty() ) { FilterContext cx; cx.setProfile( _profile.get() ); if (_query.bounds().isSet()) { cx.extent() = GeoExtent(_profile->getSRS(), _query.bounds().get()); } else { cx.extent() = _profile->getExtent(); } for( FeatureFilterList::const_iterator i = _filters.begin(); i != _filters.end(); ++i ) { FeatureFilter* filter = i->get(); cx = filter->push( filterList, cx ); } } for(FeatureList::const_iterator i = filterList.begin(); i != filterList.end(); ++i) { _queue.push( i->get() ); } } }
//clustering: // troll the external model for geodes. for each geode, create a geode in the target // model. then, for each geometry in that geode, replicate it once for each instance of // the model in the feature batch and transform the actual verts to a local offset // relative to the tile centroid. Finally, reassemble all the geodes and optimize. // hopefully stateset sharing etc will work out. we may need to strip out LODs too. bool SubstituteModelFilter::cluster(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& cx ) { MarkerToFeatures markerToFeatures; MarkerFactory factory( session); for (FeatureList::const_iterator i = features.begin(); i != features.end(); ++i) { Feature* f = i->get(); osg::ref_ptr< osg::Node > model = factory.getOrCreateNode( f, symbol ); //Try to find the existing entry if (model.valid()) { MarkerToFeatures::iterator itr = markerToFeatures.find( model.get() ); if (itr == markerToFeatures.end()) { markerToFeatures[ model.get() ].push_back( f ); } else { itr->second.push_back( f ); } } } /* OE_NOTICE << "Sorted models into " << markerToFeatures.size() << " buckets" << std::endl; for (MarkerToFeatures::iterator i = markerToFeatures.begin(); i != markerToFeatures.end(); ++i) { OE_NOTICE << " Bucket has " << i->second.size() << " features" << std::endl; } */ //For each model, cluster the features that use that model for (MarkerToFeatures::iterator i = markerToFeatures.begin(); i != markerToFeatures.end(); ++i) { osg::Node* clone = dynamic_cast<osg::Node*>( i->first->clone( osg::CopyOp::DEEP_COPY_ALL ) ); // ..and apply the clustering to the copy. ClusterVisitor cv( i->second, _modelMatrix, this, cx ); clone->accept( cv ); attachPoint->addChild( clone ); } return true; }
//clustering: // troll the external model for geodes. for each geode, create a geode in the target // model. then, for each geometry in that geode, replicate it once for each instance of // the model in the feature batch and transform the actual verts to a local offset // relative to the tile centroid. Finally, reassemble all the geodes and optimize. // hopefully stateset sharing etc will work out. we may need to strip out LODs too. bool SubstituteModelFilter::cluster(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { MarkerToFeatures markerToFeatures; // first, sort the features into buckets, each bucket corresponding to a // unique marker. for (FeatureList::const_iterator i = features.begin(); i != features.end(); ++i) { Feature* f = i->get(); // resolve the URI for the marker: StringExpression uriEx( *symbol->url() ); URI markerURI( f->eval( uriEx, &context ), uriEx.uriContext() ); // find and load the corresponding marker model. We're using the session-level // object store to cache models. This is thread-safe sine we are always going // to CLONE the model before using it. osg::ref_ptr<osg::Node> model = context.getSession()->getObject<osg::Node>( markerURI.full() ); if ( !model.valid() ) { osg::ref_ptr<MarkerResource> mres = new MarkerResource(); mres->uri() = markerURI; model = mres->createNode( context.getSession()->getDBOptions() ); if ( model.valid() ) { // store it, but only if there isn't already one in there. context.getSession()->putObject( markerURI.full(), model.get(), false ); } } if ( model.valid() ) { MarkerToFeatures::iterator itr = markerToFeatures.find( model.get() ); if (itr == markerToFeatures.end()) markerToFeatures[ model.get() ].push_back( f ); else itr->second.push_back( f ); } } //For each model, cluster the features that use that marker for (MarkerToFeatures::iterator i = markerToFeatures.begin(); i != markerToFeatures.end(); ++i) { osg::Node* prototype = i->first; // we're using the Session cache since we know we'll be cloning. if ( prototype ) { osg::Node* clone = osg::clone( prototype, osg::CopyOp::DEEP_COPY_ALL ); // ..and apply the clustering to the copy. ClusterVisitor cv( i->second, symbol, this, context ); clone->accept( cv ); attachPoint->addChild( clone ); } } return true; }
void apply( osg::Geode& geode ) { bool makeECEF = _cx.getSession()->getMapInfo().isGeocentric(); const SpatialReference* srs = _cx.profile()->getSRS(); NumericExpression scaleEx = *_symbol->scale(); osg::Matrixd scaleMatrix; // save the geode's drawables.. osg::Geode::DrawableList old_drawables = geode.getDrawableList(); // ..and clear out the drawables list. geode.removeDrawables( 0, geode.getNumDrawables() ); // foreach each drawable that was originally in the geode... for( osg::Geode::DrawableList::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ ) { osg::Geometry* originalDrawable = dynamic_cast<osg::Geometry*>( i->get() ); if ( !originalDrawable ) continue; // go through the list of input features... for( FeatureList::const_iterator j = _features.begin(); j != _features.end(); j++ ) { const Feature* feature = j->get(); if ( _symbol->scale().isSet() ) { double scale = feature->eval( scaleEx, &_cx ); scaleMatrix.makeScale( scale, scale, scale ); } ConstGeometryIterator gi( feature->getGeometry(), false ); while( gi.hasMore() ) { const Geometry* geom = gi.next(); for( Geometry::const_iterator k = geom->begin(); k != geom->end(); ++k ) { osg::Vec3d point = *k; osg::Matrixd mat; if ( makeECEF ) { osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, srs, point, rotation ); mat = rotation * scaleMatrix * osg::Matrixd::translate(point) * _f2n->world2local(); } else { mat = scaleMatrix * osg::Matrixd::translate(point) * _f2n->world2local(); } // clone the source drawable once for each input feature. osg::ref_ptr<osg::Geometry> newDrawable = osg::clone( originalDrawable, osg::CopyOp::DEEP_COPY_ARRAYS | osg::CopyOp::DEEP_COPY_PRIMITIVES ); osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>( newDrawable->getVertexArray() ); if ( verts ) { for( osg::Vec3Array::iterator v = verts->begin(); v != verts->end(); ++v ) { (*v).set( (*v) * mat ); } // add the new cloned, translated drawable back to the geode. geode.addDrawable( newDrawable.get() ); } } } } } geode.dirtyBound(); MeshConsolidator::run( geode ); // merge the geometry. Not sure this is necessary osgUtil::Optimizer opt; opt.optimize( &geode, osgUtil::Optimizer::MERGE_GEOMETRY | osgUtil::Optimizer::SHARE_DUPLICATE_STATE ); osg::NodeVisitor::apply( geode ); }
void apply( osg::Geode& geode ) { // save the geode's drawables.. osg::Geode::DrawableList old_drawables = geode.getDrawableList(); //OE_DEBUG << "ClusterVisitor geode " << &geode << " featureNode=" << _featureNode << " drawables=" << old_drawables.size() << std::endl; // ..and clear out the drawables list. geode.removeDrawables( 0, geode.getNumDrawables() ); // foreach each drawable that was originally in the geode... for( osg::Geode::DrawableList::iterator i = old_drawables.begin(); i != old_drawables.end(); i++ ) { osg::Geometry* originalDrawable = dynamic_cast<osg::Geometry*>( i->get() ); if ( !originalDrawable ) continue; // go through the list of input features... for( FeatureList::const_iterator j = _features.begin(); j != _features.end(); j++ ) { Feature* feature = j->get(); osg::Matrixd scaleMatrix; if ( _symbol->scale().isSet() ) { double scale = feature->eval( _scaleExpr, &_cx ); scaleMatrix.makeScale( scale, scale, scale ); } osg::Matrixd rotationMatrix; if ( _modelSymbol && _modelSymbol->heading().isSet() ) { float heading = feature->eval( _headingExpr, &_cx ); rotationMatrix.makeRotate( osg::Quat(osg::DegreesToRadians(heading), osg::Vec3(0,0,1)) ); } GeometryIterator gi( feature->getGeometry(), false ); while( gi.hasMore() ) { Geometry* geom = gi.next(); // if necessary, transform the points to the target SRS: if ( !_makeECEF && !_targetSRS->isEquivalentTo(_srs) ) { _srs->transform( geom->asVector(), _targetSRS ); } for( Geometry::const_iterator k = geom->begin(); k != geom->end(); ++k ) { osg::Vec3d point = *k; osg::Matrixd mat; if ( _makeECEF ) { osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, _srs, point, _targetSRS, rotation ); mat = rotationMatrix * rotation * scaleMatrix * osg::Matrixd::translate(point) * _f2n->world2local(); } else { mat = rotationMatrix * scaleMatrix * osg::Matrixd::translate(point) * _f2n->world2local(); } // clone the source drawable once for each input feature. osg::ref_ptr<osg::Geometry> newDrawable = osg::clone( originalDrawable, osg::CopyOp::DEEP_COPY_ARRAYS | osg::CopyOp::DEEP_COPY_PRIMITIVES ); osg::Vec3Array* verts = dynamic_cast<osg::Vec3Array*>( newDrawable->getVertexArray() ); if ( verts ) { for( osg::Vec3Array::iterator v = verts->begin(); v != verts->end(); ++v ) { (*v).set( (*v) * mat ); } // add the new cloned, translated drawable back to the geode. geode.addDrawable( newDrawable.get() ); if ( _cx.featureIndex() ) _cx.featureIndex()->tagPrimitiveSets( newDrawable.get(), feature ); } } } } } geode.dirtyBound(); MeshConsolidator::run( geode ); osg::NodeVisitor::apply( geode ); }
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; // 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(); // evaluate the instance URI expression: StringExpression uriEx = *symbol->url(); URI instanceURI( input->eval(uriEx, &context), 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, scale ); // cache nodes per instance. osg::ref_ptr<osg::Node>& model = uniqueModels[key]; if ( !model.valid() ) { context.resourceCache()->getInstanceNode( 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; 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() ) { 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; }
/** * Creates a complete set of positioned label nodes from a feature list. */ osg::Node* createNode( const FeatureList& input, const TextSymbol* text, const FilterContext& context ) { osg::Group* group = 0L; std::set<std::string> used; // to prevent dupes bool skipDupes = (text->removeDuplicateLabels() == true); StringExpression contentExpr ( *text->content() ); NumericExpression priorityExpr( *text->priority() ); bool makeECEF = false; if ( context.isGeoreferenced() ) { makeECEF = context.getSession()->getMapInfo().isGeocentric(); } for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i ) { const Feature* feature = i->get(); if ( !feature ) continue; const Geometry* geom = feature->getGeometry(); if ( !geom ) continue; osg::Vec3d centroid = geom->getBounds().center(); if ( makeECEF ) { context.profile()->getSRS()->transformToECEF( centroid, centroid ); } const std::string& value = feature->eval( contentExpr, &context ); if ( !value.empty() && (!skipDupes || used.find(value) == used.end()) ) { if ( !group ) { group = new osg::Group(); } double priority = feature->eval( priorityExpr, &context ); Controls::LabelControl* label = new Controls::LabelControl( value ); if ( text->fill().isSet() ) label->setForeColor( text->fill()->color() ); if ( text->halo().isSet() ) label->setHaloColor( text->halo()->color() ); if ( text->size().isSet() ) label->setFontSize( *text->size() ); if ( text->font().isSet() ) label->setFont( osgText::readFontFile(*text->font()) ); Controls::ControlNode* node = new Controls::ControlNode( label, priority ); osg::MatrixTransform* xform = new osg::MatrixTransform( osg::Matrixd::translate(centroid) ); xform->addChild( node ); // for a geocentric map, do a simple dot product cull. if ( makeECEF ) { xform->setCullCallback( new CullNodeByHorizon( centroid, context.getSession()->getMapInfo().getProfile()->getSRS()->getEllipsoid()) ); group->addChild( xform ); } else { group->addChild( xform ); } if ( skipDupes ) { used.insert( value ); } } } return group; }
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; NumericExpression scaleXEx; NumericExpression scaleYEx; NumericExpression scaleZEx; if ( modelSymbol ) { headingEx = *modelSymbol->heading(); scaleXEx = *modelSymbol->scaleX(); scaleYEx = *modelSymbol->scaleY(); scaleZEx = *modelSymbol->scaleZ(); } 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::Vec3d scaleVec(1.0, 1.0, 1.0); osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); scaleVec.set(scale, scale, scale); } if ( modelSymbol ) { if ( modelSymbol->scaleX().isSet() ) { scaleVec.x() *= input->eval( scaleXEx, &context ); } if ( modelSymbol->scaleY().isSet() ) { scaleVec.y() *= input->eval( scaleYEx, &context ); } if ( modelSymbol->scaleZ().isSet() ) { scaleVec.z() *= input->eval( scaleZEx, &context ); } } if ( scaleVec.x() == 0.0 ) scaleVec.x() = 1.0; if ( scaleVec.y() == 0.0 ) scaleVec.y() = 1.0; if ( scaleVec.z() == 0.0 ) scaleVec.z() = 1.0; scaleMatrix = osg::Matrix::scale( scaleVec ); 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, context.getDBOptions()); // if icon decluttering is off, install an AutoTransform. if ( iconSymbol ) { if ( iconSymbol->declutter() == true ) { ScreenSpaceLayout::activate(model->getOrCreateStateSet()); } 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! float scale = 1.0f; osg::Vec3d scaleVec(1.0, 1.0, 1.0); osg::Matrixd scaleMatrix; if ( symbol->scale().isSet() ) { scale = input->eval( scaleEx, &context ); scaleVec.set(scale, scale, scale); } if ( modelSymbol ) { if ( modelSymbol->scaleX().isSet() ) { scaleVec.x() *= input->eval( scaleXEx, &context ); } if ( modelSymbol->scaleY().isSet() ) { scaleVec.y() *= input->eval( scaleYEx, &context ); } if ( modelSymbol->scaleZ().isSet() ) { scaleVec.z() *= input->eval( scaleZEx, &context ); } } if ( scaleVec.x() == 0.0 ) scaleVec.x() = 1.0; if ( scaleVec.y() == 0.0 ) scaleVec.y() = 1.0; if ( scaleVec.z() == 0.0 ) scaleVec.z() = 1.0; scaleMatrix = osg::Matrix::scale( scaleVec ); 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 = scaleMatrix * rotationMatrix * rotation * osg::Matrixd::translate( point ) * _world2local; } else { mat = scaleMatrix * rotationMatrix * 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 ); // Only tag nodes if we aren't using clustering. if ( context.featureIndex() && !_cluster) { 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 ) { ScreenSpaceLayout::activate(attachPoint->getOrCreateStateSet()); } // activate horizon culling if we are in geocentric space if ( context.getSession() && context.getSession()->getMapInfo().isGeocentric() ) { // should this use clipping, or a horizon cull callback? //HorizonCullingProgram::install( attachPoint->getOrCreateStateSet() ); attachPoint->getOrCreateStateSet()->setMode(GL_CLIP_DISTANCE0, 1); } } // active DrawInstanced if required: if ( _useDrawInstanced ) { DrawInstanced::convertGraphToUseDrawInstanced( attachPoint ); // install a shader program to render draw-instanced. DrawInstanced::install( attachPoint->getOrCreateStateSet() ); } return true; }
/** * Creates a complete set of positioned label nodes from a feature list. */ osg::Node* createNode( const FeatureList& input, const Style& style, FilterContext& context ) { if ( style.get<TextSymbol>() == 0L && style.get<IconSymbol>() == 0L ) return 0L; // copy the style so we can (potentially) modify the text symbol. Style styleCopy = style; TextSymbol* text = styleCopy.get<TextSymbol>(); IconSymbol* icon = styleCopy.get<IconSymbol>(); osg::Group* group = new osg::Group(); StringExpression textContentExpr ( text ? *text->content() : StringExpression() ); NumericExpression textPriorityExpr( text ? *text->priority() : NumericExpression() ); NumericExpression textSizeExpr ( text ? *text->size() : NumericExpression() ); StringExpression iconUrlExpr ( icon ? *icon->url() : StringExpression() ); NumericExpression iconScaleExpr ( icon ? *icon->scale() : NumericExpression() ); NumericExpression iconHeadingExpr ( icon ? *icon->heading() : NumericExpression() ); for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i ) { Feature* feature = i->get(); if ( !feature ) continue; // run a symbol script if present. if ( text && text->script().isSet() ) { StringExpression temp( text->script().get() ); feature->eval( temp, &context ); } // run a symbol script if present. if ( icon && icon->script().isSet() ) { StringExpression temp( icon->script().get() ); feature->eval( temp, &context ); } const Geometry* geom = feature->getGeometry(); if ( !geom ) continue; Style tempStyle = styleCopy; // evaluate expressions into literals. // TODO: Later we could replace this with a generate "expression evaluator" type // that we could pass to PlaceNode in the DB options. -gw if ( text ) { if ( text->content().isSet() ) tempStyle.get<TextSymbol>()->content()->setLiteral( feature->eval( textContentExpr, &context ) ); if ( text->size().isSet() ) tempStyle.get<TextSymbol>()->size()->setLiteral( feature->eval(textSizeExpr, &context) ); } if ( icon ) { if ( icon->url().isSet() ) tempStyle.get<IconSymbol>()->url()->setLiteral( feature->eval(iconUrlExpr, &context) ); if ( icon->scale().isSet() ) tempStyle.get<IconSymbol>()->scale()->setLiteral( feature->eval(iconScaleExpr, &context) ); if ( icon->heading().isSet() ) tempStyle.get<IconSymbol>()->heading()->setLiteral( feature->eval(iconHeadingExpr, &context) ); } osg::Node* node = makePlaceNode( context, feature, tempStyle, textPriorityExpr); if ( node ) { if ( context.featureIndex() ) { context.featureIndex()->tagNode(node, feature); } group->addChild( node ); } } // Note to self: need to change this to support picking later. -gw //VirtualProgram* vp = VirtualProgram::getOrCreate(group->getOrCreateStateSet()); //vp->setInheritShaders( false ); return group; }
FilterStateResult CollectionFilterState::signalCheckpoint() { FilterStateResult result; FilterState* next = getNextState(); if ( next ) { int metering = filter->getMetering(); if ( dynamic_cast<FeatureFilterState*>( next ) ) { if ( !features.empty() ) { FeatureGroups feature_groups; for( FeatureList::const_iterator i = features.begin(); i != features.end(); i++ ) feature_groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); FeatureFilterState* state = static_cast<FeatureFilterState*>( next ); result = meterGroups( filter.get(), feature_groups, state, metering, current_env.get() ); } else { result.set( FilterStateResult::STATUS_NODATA, filter.get() ); } } else if ( dynamic_cast<FragmentFilterState*>( next ) ) { FragmentFilterState* state = static_cast<FragmentFilterState*>( next ); if ( !features.empty() ) { FeatureGroups groups; for( FeatureList::const_iterator i = features.begin(); i != features.end(); i++ ) groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), groups, state, metering, current_env.get() ); } else if ( !fragments.empty() ) { FragmentGroups groups; for( FragmentList::const_iterator i = fragments.begin(); i != fragments.end(); i++ ) groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), groups, state, metering, current_env.get() ); } else { result.set( FilterStateResult::STATUS_NODATA, filter.get() ); } } else if ( dynamic_cast<NodeFilterState*>( next ) ) { NodeFilterState* state = static_cast<NodeFilterState*>( next ); if ( !features.empty() ) { FeatureGroups feature_groups; for( FeatureList::const_iterator i = features.begin(); i != features.end(); i++ ) feature_groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), feature_groups, state, metering, current_env.get() ); } else if ( !fragments.empty() ) { FragmentGroups groups; for( FragmentList::const_iterator i = fragments.begin(); i != fragments.end(); i++ ) groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), groups, state, metering, current_env.get() ); } else if ( !nodes.empty() ) { NodeGroups groups; for( AttributedNodeList::const_iterator i = nodes.begin(); i != nodes.end(); i++ ) groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), groups, state, metering, current_env.get() ); } else { result.set( FilterStateResult::STATUS_NODATA, filter.get() ); } } else if ( dynamic_cast<CollectionFilterState*>( next ) ) { CollectionFilterState* state = static_cast<CollectionFilterState*>( next ); if ( !features.empty() ) { FeatureGroups feature_groups; for( FeatureList::const_iterator i = features.begin(); i != features.end(); i++ ) feature_groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), feature_groups, state, metering, current_env.get() ); } else if ( !fragments.empty() ) { FragmentGroups groups; for( FragmentList::const_iterator i = fragments.begin(); i != fragments.end(); i++ ) groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), groups, state, metering, current_env.get() ); } else if ( !nodes.empty() ) { NodeGroups groups; for( AttributedNodeList::const_iterator i = nodes.begin(); i != nodes.end(); i++ ) groups[ filter->assign( i->get(), current_env.get() ) ].push_back( i->get() ); result = meterGroups( filter.get(), groups, state, metering, current_env.get() ); } else { result.set( FilterStateResult::STATUS_NODATA, filter.get() ); } } if ( result.isOK() ) { result = next->signalCheckpoint(); } } // clean up the input: features.clear(); fragments.clear(); nodes.clear(); current_env = NULL; return result; }
osg::Node* BuildTextOperator::operator()(const FeatureList& features, const TextSymbol* symbol, const FilterContext& context) { if (!symbol) return 0; std::set< std::string > labelNames; bool removeDuplicateLabels = symbol->removeDuplicateLabels().isSet() ? symbol->removeDuplicateLabels().get() : false; StringExpression contentExpr = *symbol->content(); osg::Geode* result = new osg::Geode; for (FeatureList::const_iterator itr = features.begin(); itr != features.end(); ++itr) { Feature* feature = itr->get(); if (!feature->getGeometry()) continue; std::string text; if (symbol->content().isSet()) { //Get the text from the specified content and referenced attributes text = feature->eval( contentExpr ); } if (text.empty()) continue; //See if there is a duplicate name if (removeDuplicateLabels && labelNames.find(text) != labelNames.end()) continue; bool rotateToScreen = symbol->rotateToScreen().isSet() ? symbol->rotateToScreen().value() : false; // find the centroid osg::Vec3d position; osg::Quat orientation; GeometryIterator gi( feature->getGeometry() ); while( gi.hasMore() ) { Geometry* geom = gi.next(); TextSymbol::LinePlacement linePlacement = symbol->linePlacement().isSet() ? symbol->linePlacement().get() : TextSymbol::LINEPLACEMENT_ALONG_LINE; if (geom->getType() == Symbology::Geometry::TYPE_LINESTRING && linePlacement == TextSymbol::LINEPLACEMENT_ALONG_LINE) { //Compute the "middle" of the line string LineString* lineString = static_cast<LineString*>(geom); double length = lineString->getLength(); double center = length / 2.0; osg::Vec3d start, end; if (lineString->getSegment(center, start, end)) { TextSymbol::LineOrientation lineOrientation = symbol->lineOrientation().isSet() ? symbol->lineOrientation().get() : TextSymbol::LINEORIENTATION_HORIZONTAL; position = (end + start) / 2.0; //We don't want to orient the text at all if we are rotating to the screen if (!rotateToScreen && lineOrientation != TextSymbol::LINEORIENTATION_HORIZONTAL) { osg::Vec3d dir = (end-start); dir.normalize(); if (lineOrientation == TextSymbol::LINEORIENTATION_PERPENDICULAR) { osg::Vec3d up(0,0,1); const SpatialReference* srs = context.profile()->getSRS(); if (srs && context.isGeocentric() && srs->getEllipsoid()) { osg::Vec3d w = context.toWorld( position ); up = srs->getEllipsoid()->computeLocalUpVector(w.x(), w.y(), w.z()); } dir = up ^ dir; } orientation.makeRotate(osg::Vec3d(1,0,0), dir); } } else { //Fall back on using the center position = lineString->getBounds().center(); } } else { position = geom->getBounds().center(); } } osgText::Text* t = new osgText::Text(); t->setText( text ); std::string font = "fonts/arial.ttf"; if (symbol->font().isSet() && !symbol->font().get().empty()) { font = symbol->font().value(); } t->setFont( font ); t->setAutoRotateToScreen( rotateToScreen ); TextSymbol::SizeMode sizeMode = symbol->sizeMode().isSet() ? symbol->sizeMode().get() : TextSymbol::SIZEMODE_SCREEN; if (sizeMode == TextSymbol::SIZEMODE_SCREEN) { t->setCharacterSizeMode( osgText::TextBase::SCREEN_COORDS ); } else if (sizeMode == TextSymbol::SIZEMODE_OBJECT) { t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS ); } float size = symbol->size().isSet() ? symbol->size().get() : 32.0f; t->setCharacterSize( size ); /* //TODO: We need to do something to account for autotransformed text that is under a LOD. Setting the initial bound works sometimes but not all the time. if (rotateToScreen) { //Set the initial bound so that OSG will traverse the text even if it's under an LOD. osg::BoundingBox bb; bb.expandBy( osg::BoundingSphere(position, size)); t->setInitialBound( bb); }*/ //t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT ); //t->setCharacterSize( 300000.0f ); t->setPosition( position ); t->setRotation( orientation); t->setAlignment( osgText::TextBase::CENTER_CENTER ); t->getOrCreateStateSet()->setAttributeAndModes( new osg::Depth(osg::Depth::ALWAYS), osg::StateAttribute::ON ); t->getOrCreateStateSet()->setRenderBinDetails( 99999, "RenderBin", osg::StateSet::OVERRIDE_RENDERBIN_DETAILS ); // apply styling as appropriate: osg::Vec4f textColor = symbol->fill()->color(); osg::Vec4f haloColor = symbol->halo()->color(); t->setColor( textColor ); t->setBackdropColor( haloColor ); t->setBackdropType( osgText::Text::OUTLINE ); if ( context.isGeocentric() ) { // install a cluster culler t->setCullCallback( new CullDrawableByNormal(position * context.inverseReferenceFrame()) ); } if (_hideClutter) { osg::BoundingBox tBound = t->getBound(); osg::ref_ptr<osgUtil::PolytopeIntersector> intersector = new osgUtil::PolytopeIntersector(osgUtil::Intersector::MODEL, tBound.xMin(), tBound.yMin(), tBound.xMax(), tBound.yMax()); osgUtil::IntersectionVisitor intersectVisitor(intersector.get()); result->accept(intersectVisitor); if (!intersector->containsIntersections()) { result->addDrawable( t ); if (removeDuplicateLabels) labelNames.insert(text); } } else { result->addDrawable( t ); if (removeDuplicateLabels) labelNames.insert(text); } } 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; }
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; }
bool SubstituteModelFilter::process(const FeatureList& features, const MarkerSymbol* symbol, Session* session, osg::Group* attachPoint, FilterContext& context ) { bool makeECEF = context.getSession()->getMapInfo().isGeocentric(); // 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; //std::map< Feature*, osg::ref_ptr<osg::Node> > featureModels; StringExpression uriEx = *symbol->url(); NumericExpression scaleEx = *symbol->scale(); for( FeatureList::const_iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); // evaluate the marker URI expression: StringExpression uriEx = *symbol->url(); URI markerURI( input->eval(uriEx, &context), uriEx.uriContext() ); // find the corresponding marker in the cache MarkerResource* marker = 0L; MarkerCache::Record rec = _markerCache.get( markerURI ); if ( rec.valid() ) { marker = rec.value(); } else { marker = new MarkerResource(); marker->uri() = markerURI; _markerCache.insert( markerURI, marker ); } // 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; scaleMatrix = osg::Matrix::scale( scale, scale, scale ); } // how that we have a marker source, create a node for it std::pair<URI,float> key( markerURI, scale ); osg::ref_ptr<osg::Node>& model = uniqueModels[key]; if ( !model.valid() ) { model = context.resourceCache()->getMarkerNode( marker ); if ( scale != 1.0f && dynamic_cast<osg::AutoTransform*>( model.get() ) ) { // clone the old AutoTransform, set the new scale, and copy over its children. osg::AutoTransform* oldAT = dynamic_cast<osg::AutoTransform*>(model.get()); osg::AutoTransform* newAT = osg::clone( oldAT ); // make a scaler and put it between the new AutoTransform and its kids osg::MatrixTransform* scaler = new osg::MatrixTransform(osg::Matrix::scale(scale,scale,scale)); for( unsigned i=0; i<newAT->getNumChildren(); ++i ) scaler->addChild( newAT->getChild(0) ); newAT->removeChildren(0, newAT->getNumChildren()); newAT->addChild( scaler ); model = newAT; } } if ( model.valid() ) { 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 if the tile is big enough the up vectors won't be quite right. osg::Matrixd rotation; ECEF::transformAndGetRotationMatrix( point, context.profile()->getSRS(), point, rotation ); mat = rotation * scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } else { mat = scaleMatrix * osg::Matrixd::translate( point ) * _world2local; } osg::MatrixTransform* xform = new osg::MatrixTransform(); xform->setMatrix( mat ); xform->addChild( model.get() ); attachPoint->addChild( xform ); // name the feature if necessary if ( !_featureNameExpr.empty() ) { const std::string& name = input->eval( _featureNameExpr, &context); if ( !name.empty() ) xform->setName( name ); } } } } } return true; }
/** * Creates a complete set of positioned label nodes from a feature list. */ osg::Node* createNode( const FeatureList& input, const Style& style, const FilterContext& context ) { const TextSymbol* text = style.get<TextSymbol>(); if ( !text ) return 0L; osg::Group* group = new osg::Group(); Decluttering::setEnabled( group->getOrCreateStateSet(), true ); if ( text->priority().isSet() ) { DeclutteringOptions dco = Decluttering::getOptions(); dco.sortByPriority() = text->priority().isSet(); Decluttering::setOptions( dco ); } StringExpression contentExpr ( *text->content() ); NumericExpression priorityExpr( *text->priority() ); if ( text->removeDuplicateLabels() == true ) { // in remove-duplicates mode, make a list of unique features, selecting // the one with the largest area as the one we'll use for labeling. typedef std::pair<double, osg::ref_ptr<const Feature> > Entry; typedef std::map<std::string, Entry> EntryMap; EntryMap used; for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i ) { Feature* feature = i->get(); if ( feature && feature->getGeometry() ) { const std::string& value = feature->eval( contentExpr ); if ( !value.empty() ) { double area = feature->getGeometry()->getBounds().area2d(); if ( used.find(value) == used.end() ) { used[value] = Entry(area, feature); } else { Entry& biggest = used[value]; if ( area > biggest.first ) { biggest.first = area; biggest.second = feature; } } } } } for( EntryMap::iterator i = used.begin(); i != used.end(); ++i ) { const std::string& value = i->first; const Feature* feature = i->second.second.get(); group->addChild( makeLabelNode(context, feature, value, text, priorityExpr) ); } } else { for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i ) { const Feature* feature = i->get(); if ( !feature ) continue; const Geometry* geom = feature->getGeometry(); if ( !geom ) continue; const std::string& value = feature->eval( contentExpr, &context ); if ( value.empty() ) continue; group->addChild( makeLabelNode(context, feature, value, text, priorityExpr) ); } } #if 0 // good idea but needs work. DepthOffsetGroup* dog = new DepthOffsetGroup(); dog->setMinimumOffset( 500.0 ); dog->addChild( group ); return dog; #endif return group; }
//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; }
osg::Node* BuildTextOperator::operator()(const FeatureList& features, const TextSymbol* symbol, const FilterContext& context) { if (!symbol) return 0; std::set< std::string > labelNames; bool removeDuplicateLabels = symbol->removeDuplicateLabels().isSet() ? symbol->removeDuplicateLabels().get() : false; osg::Geode* result = new osg::Geode; for (FeatureList::const_iterator itr = features.begin(); itr != features.end(); ++itr) { Feature* feature = itr->get(); if (!feature->getGeometry()) continue; std::string text; //If the feature is a TextAnnotation, just get the value from it TextAnnotation* annotation = dynamic_cast<TextAnnotation*>(feature); if (annotation) { text = annotation->text(); } else if (symbol->content().isSet()) { //Get the text from the specified content and referenced attributes std::string content = symbol->content().value(); text = parseAttributes(feature, content, symbol->contentAttributeDelimiter().value()); } else if (symbol->attribute().isSet()) { //Get the text from the specified attribute std::string attr = symbol->attribute().value(); text = feature->getAttr(attr); } if (text.empty()) continue; //See if there is a duplicate name if (removeDuplicateLabels && labelNames.find(text) != labelNames.end()) continue; bool rotateToScreen = symbol->rotateToScreen().isSet() ? symbol->rotateToScreen().value() : false; // find the centroid osg::Vec3d position; osg::Quat orientation; GeometryIterator gi( feature->getGeometry() ); while( gi.hasMore() ) { Geometry* geom = gi.next(); TextSymbol::LinePlacement linePlacement = symbol->linePlacement().isSet() ? symbol->linePlacement().get() : TextSymbol::LINEPLACEMENT_ALONG_LINE; if (geom->getType() == Symbology::Geometry::TYPE_LINESTRING && linePlacement == TextSymbol::LINEPLACEMENT_ALONG_LINE) { //Compute the "middle" of the line string LineString* lineString = static_cast<LineString*>(geom); double length = lineString->getLength(); double center = length / 2.0; osg::Vec3d start, end; if (lineString->getSegment(center, start, end)) { TextSymbol::LineOrientation lineOrientation = symbol->lineOrientation().isSet() ? symbol->lineOrientation().get() : TextSymbol::LINEORIENTATION_HORIZONTAL; position = (end + start) / 2.0; //We don't want to orient the text at all if we are rotating to the screen if (!rotateToScreen && lineOrientation != TextSymbol::LINEORIENTATION_HORIZONTAL) { osg::Vec3d dir = (end-start); dir.normalize(); if (lineOrientation == TextSymbol::LINEORIENTATION_PERPENDICULAR) { osg::Vec3d up(0,0,1); const SpatialReference* srs = context.profile()->getSRS(); if (srs && context.isGeocentric() && srs->getEllipsoid()) { osg::Vec3d w = context.toWorld( position ); up = srs->getEllipsoid()->computeLocalUpVector(w.x(), w.y(), w.z()); } dir = up ^ dir; } orientation.makeRotate(osg::Vec3d(1,0,0), dir); } } else { //Fall back on using the center position = lineString->getBounds().center(); } } else { position = geom->getBounds().center(); } } osgText::Text* t = new osgText::Text(); t->setText( text ); std::string font = "fonts/arial.ttf"; if (symbol->font().isSet() && !symbol->font().get().empty()) { font = symbol->font().value(); } t->setFont( font ); t->setAutoRotateToScreen( rotateToScreen ); TextSymbol::SizeMode sizeMode = symbol->sizeMode().isSet() ? symbol->sizeMode().get() : TextSymbol::SIZEMODE_SCREEN; if (sizeMode == TextSymbol::SIZEMODE_SCREEN) { t->setCharacterSizeMode( osgText::TextBase::SCREEN_COORDS ); } else if (sizeMode == TextSymbol::SIZEMODE_OBJECT) { t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS ); } float size = symbol->size().isSet() ? symbol->size().get() : 32.0f; t->setCharacterSize( size ); //t->setCharacterSizeMode( osgText::TextBase::OBJECT_COORDS_WITH_MAXIMUM_SCREEN_SIZE_CAPPED_BY_FONT_HEIGHT ); //t->setCharacterSize( 300000.0f ); t->setPosition( position ); t->setRotation( orientation); 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 = symbol->fill()->color(); osg::Vec4f haloColor = symbol->halo()->color(); t->setColor( textColor ); t->setBackdropColor( haloColor ); t->setBackdropType( osgText::Text::OUTLINE ); if ( context.isGeocentric() ) { // install a cluster culler t->setCullCallback( new CullPlaneCallback( position * context.inverseReferenceFrame() ) ); } result->addDrawable( t ); if (removeDuplicateLabels) labelNames.insert(text); } return result; }
/** * Creates a complete set of positioned label nodes from a feature list. */ osg::Node* createNode( const FeatureList& input, const Style& style, const FilterContext& context ) { const TextSymbol* text = style.get<TextSymbol>(); osg::Group* group = 0L; std::set<std::string> used; // to prevent dupes bool skipDupes = (text->removeDuplicateLabels() == true); StringExpression contentExpr ( *text->content() ); NumericExpression priorityExpr( *text->priority() ); //bool makeECEF = false; const SpatialReference* ecef = 0L; if ( context.isGeoreferenced() ) { //makeECEF = context.getSession()->getMapInfo().isGeocentric(); ecef = context.getSession()->getMapSRS()->getECEF(); } for( FeatureList::const_iterator i = input.begin(); i != input.end(); ++i ) { const Feature* feature = i->get(); if ( !feature ) continue; const Geometry* geom = feature->getGeometry(); if ( !geom ) continue; osg::Vec3d centroid = geom->getBounds().center(); if ( ecef ) { context.profile()->getSRS()->transform( centroid, ecef, centroid ); //context.profile()->getSRS()->transformToECEF( centroid, centroid ); } const std::string& value = feature->eval( contentExpr, &context ); if ( !value.empty() && (!skipDupes || used.find(value) == used.end()) ) { if ( !group ) { group = new osg::Group(); } double priority = feature->eval( priorityExpr, &context ); Controls::LabelControl* label = new Controls::LabelControl( value ); if ( text->fill().isSet() ) label->setForeColor( text->fill()->color() ); if ( text->halo().isSet() ) label->setHaloColor( text->halo()->color() ); //if ( text->size().isSet() ) // label->setFontSize( *text->size() ); if ( text->font().isSet() ) { // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement osgText::Font* font = osgText::readFontFile(*text->font() ); // mitigates mipmapping issues that cause rendering artifacts for some fonts/placement if ( font ) font->setGlyphImageMargin( 2 ); label->setFont( font ); } Controls::ControlNode* node = new Controls::ControlNode( label, priority ); osg::MatrixTransform* xform = new osg::MatrixTransform( osg::Matrixd::translate(centroid) ); xform->addChild( node ); // for a geocentric map, do a simple dot product cull. if ( ecef ) { xform->setCullCallback( new HorizonCullCallback(*ecef->getEllipsoid()) ); group->addChild( xform ); } else { group->addChild( xform ); } if ( skipDupes ) { used.insert( value ); } } } return group; }