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* 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; }
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 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; }
void ScatterFilter::polyScatter(const Geometry* input, const SpatialReference* inputSRS, const FilterContext& context, PointSet* output ) const { 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 = 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 rx = ((double)::rand()) / (double)RAND_MAX; double ry = ((double)::rand()) / (double)RAND_MAX; double x = bounds.xMin() + rx * bounds.width(); double y = bounds.yMin() + ry * 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) ); } } } } }
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; }