// 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() ); } } }
FilterContext TransformFilter::push( FeatureList& input, FilterContext& incx ) { _bbox = osg::BoundingBoxd(); // first transform all the points into the output SRS, collecting a bounding box as we go: bool ok = true; for( FeatureList::iterator i = input.begin(); i != input.end(); i++ ) if ( !push( i->get(), incx ) ) ok = false; FilterContext outcx( incx ); outcx.isGeocentric() = _makeGeocentric; if ( _outputSRS.valid() ) { if ( incx.extent()->isValid() ) outcx.profile() = new FeatureProfile( incx.extent()->transform( _outputSRS.get() ) ); else outcx.profile() = new FeatureProfile( incx.profile()->getExtent().transform( _outputSRS.get() ) ); } // set the reference frame to shift data to the centroid. This will // prevent floating point precision errors in the openGL pipeline for // properly gridded data. if ( _bbox.valid() && _localize ) { // create a suitable reference frame: osg::Matrixd localizer; if ( _makeGeocentric ) { localizer = createGeocentricInvRefFrame( _bbox.center(), _outputSRS.get() ); localizer.invert( localizer ); } else { localizer = osg::Matrixd::translate( -_bbox.center() ); } // localize the geometry relative to the reference frame. for( FeatureList::iterator i = input.begin(); i != input.end(); i++ ) { localizeGeometry( i->get(), localizer ); } outcx.setReferenceFrame( localizer ); } return outcx; }
v8::Handle<v8::Value> JSFilterContext::PropertyCallback(v8::Local<v8::String> name, const v8::AccessorInfo& info) { FilterContext* context = V8Util::UnwrapObject<FilterContext>(info.Holder()); v8::String::Utf8Value utf8_value(name); std::string prop(*utf8_value); if (!context || prop.empty()) return v8::Handle<v8::Value>(); if (prop == "session") return JSSession::WrapSession(const_cast<Session*>(context->getSession())); if (prop == "profile") return JSFeatureProfile::WrapFeatureProfile(const_cast<FeatureProfile*>(context->profile().get())); if (prop == "extent" && context->extent().isSet()) return JSGeoExtent::WrapGeoExtent(const_cast<osgEarth::GeoExtent*>(&context->extent().get())); //if (prop == "geocentric") // return v8::Boolean::New(context->isGeocentric()); return v8::Handle<v8::Value>(); }
void FeaturesToNodeFilter::computeLocalizers( const FilterContext& context ) { if ( context.isGeoreferenced() ) { if ( context.getSession()->getMapInfo().isGeocentric() ) { const SpatialReference* geogSRS = context.profile()->getSRS()->getGeographicSRS(); GeoExtent geodExtent = context.extent()->transform( geogSRS ); if ( geodExtent.width() < 180.0 ) { osg::Vec3d centroid, centroidECEF; geodExtent.getCentroid( centroid.x(), centroid.y() ); geogSRS->transform( centroid, geogSRS->getECEF(), centroidECEF ); geogSRS->getECEF()->createLocalToWorld( centroidECEF, _local2world ); _world2local.invert( _local2world ); } } else // projected { if ( context.extent().isSet() ) { osg::Vec3d centroid; context.extent()->getCentroid(centroid.x(), centroid.y()); context.extent()->getSRS()->transform( centroid, context.getSession()->getMapInfo().getProfile()->getSRS(), centroid ); _world2local.makeTranslate( -centroid ); _local2world.invert( _world2local ); } } } }
void FeatureSource::applyFilters(FeatureList& features, const GeoExtent& extent) const { // apply filters before returning. if ( !getFilters().empty() ) { FilterContext cx; cx.setProfile( getFeatureProfile() ); cx.extent() = extent; for(FeatureFilterList::const_iterator filter = getFilters().begin(); filter != getFilters().end(); ++filter) { cx = filter->get()->push( features, cx ); } } }
void FeatureSource::applyFilters(FeatureList& features, const GeoExtent& extent) const { // apply filters before returning. if (_filters.valid() && _filters->empty() == false) { FilterContext cx; cx.setProfile( getFeatureProfile() ); cx.extent() = extent; for(FeatureFilterChain::const_iterator filter = _filters->begin(); filter != _filters->end(); ++filter) { cx = filter->get()->push( features, cx ); } } }
bool ExtrudeGeometryFilter::buildStructure(const Geometry* input, double height, double heightOffset, bool flatten, const SkinResource* wallSkin, const SkinResource* roofSkin, Structure& structure, FilterContext& cx ) { bool makeECEF = false; const SpatialReference* srs = 0L; const SpatialReference* mapSRS = 0L; if ( cx.isGeoreferenced() ) { srs = cx.extent()->getSRS(); makeECEF = cx.getSession()->getMapInfo().isGeocentric(); mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS(); } // whether this is a closed polygon structure. structure.isPolygon = (input->getComponentType() == Geometry::TYPE_POLYGON); // extrusion working variables double targetLen = -DBL_MAX; osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX); double minLoc_len = DBL_MAX; osg::Vec3d maxLoc(0,0,0); double maxLoc_len = 0; // Initial pass over the geometry does two things: // 1: Calculate the minimum Z across all parts. // 2: Establish a "target length" for extrusion double absHeight = fabs(height); ConstGeometryIterator zfinder( input ); while( zfinder.hasMore() ) { const Geometry* geom = zfinder.next(); for( Geometry::const_iterator m = geom->begin(); m != geom->end(); ++m ) { osg::Vec3d m_point = *m; if ( m_point.z() + absHeight > targetLen ) targetLen = m_point.z() + absHeight; if (m_point.z() < minLoc.z()) minLoc = m_point; if (m_point.z() > maxLoc.z()) maxLoc = m_point; } } // apply the height offsets height -= heightOffset; targetLen -= heightOffset; float roofRotation = 0.0f; Bounds roofBounds; float sinR = 0.0f, cosR = 0.0f; double roofTexSpanX = 0.0, roofTexSpanY = 0.0; osg::ref_ptr<const SpatialReference> roofProjSRS; if ( roofSkin ) { roofBounds = input->getBounds(); // if our data is lat/long, we need to reproject the geometry and the bounds into a projected // coordinate system in order to properly generate tex coords. if ( srs && srs->isGeographic() ) { osg::Vec2d geogCenter = roofBounds.center2d(); roofProjSRS = srs->createUTMFromLonLat( Angle(geogCenter.x()), Angle(geogCenter.y()) ); if ( roofProjSRS.valid() ) { roofBounds.transform( srs, roofProjSRS.get() ); osg::ref_ptr<Geometry> projectedInput = input->clone(); srs->transform( projectedInput->asVector(), roofProjSRS.get() ); roofRotation = getApparentRotation( projectedInput.get() ); } } else { roofRotation = getApparentRotation( input ); } sinR = sin(roofRotation); cosR = cos(roofRotation); if ( !roofSkin->isTiled().value() ) { //note: non-tiled roofs don't really work atm. roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height(); roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height(); } else { roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0; if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0; roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0; if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0; } } // prep for wall texture coordinate generation. double texWidthM = wallSkin ? *wallSkin->imageWidth() : 0.0; double texHeightM = wallSkin ? *wallSkin->imageHeight() : 1.0; ConstGeometryIterator iter( input ); while( iter.hasMore() ) { const Geometry* part = iter.next(); // skip a part that's too small if (part->size() < 2) continue; // add a new wall. structure.elevations.push_back(Elevation()); Elevation& elevation = structure.elevations.back(); double maxHeight = targetLen - minLoc.z(); // Adjust the texture height so it is a multiple of the maximum height double div = osg::round(maxHeight / texHeightM); elevation.texHeightAdjustedM = div > 0.0 ? maxHeight / div : maxHeight; // Step 1 - Create the real corners and transform them into our target SRS. Corners corners; for(Geometry::const_iterator m = part->begin(); m != part->end(); ++m) { Corners::iterator corner = corners.insert(corners.end(), Corner()); // mark as "from source", as opposed to being inserted by the algorithm. corner->isFromSource = true; corner->base = *m; // extrude: if ( height >= 0 ) // extrude up { if ( flatten ) corner->roof.set( corner->base.x(), corner->base.y(), targetLen ); else corner->roof.set( corner->base.x(), corner->base.y(), corner->base.z() + height ); } else // height < 0 .. extrude down { corner->roof = *m; corner->base.z() += height; } // figure out the rooftop texture coords before doing any transformation: if ( roofSkin && srs ) { double xr, yr; if ( srs && srs->isGeographic() && roofProjSRS ) { osg::Vec3d projRoofPt; srs->transform( corner->roof, roofProjSRS.get(), projRoofPt ); xr = (projRoofPt.x() - roofBounds.xMin()); yr = (projRoofPt.y() - roofBounds.yMin()); } else { xr = (corner->roof.x() - roofBounds.xMin()); yr = (corner->roof.y() - roofBounds.yMin()); } corner->roofTexU = (cosR*xr - sinR*yr) / roofTexSpanX; corner->roofTexV = (sinR*xr + cosR*yr) / roofTexSpanY; } // transform into target SRS. transformAndLocalize( corner->base, srs, corner->base, mapSRS, _world2local, makeECEF ); transformAndLocalize( corner->roof, srs, corner->roof, mapSRS, _world2local, makeECEF ); } // Step 2 - Insert intermediate Corners as needed to satify texturing // requirements (if necessary) and record each corner offset (horizontal distance // from the beginning of the part geometry to the corner.) double cornerOffset = 0.0; double nextTexBoundary = texWidthM; for(Corners::iterator c = corners.begin(); c != corners.end(); ++c) { Corners::iterator this_corner = c; Corners::iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); osg::Vec3d base_vec = next_corner->base - this_corner->base; double span = base_vec.length(); this_corner->offsetX = cornerOffset; if (wallSkin) { base_vec /= span; // normalize osg::Vec3d roof_vec = next_corner->roof - this_corner->roof; roof_vec.normalize(); while(nextTexBoundary < cornerOffset+span) { // insert a new fake corner. Corners::iterator new_corner = corners.insert(next_corner, Corner()); new_corner->isFromSource = false; double advance = nextTexBoundary-cornerOffset; new_corner->base = this_corner->base + base_vec*advance; new_corner->roof = this_corner->roof + roof_vec*advance; new_corner->offsetX = cornerOffset + advance; nextTexBoundary += texWidthM; // advance the main iterator c = new_corner; } } cornerOffset += span; } // Step 3 - Calculate the angle of each corner. osg::Vec3d prev_vec; for(Corners::iterator c = corners.begin(); c != corners.end(); ++c) { Corners::const_iterator this_corner = c; Corners::const_iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); if ( this_corner == corners.begin() ) { Corners::const_iterator prev_corner = corners.end(); --prev_corner; prev_vec = this_corner->roof - prev_corner->roof; prev_vec.normalize(); } osg::Vec3d this_vec = next_corner->roof - this_corner->roof; this_vec.normalize(); if ( c != corners.begin() ) { c->cosAngle = prev_vec * this_vec; } } // Step 4 - Create faces connecting each pair of Posts. Faces& faces = elevation.faces; for(Corners::const_iterator c = corners.begin(); c != corners.end(); ++c) { Corners::const_iterator this_corner = c; Corners::const_iterator next_corner = c; if ( ++next_corner == corners.end() ) next_corner = corners.begin(); // only close the shape for polygons. if (next_corner != corners.begin() || structure.isPolygon) { faces.push_back(Face()); Face& face = faces.back(); face.left = *this_corner; face.right = *next_corner; // recalculate the final offset on the last face if ( next_corner == corners.begin() ) { osg::Vec3d vec = next_corner->roof - this_corner->roof; face.right.offsetX = face.left.offsetX + vec.length(); } face.widthM = next_corner->offsetX - this_corner->offsetX; } } } return true; }
bool BuildGeometryFilter::process( FeatureList& features, const FilterContext& context ) { bool makeECEF = false; const SpatialReference* featureSRS = 0L; const SpatialReference* mapSRS = 0L; if ( context.isGeoreferenced() ) { makeECEF = context.getSession()->getMapInfo().isGeocentric(); featureSRS = context.extent()->getSRS(); mapSRS = context.getSession()->getMapInfo().getProfile()->getSRS(); } for( FeatureList::iterator f = features.begin(); f != features.end(); ++f ) { Feature* input = f->get(); GeometryIterator parts( input->getGeometry(), false ); while( parts.hasMore() ) { Geometry* part = parts.next(); // skip empty geometry if ( part->size() == 0 ) continue; const Style& myStyle = input->style().isSet() ? *input->style() : _style; bool setLinePropsHere = input->style().isSet(); // otherwise it will be set globally, we assume float width = 1.0f; bool hasPolyOutline = false; const PointSymbol* pointSymbol = myStyle.get<PointSymbol>(); const LineSymbol* lineSymbol = myStyle.get<LineSymbol>(); const PolygonSymbol* polySymbol = myStyle.get<PolygonSymbol>(); // resolve the geometry type from the component type and the symbology: Geometry::Type renderType = Geometry::TYPE_UNKNOWN; // First priority is a matching part type and symbol: if ( polySymbol != 0L && part->getType() == Geometry::TYPE_POLYGON ) { renderType = Geometry::TYPE_POLYGON; } else if ( lineSymbol != 0L && part->isLinear() ) { renderType = part->getType(); } else if ( pointSymbol != 0L && part->getType() == Geometry::TYPE_POINTSET ) { renderType = Geometry::TYPE_POINTSET; } // Second priority is the symbol: else if ( polySymbol != 0L ) { renderType = Geometry::TYPE_POLYGON; } else if ( lineSymbol != 0L ) { if ( part->getType() == Geometry::TYPE_POLYGON ) renderType = Geometry::TYPE_RING; else renderType = Geometry::TYPE_LINESTRING; } else if ( pointSymbol != 0L ) { renderType = Geometry::TYPE_POINTSET; } // No symbol? just use the geometry type. else { renderType = part->getType(); } // validate the geometry: if ( renderType == Geometry::TYPE_POLYGON && part->size() < 3 ) continue; else if ( (renderType == Geometry::TYPE_LINESTRING || renderType == Geometry::TYPE_RING) && part->size() < 2 ) continue; // resolve the color: osg::Vec4f primaryColor = polySymbol ? osg::Vec4f(polySymbol->fill()->color()) : lineSymbol ? osg::Vec4f(lineSymbol->stroke()->color()) : pointSymbol ? osg::Vec4f(pointSymbol->fill()->color()) : osg::Vec4f(1,1,1,1); osg::Geometry* osgGeom = new osg::Geometry(); osgGeom->setUseVertexBufferObjects( _useVertexBufferObjects.value() ); if ( _featureNameExpr.isSet() ) { const std::string& name = input->eval( _featureNameExpr.mutable_value(), &context ); osgGeom->setName( name ); } // build the geometry: osg::Vec3Array* allPoints = 0L; if ( renderType == Geometry::TYPE_POLYGON ) { buildPolygon(part, featureSRS, mapSRS, makeECEF, true, osgGeom); allPoints = static_cast<osg::Vec3Array*>( osgGeom->getVertexArray() ); } else { // line or point geometry GLenum primMode = renderType == Geometry::TYPE_LINESTRING ? GL_LINE_STRIP : renderType == Geometry::TYPE_RING ? GL_LINE_LOOP : GL_POINTS; allPoints = new osg::Vec3Array(); transformAndLocalize( part->asVector(), featureSRS, allPoints, mapSRS, _world2local, makeECEF ); osgGeom->addPrimitiveSet( new osg::DrawArrays( primMode, 0, part->size() ) ); osgGeom->setVertexArray( allPoints ); applyLineAndPointSymbology( osgGeom->getOrCreateStateSet(), lineSymbol, pointSymbol ); if ( primMode == GL_POINTS && allPoints->size() == 1 ) { const osg::Vec3d& center = (*allPoints)[0]; osgGeom->setInitialBound( osg::BoundingBox(center-osg::Vec3(.5,.5,.5), center+osg::Vec3(.5,.5,.5)) ); } } if (allPoints->getVertexBufferObject()) allPoints->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB); // subdivide the mesh if necessary to conform to an ECEF globe: if ( makeECEF && renderType != Geometry::TYPE_POINTSET ) { // check for explicit tessellation disable: const LineSymbol* line = _style.get<LineSymbol>(); bool disableTess = line && line->tessellation().isSetTo(0); if ( makeECEF && !disableTess ) { double threshold = osg::DegreesToRadians( *_maxAngle_deg ); OE_DEBUG << "Running mesh subdivider with threshold " << *_maxAngle_deg << std::endl; MeshSubdivider ms( _world2local, _local2world ); //ms.setMaxElementsPerEBO( INT_MAX ); if ( input->geoInterp().isSet() ) ms.run( *osgGeom, threshold, *input->geoInterp() ); else ms.run( *osgGeom, threshold, *_geoInterp ); } } // assign the primary color: #if USE_SINGLE_COLOR osg::Vec4Array* colors = new osg::Vec4Array( 1 ); (*colors)[0] = primaryColor; osgGeom->setColorBinding( osg::Geometry::BIND_OVERALL ); #else osg::Vec4Array* colors = new osg::Vec4Array( osgGeom->getVertexArray()->getNumElements() ); //allPoints->size() ); for(unsigned c=0; c<colors->size(); ++c) (*colors)[c] = primaryColor; osgGeom->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); #endif osgGeom->setColorArray( colors ); _geode->addDrawable( osgGeom ); // record the geometry's primitive set(s) in the index: if ( context.featureIndex() ) context.featureIndex()->tagPrimitiveSets( osgGeom, input ); // build secondary geometry, if necessary (polygon outlines) if ( renderType == Geometry::TYPE_POLYGON && lineSymbol ) { // polygon offset on the poly so the outline doesn't z-fight osgGeom->getOrCreateStateSet()->setAttributeAndModes( new osg::PolygonOffset(1,1), 1 ); osg::Geometry* outline = new osg::Geometry(); outline->setUseVertexBufferObjects( _useVertexBufferObjects.value() ); buildPolygon(part, featureSRS, mapSRS, makeECEF, false, outline); if ( outline->getVertexArray()->getVertexBufferObject() ) outline->getVertexArray()->getVertexBufferObject()->setUsage(GL_STATIC_DRAW_ARB); osg::Vec4f outlineColor = lineSymbol->stroke()->color(); osg::Vec4Array* outlineColors = new osg::Vec4Array(); #if USE_SINGLE_COLOR outlineColors->reserve(1); outlineColors->push_back( outlineColor ); outline->setColorBinding( osg::Geometry::BIND_OVERALL ); #else unsigned pcount = part->getTotalPointCount(); outlineColors->reserve( pcount ); for( unsigned c=0; c < pcount; ++c ) outlineColors->push_back( outlineColor ); outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); #endif outline->setColorArray(outlineColors); // check for explicit tessellation disable: bool disableTess = lineSymbol && lineSymbol->tessellation().isSetTo(0); // subdivide if necessary. if ( makeECEF && !disableTess ) { double threshold = osg::DegreesToRadians( *_maxAngle_deg ); OE_DEBUG << "Running mesh subdivider for outlines with threshold " << *_maxAngle_deg << std::endl; MeshSubdivider ms( _world2local, _local2world ); if ( input->geoInterp().isSet() ) ms.run( *outline, threshold, *input->geoInterp() ); else ms.run( *outline, threshold, *_geoInterp ); } applyLineAndPointSymbology( outline->getOrCreateStateSet(), lineSymbol, 0L ); // make normals before adding an outline osgUtil::SmoothingVisitor sv; _geode->accept( sv ); _geode->addDrawable( outline ); //_featureNode->addDrawable( outline, input->getFID() ); // Mark each primitive set with its feature ID. if ( context.featureIndex() ) context.featureIndex()->tagPrimitiveSets( outline, input ); } } } return true; }
bool ExtrudeGeometryFilter::extrudeGeometry(const Geometry* input, double height, double heightOffset, bool flatten, osg::Geometry* walls, osg::Geometry* roof, osg::Geometry* base, osg::Geometry* outline, const osg::Vec4& wallColor, const osg::Vec4& wallBaseColor, const osg::Vec4& roofColor, const osg::Vec4& outlineColor, const SkinResource* wallSkin, const SkinResource* roofSkin, FilterContext& cx ) { bool makeECEF = false; const SpatialReference* srs = 0L; const SpatialReference* mapSRS = 0L; if ( cx.isGeoreferenced() ) { srs = cx.extent()->getSRS(); makeECEF = cx.getSession()->getMapInfo().isGeocentric(); mapSRS = cx.getSession()->getMapInfo().getProfile()->getSRS(); } bool made_geom = false; double tex_width_m = wallSkin ? *wallSkin->imageWidth() : 1.0; double tex_height_m = wallSkin ? *wallSkin->imageHeight() : 1.0; bool tex_repeats_y = wallSkin ? *wallSkin->isTiled() : false; bool useColor = (!wallSkin || wallSkin->texEnvMode() != osg::TexEnv::DECAL) && !_makeStencilVolume; bool isPolygon = input->getComponentType() == Geometry::TYPE_POLYGON; unsigned pointCount = input->getTotalPointCount(); // If we are extruding a polygon, and applying a wall texture, we need an extra // point in the geometry in order to close the polygon and generate a unique // texture coordinate for that final point. bool isSkinnedPolygon = isPolygon && wallSkin != 0L; // Total number of verts. Add 2 to close a polygon (necessary so the first and last // points can have unique texture coordinates) unsigned numWallVerts = 2 * pointCount + (isSkinnedPolygon? (2 * input->getNumGeometries()) : 0); // create all the OSG geometry components osg::Vec3Array* verts = new osg::Vec3Array( numWallVerts ); walls->setVertexArray( verts ); osg::Vec2Array* wallTexcoords = 0L; if ( wallSkin ) { wallTexcoords = new osg::Vec2Array( numWallVerts ); walls->setTexCoordArray( 0, wallTexcoords ); } osg::Vec4Array* colors = 0L; if ( useColor ) { // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw colors = new osg::Vec4Array(); colors->reserve( numWallVerts ); colors->assign( numWallVerts, wallColor ); walls->setColorArray( colors ); walls->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); } // set up rooftop tessellation and texturing, if necessary: osg::Vec3Array* roofVerts = 0L; osg::Vec2Array* roofTexcoords = 0L; float roofRotation = 0.0f; Bounds roofBounds; float sinR = 0.0f, cosR = 0.0f; double roofTexSpanX = 0.0, roofTexSpanY = 0.0; osg::ref_ptr<const SpatialReference> roofProjSRS; if ( roof ) { roofVerts = new osg::Vec3Array( pointCount ); roof->setVertexArray( roofVerts ); // per-vertex colors are necessary if we are going to use the MeshConsolidator -gw if ( useColor ) { osg::Vec4Array* roofColors = new osg::Vec4Array(); roofColors->reserve( pointCount ); roofColors->assign( pointCount, roofColor ); roof->setColorArray( roofColors ); roof->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); } if ( roofSkin ) { roofTexcoords = new osg::Vec2Array( pointCount ); roof->setTexCoordArray( 0, roofTexcoords ); // Get the orientation of the geometry. This is a hueristic that will help // us align the roof skin texture properly. TODO: make this optional? It makes // sense for buildings and such, but perhaps not for all extruded shapes. roofRotation = getApparentRotation( input ); roofBounds = input->getBounds(); // if our data is lat/long, we need to reproject the geometry and the bounds into a projected // coordinate system in order to properly generate tex coords. if ( srs && srs->isGeographic() ) { osg::Vec2d geogCenter = roofBounds.center2d(); roofProjSRS = srs->createUTMFromLonLat( Angular(geogCenter.x()), Angular(geogCenter.y()) ); roofBounds.transform( srs, roofProjSRS.get() ); osg::ref_ptr<Geometry> projectedInput = input->clone(); srs->transform( projectedInput->asVector(), roofProjSRS.get() ); roofRotation = getApparentRotation( projectedInput.get() ); } else { roofRotation = getApparentRotation( input ); } sinR = sin(roofRotation); cosR = cos(roofRotation); if ( !roofSkin->isTiled().value() ) { //note: doesn't really work roofTexSpanX = cosR*roofBounds.width() - sinR*roofBounds.height(); roofTexSpanY = sinR*roofBounds.width() + cosR*roofBounds.height(); } else { roofTexSpanX = roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : 10.0; if ( roofTexSpanX <= 0.0 ) roofTexSpanX = 10.0; roofTexSpanY = roofSkin->imageHeight().isSet() ? *roofSkin->imageHeight() : roofSkin->imageWidth().isSet() ? *roofSkin->imageWidth() : 10.0; if ( roofTexSpanY <= 0.0 ) roofTexSpanY = 10.0; } } } osg::Vec3Array* baseVerts = NULL; if ( base ) { baseVerts = new osg::Vec3Array( pointCount ); base->setVertexArray( baseVerts ); } osg::Vec3Array* outlineVerts = 0L; osg::Vec3Array* outlineNormals = 0L; if ( outline ) { outlineVerts = new osg::Vec3Array( numWallVerts ); outline->setVertexArray( outlineVerts ); osg::Vec4Array* outlineColors = new osg::Vec4Array(); outlineColors->reserve( numWallVerts ); outlineColors->assign( numWallVerts, outlineColor ); outline->setColorArray( outlineColors ); outline->setColorBinding( osg::Geometry::BIND_PER_VERTEX ); // cop out, just point all the outline normals up. fix this later. outlineNormals = new osg::Vec3Array(); outlineNormals->reserve( numWallVerts ); outlineNormals->assign( numWallVerts, osg::Vec3(0,0,1) ); outline->setNormalArray( outlineNormals ); } unsigned wallVertPtr = 0; unsigned roofVertPtr = 0; unsigned baseVertPtr = 0; double targetLen = -DBL_MAX; osg::Vec3d minLoc(DBL_MAX, DBL_MAX, DBL_MAX); double minLoc_len = DBL_MAX; osg::Vec3d maxLoc(0,0,0); double maxLoc_len = 0; // Initial pass over the geometry does two things: // 1: Calculate the minimum Z across all parts. // 2: Establish a "target length" for extrusion double absHeight = fabs(height); ConstGeometryIterator zfinder( input ); while( zfinder.hasMore() ) { const Geometry* geom = zfinder.next(); for( Geometry::const_iterator m = geom->begin(); m != geom->end(); ++m ) { osg::Vec3d m_point = *m; if ( m_point.z() + absHeight > targetLen ) targetLen = m_point.z() + absHeight; if (m_point.z() < minLoc.z()) minLoc = m_point; if (m_point.z() > maxLoc.z()) maxLoc = m_point; } } // apply the height offsets height -= heightOffset; targetLen -= heightOffset; // now generate the extruded geometry. ConstGeometryIterator iter( input ); while( iter.hasMore() ) { const Geometry* part = iter.next(); double tex_height_m_adj = tex_height_m; unsigned wallPartPtr = wallVertPtr; unsigned roofPartPtr = roofVertPtr; unsigned basePartPtr = baseVertPtr; double partLen = 0.0; double maxHeight = 0.0; maxHeight = targetLen - minLoc.z(); // Adjust the texture height so it is a multiple of the maximum height double div = osg::round(maxHeight / tex_height_m); if (div == 0) div = 1; //Prevent divide by zero tex_height_m_adj = maxHeight / div; //osg::DrawElementsUShort* idx = new osg::DrawElementsUShort( GL_TRIANGLES ); osg::DrawElementsUInt* idx = new osg::DrawElementsUInt( GL_TRIANGLES ); for( Geometry::const_iterator m = part->begin(); m != part->end(); ++m ) { osg::Vec3d basePt = *m; osg::Vec3d roofPt; if ( height >= 0 ) { if ( flatten ) roofPt.set( basePt.x(), basePt.y(), targetLen ); else roofPt.set( basePt.x(), basePt.y(), basePt.z() + height ); } else // height < 0 { roofPt = *m; basePt.z() += height; } // add to the approprate vertex lists: int p = wallVertPtr; // figure out the rooftop texture coordinates before doing any // transformations: if ( roofSkin && roofProjSRS && srs ) { double xr, yr; if ( srs && srs->isGeographic() ) { osg::Vec3d projRoofPt; srs->transform( roofPt, roofProjSRS.get(), projRoofPt ); xr = (projRoofPt.x() - roofBounds.xMin()); yr = (projRoofPt.y() - roofBounds.yMin()); } else { xr = (roofPt.x() - roofBounds.xMin()); yr = (roofPt.y() - roofBounds.yMin()); } float u = (cosR*xr - sinR*yr) / roofTexSpanX; float v = (sinR*xr + cosR*yr) / roofTexSpanY; (*roofTexcoords)[roofVertPtr].set( u, v ); } transformAndLocalize( basePt, srs, basePt, mapSRS, _world2local, makeECEF ); transformAndLocalize( roofPt, srs, roofPt, mapSRS, _world2local, makeECEF ); if ( base ) { (*baseVerts)[baseVertPtr] = basePt; } if ( roof ) { (*roofVerts)[roofVertPtr] = roofPt; } baseVertPtr++; roofVertPtr++; (*verts)[p] = roofPt; (*verts)[p+1] = basePt; if ( useColor ) { (*colors)[p+1] = wallBaseColor; } if ( outline ) { (*outlineVerts)[p] = roofPt; (*outlineVerts)[p+1] = basePt; } partLen += wallVertPtr > wallPartPtr ? ((*verts)[p] - (*verts)[p-2]).length() : 0.0; double h = tex_repeats_y ? -((*verts)[p] - (*verts)[p+1]).length() : -tex_height_m_adj; if ( wallSkin ) { (*wallTexcoords)[p].set( partLen/tex_width_m, 0.0f ); (*wallTexcoords)[p+1].set( partLen/tex_width_m, h/tex_height_m_adj ); } // form the 2 triangles if ( (m+1) == part->end() ) { if ( isPolygon ) { // end of the wall; loop around to close it off. if ( isSkinnedPolygon ) { // if we requested an extra geometry point, that means we are generating // a polygon-closing line so we can have a unique texcoord for it. idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+2); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+3); idx->push_back(wallVertPtr+2); (*verts)[p+2] = (*verts)[wallPartPtr]; (*verts)[p+3] = (*verts)[wallPartPtr+1]; if ( wallSkin ) { partLen += ((*verts)[p+2] - (*verts)[p]).length(); double h = tex_repeats_y ? -((*verts)[p+2] - (*verts)[p+3]).length() : -tex_height_m_adj; (*wallTexcoords)[p+2].set( partLen/tex_width_m, 0.0f ); (*wallTexcoords)[p+3].set( partLen/tex_width_m, h/tex_height_m_adj ); } wallVertPtr += 2; } else { // either not a poly, or no wall skin, so we can share the polygon-closing // loop point. idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallPartPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallPartPtr+1); idx->push_back(wallPartPtr); } } else { //nop - no elements required at the end of a line } } else { idx->push_back(wallVertPtr); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+2); idx->push_back(wallVertPtr+1); idx->push_back(wallVertPtr+3); idx->push_back(wallVertPtr+2); } wallVertPtr += 2; made_geom = true; } walls->addPrimitiveSet( idx ); if ( roof ) { roof->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_LOOP, roofPartPtr, roofVertPtr - roofPartPtr ) ); } if ( base ) { // reverse the base verts: int len = baseVertPtr - basePartPtr; for( int i=basePartPtr; i<len/2; i++ ) std::swap( (*baseVerts)[i], (*baseVerts)[basePartPtr+(len-1)-i] ); base->addPrimitiveSet( new osg::DrawArrays( osg::PrimitiveSet::LINE_LOOP, basePartPtr, baseVertPtr - basePartPtr ) ); } if ( outline ) { unsigned len = baseVertPtr - basePartPtr; GLenum roofLineMode = isPolygon ? GL_LINE_LOOP : GL_LINE_STRIP; osg::DrawElementsUInt* roofLine = new osg::DrawElementsUInt( roofLineMode ); roofLine->reserveElements( len ); for( unsigned i=0; i<len; ++i ) roofLine->addElement( basePartPtr + i*2 ); outline->addPrimitiveSet( roofLine ); // if the outline is tessellated, we only want outlines on the original // points (not the inserted points) unsigned step = std::max( 1u, _outlineSymbol->tessellation().isSet() ? *_outlineSymbol->tessellation() : 1u ); osg::DrawElementsUInt* wallLines = new osg::DrawElementsUInt( GL_LINES ); wallLines->reserve( len*2 ); for( unsigned i=0; i<len; i+=step ) { wallLines->push_back( basePartPtr + i*2 ); wallLines->push_back( basePartPtr + i*2 + 1 ); } outline->addPrimitiveSet( wallLines ); applyLineSymbology( outline->getOrCreateStateSet(), _outlineSymbol.get() ); } } return made_geom; }
osg::Node* GeometryCompiler::compile(FeatureList& workingSet, const Style& style, const FilterContext& context) { #ifdef PROFILING osg::Timer_t p_start = osg::Timer::instance()->tick(); unsigned p_features = workingSet.size(); #endif // for debugging/validation. std::vector<std::string> history; bool trackHistory = (_options.validate() == true); osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // create a filter context that will track feature data through the process FilterContext sharedCX = context; if ( !sharedCX.extent().isSet() && sharedCX.profile() ) { sharedCX.extent() = sharedCX.profile()->getExtent(); } // ref_ptr's to hold defaults in case we need them. osg::ref_ptr<PointSymbol> defaultPoint; osg::ref_ptr<LineSymbol> defaultLine; osg::ref_ptr<PolygonSymbol> defaultPolygon; // go through the Style and figure out which filters to use. const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); const MarkerSymbol* marker = style.get<MarkerSymbol>(); // to be deprecated const IconSymbol* icon = style.get<IconSymbol>(); const ModelSymbol* model = style.get<ModelSymbol>(); // Perform tessellation first. if ( line ) { if ( line->tessellation().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setNumPartitions( *line->tessellation() ); filter.setDefaultGeoInterp( _options.geoInterp().get() ); sharedCX = filter.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "tessellation" ); } else if ( line->tessellationSize().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setMaxPartitionSize( *line->tessellationSize() ); filter.setDefaultGeoInterp( _options.geoInterp().get() ); sharedCX = filter.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "tessellationSize" ); } } // if the style was empty, use some defaults based on the geometry type of the // first feature. if ( !point && !line && !polygon && !marker && !extrusion && !text && !model && !icon && workingSet.size() > 0 ) { Feature* first = workingSet.begin()->get(); Geometry* geom = first->getGeometry(); if ( geom ) { switch( geom->getComponentType() ) { case Geometry::TYPE_LINESTRING: case Geometry::TYPE_RING: defaultLine = new LineSymbol(); line = defaultLine.get(); break; case Geometry::TYPE_POINTSET: defaultPoint = new PointSymbol(); point = defaultPoint.get(); break; case Geometry::TYPE_POLYGON: defaultPolygon = new PolygonSymbol(); polygon = defaultPolygon.get(); break; case Geometry::TYPE_MULTI: case Geometry::TYPE_UNKNOWN: break; } } } // resample the geometry if necessary: if (_options.resampleMode().isSet()) { ResampleFilter resample; resample.resampleMode() = *_options.resampleMode(); if (_options.resampleMaxLength().isSet()) { resample.maxLength() = *_options.resampleMaxLength(); } sharedCX = resample.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "resample" ); } // check whether we need to do elevation clamping: bool altRequired = _options.ignoreAltitudeSymbol() != true && altitude && ( altitude->clamping() != AltitudeSymbol::CLAMP_NONE || altitude->verticalOffset().isSet() || altitude->verticalScale().isSet() || altitude->script().isSet() ); // marker substitution -- to be deprecated in favor of model/icon if ( marker ) { if ( trackHistory ) history.push_back( "marker" ); // use a separate filter context since we'll be munging the data FilterContext markerCX = sharedCX; if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); markerCX = scatter.push( workingSet, markerCX ); if ( trackHistory ) history.push_back( "scatter" ); } else if ( marker->placement() == MarkerSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; markerCX = centroid.push( workingSet, markerCX ); if ( trackHistory ) history.push_back( "centroid" ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); markerCX = clamp.push( workingSet, markerCX ); if ( trackHistory ) history.push_back( "altitude" ); // don't set this; we changed the input data. //altRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); sub.setUseDrawInstanced( *_options.instancing() ); if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, markerCX ); if ( node ) { if ( trackHistory ) history.push_back( "substitute" ); resultGroup->addChild( node ); } } // instance substitution (replaces marker) else if ( model ) { const InstanceSymbol* instance = model ? (const InstanceSymbol*)model : (const InstanceSymbol*)icon; // use a separate filter context since we'll be munging the data FilterContext localCX = sharedCX; if ( trackHistory ) history.push_back( "model"); if ( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM || instance->placement() == InstanceSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *instance->density() ); scatter.setRandom( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *instance->randomSeed() ); localCX = scatter.push( workingSet, localCX ); if ( trackHistory ) history.push_back( "scatter" ); } else if ( instance->placement() == InstanceSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; localCX = centroid.push( workingSet, localCX ); if ( trackHistory ) history.push_back( "centroid" ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); localCX = clamp.push( workingSet, localCX ); if ( trackHistory ) history.push_back( "altitude" ); } SubstituteModelFilter sub( style ); // activate clustering sub.setClustering( *_options.clustering() ); // activate draw-instancing sub.setUseDrawInstanced( *_options.instancing() ); // activate feature naming if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, localCX ); if ( node ) { if ( trackHistory ) history.push_back( "substitute" ); resultGroup->addChild( node ); // enable auto scaling on the group? if ( model && model->autoScale() == true ) { resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN ); } } } // extruded geometry if ( extrusion ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "altitude" ); altRequired = false; } ExtrudeGeometryFilter extrude; extrude.setStyle( style ); // apply per-feature naming if requested. if ( _options.featureName().isSet() ) extrude.setFeatureNameExpr( *_options.featureName() ); if ( _options.mergeGeometry().isSet() ) extrude.setMergeGeometry( *_options.mergeGeometry() ); osg::Node* node = extrude.push( workingSet, sharedCX ); if ( node ) { if ( trackHistory ) history.push_back( "extrude" ); resultGroup->addChild( node ); } } // simple geometry else if ( point || line || polygon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "altitude" ); altRequired = false; } BuildGeometryFilter filter( style ); filter.maxGranularity() = *_options.maxGranularity(); filter.geoInterp() = *_options.geoInterp(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { if ( trackHistory ) history.push_back( "geometry" ); resultGroup->addChild( node ); } } if ( text || icon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); if ( trackHistory ) history.push_back( "altitude" ); altRequired = false; } BuildTextFilter filter( style ); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { if ( trackHistory ) history.push_back( "text" ); resultGroup->addChild( node ); } } if (Registry::capabilities().supportsGLSL()) { if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE ) { // no ss cache because we will optimize later. Registry::shaderGenerator().run( resultGroup.get(), "osgEarth.GeomCompiler" ); } else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE ) { resultGroup->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE ); if ( trackHistory ) history.push_back( "no shaders" ); } } // Optimize stateset sharing. if ( _options.optimizeStateSharing() == true ) { // Common state set cache? osg::ref_ptr<StateSetCache> sscache; if ( sharedCX.getSession() ) { // with a shared cache, don't combine statesets. They may be // in the live graph sscache = sharedCX.getSession()->getStateSetCache(); sscache->consolidateStateAttributes( resultGroup.get() ); } else { // isolated: perform full optimization sscache = new StateSetCache(); sscache->optimize( resultGroup.get() ); } if ( trackHistory ) history.push_back( "share state" ); } if ( _options.optimize() == true ) { OE_DEBUG << LC << "optimize begin" << std::endl; // Run the optimizer on the resulting graph int optimizations = osgUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS | osgUtil::Optimizer::REMOVE_REDUNDANT_NODES | osgUtil::Optimizer::COMBINE_ADJACENT_LODS | osgUtil::Optimizer::SHARE_DUPLICATE_STATE | osgUtil::Optimizer::MERGE_GEOMETRY | osgUtil::Optimizer::CHECK_GEOMETRY | osgUtil::Optimizer::MERGE_GEODES | osgUtil::Optimizer::STATIC_OBJECT_DETECTION; osgUtil::Optimizer opt; opt.optimize(resultGroup.get(), optimizations); OE_DEBUG << LC << "optimize complete" << std::endl; if ( trackHistory ) history.push_back( "optimize" ); } //test: dump the tile to disk //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); #ifdef PROFILING static double totalTime = 0.0; static Threading::Mutex totalTimeMutex; osg::Timer_t p_end = osg::Timer::instance()->tick(); double t = osg::Timer::instance()->delta_s(p_start, p_end); totalTimeMutex.lock(); totalTime += t; totalTimeMutex.unlock(); OE_INFO << LC << "features = " << p_features << ", time = " << t << " s. cummulative = " << totalTime << " s." << std::endl; #endif if ( _options.validate() == true ) { OE_NOTICE << LC << "-- Start Debugging --\n"; std::stringstream buf; buf << "HISTORY "; for(std::vector<std::string>::iterator h = history.begin(); h != history.end(); ++h) buf << ".. " << *h; OE_NOTICE << LC << buf.str() << "\n"; osgEarth::GeometryValidator validator; resultGroup->accept(validator); OE_NOTICE << LC << "-- End Debugging --\n"; } return resultGroup.release(); }
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(); }
FilterContext CropFilter::push( FeatureList& input, FilterContext& context ) { if ( !context.extent().isSet() ) { OE_WARN << LC << "Extent is not set (and is required)" << std::endl; return context; } const GeoExtent& extent = *context.extent(); GeoExtent newExtent( extent.getSRS() ); if ( _method == METHOD_CENTROID ) { for( FeatureList::iterator i = input.begin(); i != input.end(); ) { bool keepFeature = false; Feature* feature = i->get(); Geometry* featureGeom = feature->getGeometry(); if ( featureGeom && featureGeom->isValid() ) { Bounds bounds = featureGeom->getBounds(); if ( bounds.isValid() ) { osg::Vec3d centroid = bounds.center(); if ( extent.contains( centroid.x(), centroid.y() ) ) { keepFeature = true; newExtent.expandToInclude( bounds.xMin(), bounds.yMin() ); } } } if ( keepFeature ) ++i; else i = input.erase( i ); } } else // METHOD_CROPPING (requires GEOS) { #ifdef OSGEARTH_HAVE_GEOS // create the intersection polygon: osg::ref_ptr<Symbology::Polygon> poly; for( FeatureList::iterator i = input.begin(); i != input.end(); ) { bool keepFeature = false; Feature* feature = i->get(); Symbology::Geometry* featureGeom = feature->getGeometry(); if ( featureGeom && featureGeom->isValid() ) { // test for trivial acceptance: const Bounds bounds = featureGeom->getBounds(); if ( !bounds.isValid() ) { //nop } else if ( extent.contains( bounds ) ) { keepFeature = true; newExtent.expandToInclude( bounds ); } // then move on to the cropping operation: else { if ( !poly.valid() ) { poly = new Symbology::Polygon(); poly->push_back( osg::Vec3d( extent.xMin(), extent.yMin(), 0 )); poly->push_back( osg::Vec3d( extent.xMax(), extent.yMin(), 0 )); poly->push_back( osg::Vec3d( extent.xMax(), extent.yMax(), 0 )); poly->push_back( osg::Vec3d( extent.xMin(), extent.yMax(), 0 )); } osg::ref_ptr<Geometry> croppedGeometry; if ( featureGeom->crop( poly.get(), croppedGeometry ) ) { if ( croppedGeometry->isValid() ) { feature->setGeometry( croppedGeometry.get() ); keepFeature = true; newExtent.expandToInclude( croppedGeometry->getBounds() ); } } } } if ( keepFeature ) ++i; else i = input.erase( i ); } #else // OSGEARTH_HAVE_GEOS OE_WARN << "CropFilter - METHOD_CROPPING not available - please compile osgEarth with GEOS" << std::endl; return context; #endif } FilterContext newContext = context; newContext.extent() = newExtent; return newContext; }
osg::Node* GeometryCompiler::compile(FeatureList& workingSet, const Style& style, const FilterContext& context) { #ifdef PROFILING osg::Timer_t p_start = osg::Timer::instance()->tick(); unsigned p_features = workingSet.size(); #endif osg::ref_ptr<osg::Group> resultGroup = new osg::Group(); // create a filter context that will track feature data through the process FilterContext sharedCX = context; if ( !sharedCX.extent().isSet() && sharedCX.profile() ) { sharedCX.extent() = sharedCX.profile()->getExtent(); } // ref_ptr's to hold defaults in case we need them. osg::ref_ptr<PointSymbol> defaultPoint; osg::ref_ptr<LineSymbol> defaultLine; osg::ref_ptr<PolygonSymbol> defaultPolygon; // go through the Style and figure out which filters to use. const PointSymbol* point = style.get<PointSymbol>(); const LineSymbol* line = style.get<LineSymbol>(); const PolygonSymbol* polygon = style.get<PolygonSymbol>(); const ExtrusionSymbol* extrusion = style.get<ExtrusionSymbol>(); const AltitudeSymbol* altitude = style.get<AltitudeSymbol>(); const TextSymbol* text = style.get<TextSymbol>(); const MarkerSymbol* marker = style.get<MarkerSymbol>(); // to be deprecated const IconSymbol* icon = style.get<IconSymbol>(); const ModelSymbol* model = style.get<ModelSymbol>(); // check whether we need tessellation: if ( line && line->tessellation().isSet() ) { TemplateFeatureFilter<TessellateOperator> filter; filter.setNumPartitions( *line->tessellation() ); sharedCX = filter.push( workingSet, sharedCX ); } // if the style was empty, use some defaults based on the geometry type of the // first feature. if ( !point && !line && !polygon && !marker && !extrusion && !text && !model && !icon && workingSet.size() > 0 ) { Feature* first = workingSet.begin()->get(); Geometry* geom = first->getGeometry(); if ( geom ) { switch( geom->getComponentType() ) { case Geometry::TYPE_LINESTRING: case Geometry::TYPE_RING: defaultLine = new LineSymbol(); line = defaultLine.get(); break; case Geometry::TYPE_POINTSET: defaultPoint = new PointSymbol(); point = defaultPoint.get(); break; case Geometry::TYPE_POLYGON: defaultPolygon = new PolygonSymbol(); polygon = defaultPolygon.get(); break; case Geometry::TYPE_MULTI: case Geometry::TYPE_UNKNOWN: break; } } } // resample the geometry if necessary: if (_options.resampleMode().isSet()) { ResampleFilter resample; resample.resampleMode() = *_options.resampleMode(); if (_options.resampleMaxLength().isSet()) { resample.maxLength() = *_options.resampleMaxLength(); } sharedCX = resample.push( workingSet, sharedCX ); } // check whether we need to do elevation clamping: bool altRequired = _options.ignoreAltitudeSymbol() != true && altitude && ( altitude->clamping() != AltitudeSymbol::CLAMP_NONE || altitude->verticalOffset().isSet() || altitude->verticalScale().isSet() || altitude->script().isSet() ); // marker substitution -- to be deprecated in favor of model/icon if ( marker ) { // use a separate filter context since we'll be munging the data FilterContext markerCX = sharedCX; if ( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM || marker->placement() == MarkerSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *marker->density() ); scatter.setRandom( marker->placement() == MarkerSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *marker->randomSeed() ); markerCX = scatter.push( workingSet, markerCX ); } else if ( marker->placement() == MarkerSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; centroid.push( workingSet, markerCX ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); markerCX = clamp.push( workingSet, markerCX ); // don't set this; we changed the input data. //altRequired = false; } SubstituteModelFilter sub( style ); sub.setClustering( *_options.clustering() ); sub.setUseDrawInstanced( *_options.instancing() ); if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, markerCX ); if ( node ) { resultGroup->addChild( node ); } } // instance substitution (replaces marker) else if ( model ) { const InstanceSymbol* instance = model ? (const InstanceSymbol*)model : (const InstanceSymbol*)icon; // use a separate filter context since we'll be munging the data FilterContext localCX = sharedCX; if ( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM || instance->placement() == InstanceSymbol::PLACEMENT_INTERVAL ) { ScatterFilter scatter; scatter.setDensity( *instance->density() ); scatter.setRandom( instance->placement() == InstanceSymbol::PLACEMENT_RANDOM ); scatter.setRandomSeed( *instance->randomSeed() ); localCX = scatter.push( workingSet, localCX ); } else if ( instance->placement() == InstanceSymbol::PLACEMENT_CENTROID ) { CentroidFilter centroid; centroid.push( workingSet, localCX ); } if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); localCX = clamp.push( workingSet, localCX ); } SubstituteModelFilter sub( style ); // activate clustering sub.setClustering( *_options.clustering() ); // activate draw-instancing sub.setUseDrawInstanced( *_options.instancing() ); // activate feature naming if ( _options.featureName().isSet() ) sub.setFeatureNameExpr( *_options.featureName() ); osg::Node* node = sub.push( workingSet, localCX ); if ( node ) { resultGroup->addChild( node ); // enable auto scaling on the group? if ( model && model->autoScale() == true ) { resultGroup->getOrCreateStateSet()->setRenderBinDetails(0, osgEarth::AUTO_SCALE_BIN ); } } } // extruded geometry if ( extrusion ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } ExtrudeGeometryFilter extrude; extrude.setStyle( style ); // Activate texture arrays if the GPU supports them and if the user // hasn't disabled them. extrude.useTextureArrays() = Registry::capabilities().supportsTextureArrays() && _options.useTextureArrays() == true; // apply per-feature naming if requested. if ( _options.featureName().isSet() ) extrude.setFeatureNameExpr( *_options.featureName() ); if ( _options.useVertexBufferObjects().isSet()) extrude.useVertexBufferObjects() = *_options.useVertexBufferObjects(); osg::Node* node = extrude.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } // simple geometry else if ( point || line || polygon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } BuildGeometryFilter filter( style ); if ( _options.maxGranularity().isSet() ) filter.maxGranularity() = *_options.maxGranularity(); if ( _options.geoInterp().isSet() ) filter.geoInterp() = *_options.geoInterp(); if ( _options.featureName().isSet() ) filter.featureName() = *_options.featureName(); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } if ( text || icon ) { if ( altRequired ) { AltitudeFilter clamp; clamp.setPropertiesFromStyle( style ); sharedCX = clamp.push( workingSet, sharedCX ); altRequired = false; } BuildTextFilter filter( style ); osg::Node* node = filter.push( workingSet, sharedCX ); if ( node ) { resultGroup->addChild( node ); } } if (Registry::capabilities().supportsGLSL()) { if ( _options.shaderPolicy() == SHADERPOLICY_GENERATE ) { // no ss cache because we will optimize later. Registry::shaderGenerator().run( resultGroup.get(), "osgEarth.GeomCompiler" ); } else if ( _options.shaderPolicy() == SHADERPOLICY_DISABLE ) { resultGroup->getOrCreateStateSet()->setAttributeAndModes( new osg::Program(), osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE ); } } // Optimize stateset sharing. if ( _options.optimizeStateSharing() == true ) { // Common state set cache? osg::ref_ptr<StateSetCache> sscache; if ( sharedCX.getSession() ) { // with a shared cache, don't combine statesets. They may be // in the live graph sscache = sharedCX.getSession()->getStateSetCache(); sscache->consolidateStateAttributes( resultGroup.get() ); } else { // isolated: perform full optimization sscache = new StateSetCache(); sscache->optimize( resultGroup.get() ); } } //test: dump the tile to disk //osgDB::writeNodeFile( *(resultGroup.get()), "out.osg" ); #ifdef PROFILING osg::Timer_t p_end = osg::Timer::instance()->tick(); OE_INFO << LC << "features = " << p_features << ", time = " << osg::Timer::instance()->delta_s(p_start, p_end) << " s." << std::endl; #endif return resultGroup.release(); }
void FeaturesToNodeFilter::computeLocalizers( const FilterContext& context ) { computeLocalizers(context, context.extent().get(), _world2local, _local2world); }
FeatureCursor* createFeatureCursor(const Symbology::Query& query, ProgressCallback* progress) { FeatureCursor* result = 0L; std::string url = createURL( query ); // the URL wil lbe empty if it was invalid or outside the level bounds of the layer. if (url.empty()) return 0L; OE_DEBUG << LC << url << std::endl; URI uri(url, _options.url()->context()); // read the data: ReadResult r = uri.readString(_readOptions.get(), progress); const std::string& buffer = r.getString(); const Config& meta = r.metadata(); bool dataOK = false; FeatureList features; if ( !buffer.empty() ) { // Get the mime-type from the metadata record if possible std::string mimeType = r.metadata().value( IOMetadata::CONTENT_TYPE ); //If the mimetype is empty then try to set it from the format specification if (mimeType.empty()) { if (_options.format().value() == "json") mimeType = "json"; else if (_options.format().value().compare("gml") == 0) mimeType = "text/xml"; else if (_options.format().value().compare("pbf") == 0) mimeType = "application/x-protobuf"; } dataOK = getFeatures( buffer, *query.tileKey(), mimeType, features ); } if ( dataOK ) { OE_DEBUG << LC << "Read " << features.size() << " features" << std::endl; } //If we have any filters, process them here before the cursor is created if (getFilters() && !getFilters()->empty() && !features.empty()) { FilterContext cx; cx.setProfile(getFeatureProfile()); cx.extent() = query.tileKey()->getExtent(); for (FeatureFilterChain::const_iterator i = getFilters()->begin(); i != getFilters()->end(); ++i) { FeatureFilter* filter = i->get(); cx = filter->push(features, cx); } } // If we have any features and we have an fid attribute, override the fid of the features if (_options.fidAttribute().isSet()) { for (FeatureList::iterator itr = features.begin(); itr != features.end(); ++itr) { std::string attr = itr->get()->getString(_options.fidAttribute().get()); FeatureID fid = as<long>(attr, 0); itr->get()->setFID( fid ); } } result = new FeatureListCursor(features); 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; }