GeoImage GeoImage::crop( const GeoExtent& extent, bool exact, unsigned int width, unsigned int height ) const { //Check for equivalence if ( extent.getSRS()->isEquivalentTo( getSRS() ) ) { //If we want an exact crop or they want to specify the output size of the image, use GDAL if (exact || width != 0 || height != 0 ) { OE_DEBUG << "[osgEarth::GeoImage::crop] Performing exact crop" << std::endl; //Suggest an output image size if (width == 0 || height == 0) { double xRes = (getExtent().xMax() - getExtent().xMin()) / (double)_image->s(); double yRes = (getExtent().yMax() - getExtent().yMin()) / (double)_image->t(); width = osg::maximum(1u, (unsigned int)((extent.xMax() - extent.xMin()) / xRes)); height = osg::maximum(1u, (unsigned int)((extent.yMax() - extent.yMin()) / yRes)); OE_DEBUG << "[osgEarth::GeoImage::crop] Computed output image size " << width << "x" << height << std::endl; } //Note: Passing in the current SRS simply forces GDAL to not do any warping return reproject( getSRS(), &extent, width, height); } else { OE_DEBUG << "[osgEarth::GeoImage::crop] Performing non-exact crop " << std::endl; //If an exact crop is not desired, we can use the faster image cropping code that does no resampling. double destXMin = extent.xMin(); double destYMin = extent.yMin(); double destXMax = extent.xMax(); double destYMax = extent.yMax(); osg::Image* new_image = ImageUtils::cropImage( _image.get(), _extent.xMin(), _extent.yMin(), _extent.xMax(), _extent.yMax(), destXMin, destYMin, destXMax, destYMax ); //The destination extents may be different than the input extents due to not being able to crop along pixel boundaries. return new_image? GeoImage( new_image, GeoExtent( getSRS(), destXMin, destYMin, destXMax, destYMax ) ) : GeoImage::INVALID; } } else { //TODO: just reproject the image before cropping OE_NOTICE << "[osgEarth::GeoImage::crop] Cropping extent does not have equivalent SpatialReference" << std::endl; return GeoImage::INVALID; } }
osg::Node* SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary) { // compile the model into a node: TileNode* tileNode = _modelCompiler->compile( model, _frame ); // see if this tile might have children. bool prepareForChildren = setupChildrenIfNecessary && model->_tileKey.getLOD() < *_options.maxLOD(); osg::Node* result = 0L; if ( prepareForChildren ) { //Compute the min range based on the 2D size of the tile osg::BoundingSphere bs = tileNode->getBound(); GeoExtent extent = model->_tileKey.getExtent(); GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radius = (ur - ll).length() / 2.0; float minRange = (float)(radius * _options.minTileRangeFactor().value()); TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles ); plod->setCenter ( bs.center() ); plod->addChild ( tileNode ); plod->setRange ( 0, minRange, FLT_MAX ); plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" ); plod->setRange ( 1, 0, minRange ); #if USE_FILELOCATIONCALLBACK osgDB::Options* options = Registry::instance()->cloneOrCreateOptions(); options->setFileLocationCallback( new FileLocationCallback() ); plod->setDatabaseOptions( options ); #endif result = plod; // this one rejects back-facing tiles: if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true ) { osg::HeightField* hf = model->_elevationData.getHeightField(); result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( hf, tileNode->getKey().getProfile()->getSRS()->getEllipsoid(), *_options.verticalScale() ) ); } } else { result = tileNode; } return result; }
GeoImage* GeoImage::reproject(const SpatialReference* to_srs, const GeoExtent* to_extent, unsigned int width, unsigned int height) const { GeoExtent destExtent; if (to_extent) { destExtent = *to_extent; } else { destExtent = getExtent().transform(to_srs); } osg::Image* resultImage = 0L; if ( getSRS()->isUserDefined() || to_srs->isUserDefined() ) { // if either of the SRS is a custom projection, we have to do a manual reprojection since // GDAL will not recognize the SRS. resultImage = manualReproject(getImage(), getExtent(), *to_extent, width, height); } else { // otherwise use GDAL. resultImage = reprojectImage(getImage(), getSRS()->getWKT(), getExtent().xMin(), getExtent().yMin(), getExtent().xMax(), getExtent().yMax(), to_srs->getWKT(), destExtent.xMin(), destExtent.yMin(), destExtent.xMax(), destExtent.yMax(), width, height); } return new GeoImage(resultImage, destExtent); }
osg::Node* SerialKeyNodeFactory::createTile(TileModel* model, bool tileHasRealData) { // compile the model into a node: TileNode* tileNode = _modelCompiler->compile( model ); // see if this tile might have children. bool prepareForChildren = (tileHasRealData || (_options.minLOD().isSet() && model->_tileKey.getLOD() < *_options.minLOD())) && model->_tileKey.getLOD() < *_options.maxLOD(); osg::Node* result = 0L; if ( prepareForChildren ) { //Compute the min range based on the 2D size of the tile osg::BoundingSphere bs = tileNode->getBound(); GeoExtent extent = model->_tileKey.getExtent(); GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radius = (ur - ll).length() / 2.0; float minRange = (float)(radius * _options.minTileRangeFactor().value()); osgDB::Options* dbOptions = Registry::instance()->cloneOrCreateOptions(); TileGroup* plod = new TileGroup(tileNode, _engineUID, _liveTiles.get(), _deadTiles.get(), dbOptions); plod->setSubtileRange( minRange ); #if USE_FILELOCATIONCALLBACK dbOptions->setFileLocationCallback( new FileLocationCallback() ); #endif result = plod; } else { result = tileNode; } // this one rejects back-facing tiles: if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true ) { osg::HeightField* hf = model->_elevationData.getHeightField(); result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( hf, tileNode->getKey().getProfile()->getSRS()->getEllipsoid(), *_options.verticalScale() ) ); } return result; }
TileMap* TileMap::create(const std::string& url, const Profile* profile, const DataExtentList& dataExtents, const std::string& format, int tile_width, int tile_height) { const GeoExtent& ex = profile->getExtent(); TileMap* tileMap = new TileMap(); tileMap->setProfileType(profile->getProfileType()); tileMap->setExtents(ex.xMin(), ex.yMin(), ex.xMax(), ex.yMax()); tileMap->setOrigin(ex.xMin(), ex.yMin()); tileMap->_filename = url; tileMap->_srs = getHorizSRSString(profile->getSRS()); tileMap->_vsrs = profile->getSRS()->getVertInitString(); tileMap->_format.setWidth( tile_width ); tileMap->_format.setHeight( tile_height ); profile->getNumTiles( 0, tileMap->_numTilesWide, tileMap->_numTilesHigh ); // format can be a mime-type or an extension: std::string::size_type p = format.find('/'); if ( p == std::string::npos ) { tileMap->_format.setExtension(format); tileMap->_format.setMimeType( Registry::instance()->getMimeTypeForExtension(format) ); } else { tileMap->_format.setMimeType(format); tileMap->_format.setExtension( Registry::instance()->getExtensionForMimeType(format) ); } //Add the data extents tileMap->getDataExtents().insert(tileMap->getDataExtents().end(), dataExtents.begin(), dataExtents.end()); // If we have some data extents specified then make a nicer bounds than the if (!tileMap->getDataExtents().empty()) { // Get the union of all the extents GeoExtent e(tileMap->getDataExtents()[0]); for (unsigned int i = 1; i < tileMap->getDataExtents().size(); i++) { e.expandToInclude(tileMap->getDataExtents()[i]); } // Convert the bounds to the output profile GeoExtent bounds = e.transform(profile->getSRS()); tileMap->setExtents(bounds.xMin(), bounds.yMin(), bounds.xMax(), bounds.yMax()); } tileMap->generateTileSets(); tileMap->computeMinMaxLevel(); return tileMap; }
void Profile::addIntersectingTiles(const GeoExtent& key_ext, unsigned localLOD, std::vector<TileKey>& out_intersectingKeys) const { // assume a non-crossing extent here. if ( key_ext.crossesAntimeridian() ) { OE_WARN << "Profile::addIntersectingTiles cannot process date-line cross" << std::endl; return; } int tileMinX, tileMaxX; int tileMinY, tileMaxY; double destTileWidth, destTileHeight; getTileDimensions(localLOD, destTileWidth, destTileHeight); //OE_DEBUG << std::fixed << " Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl; //OE_DEBUG << std::fixed << " Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl; double east = key_ext.xMax() - _extent.xMin(); bool xMaxOnTileBoundary = fmod(east, destTileWidth) == 0.0; double south = _extent.yMax() - key_ext.yMin(); bool yMaxOnTileBoundary = fmod(south, destTileHeight) == 0.0; tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth); tileMaxX = (int)(east / destTileWidth) - (xMaxOnTileBoundary ? 1 : 0); tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); tileMaxY = (int)(south / destTileHeight) - (yMaxOnTileBoundary ? 1 : 0); unsigned int numWide, numHigh; getNumTiles(localLOD, numWide, numHigh); // bail out if the tiles are out of bounds. if ( tileMinX >= (int)numWide || tileMinY >= (int)numHigh || tileMaxX < 0 || tileMaxY < 0 ) { return; } tileMinX = osg::clampBetween(tileMinX, 0, (int)numWide-1); tileMaxX = osg::clampBetween(tileMaxX, 0, (int)numWide-1); tileMinY = osg::clampBetween(tileMinY, 0, (int)numHigh-1); tileMaxY = osg::clampBetween(tileMaxY, 0, (int)numHigh-1); OE_DEBUG << std::fixed << " Dest Tiles: " << tileMinX << "," << tileMinY << " => " << tileMaxX << "," << tileMaxY << std::endl; for (int i = tileMinX; i <= tileMaxX; ++i) { for (int j = tileMinY; j <= tileMaxY; ++j) { //TODO: does not support multi-face destination keys. out_intersectingKeys.push_back( TileKey(localLOD, i, j, this) ); } } }
GeoLocator::GeoLocator( const osgTerrain::Locator& prototype, const GeoExtent& dataExtent, const GeoExtent& displayExtent ) : osgTerrain::Locator( prototype ), _dataExtent( dataExtent ), _inverseCalculated(false) { // assume they are the same SRS _x0 = osg::clampBetween( (displayExtent.xMin()-dataExtent.xMin())/dataExtent.width(), 0.0, 1.0 ); _x1 = osg::clampBetween( (displayExtent.xMax()-dataExtent.xMin())/dataExtent.width(), 0.0, 1.0 ); _y0 = osg::clampBetween( (displayExtent.yMin()-dataExtent.yMin())/dataExtent.height(), 0.0, 1.0 ); _y1 = osg::clampBetween( (displayExtent.yMax()-dataExtent.yMin())/dataExtent.height(), 0.0, 1.0 ); }
GeoExtent GeoExtent::intersectionSameSRS( const GeoExtent& rhs ) const { Bounds b( osg::maximum( xMin(), rhs.xMin() ), osg::maximum( yMin(), rhs.yMin() ), osg::minimum( xMax(), rhs.xMax() ), osg::minimum( yMax(), rhs.yMax() ) ); return b.width() > 0 && b.height() > 0 ? GeoExtent( getSRS(), b ) : GeoExtent::INVALID; }
GeoExtent UnifiedCubeProfile::transformGcsExtentOnFace( const GeoExtent& gcsExtent, int face ) const { if ( face < 4 ) { const GeoExtent& fex = _faceExtent_gcs[face]; return GeoExtent( getSRS(), (double)face + (gcsExtent.xMin()-fex.xMin()) / fex.width(), (gcsExtent.yMin()-fex.yMin()) / fex.height(), (double)face + (gcsExtent.xMax()-fex.xMin()) / fex.width(), (gcsExtent.yMax()-fex.yMin()) / fex.height() ); } else { // transform all 4 corners; then do the min/max for x/y. double lon[4] = { gcsExtent.xMin(), gcsExtent.xMax(), gcsExtent.xMax(), gcsExtent.xMin() }; double lat[4] = { gcsExtent.yMin(), gcsExtent.yMin(), gcsExtent.yMax(), gcsExtent.yMax() }; double x[4], y[4]; for( int i=0; i<4; ++i ) { int dummy; if ( ! CubeUtils::latLonToFaceCoords( lat[i], lon[i], x[i], y[i], dummy, face ) ) { OE_WARN << LC << "transformGcsExtentOnFace, ll2fc failed" << std::endl; } } double xmin = SMALLEST( x[0], x[1], x[2], x[3] ); double xmax = LARGEST( x[0], x[1], x[2], x[3] ); double ymin = SMALLEST( y[0], y[1], y[2], y[3] ); double ymax = LARGEST( y[0], y[1], y[2], y[3] ); CubeUtils::faceToCube( xmin, ymin, face ); CubeUtils::faceToCube( xmax, ymax, face ); return GeoExtent( getSRS(), xmin, ymin, xmax, ymax ); } }
bool GeoExtent::intersects( const GeoExtent& rhs ) const { bool valid = isValid(); if ( !valid ) return false; bool exclusive = _xmin > rhs.xMax() || _xmax < rhs.xMin() || _ymin > rhs.yMax() || _ymax < rhs.yMin(); return !exclusive; }
void Profile::addIntersectingTiles(const GeoExtent& key_ext, unsigned localLOD, std::vector<TileKey>& out_intersectingKeys) const { // assume a non-crossing extent here. if ( key_ext.crossesAntimeridian() ) { OE_WARN << "Profile::addIntersectingTiles cannot process date-line cross" << std::endl; return; } int tileMinX, tileMaxX; int tileMinY, tileMaxY; // Special path for mercator (does NOT work for cube, e.g.) if ( key_ext.getSRS()->isMercator() ) { int precision = 5; double eps = 0.001; double keyWidth = round(key_ext.width(), precision); int destLOD = 0; double w, h; getTileDimensions(0, w, h); for(; (round(w,precision) - keyWidth) > eps; w*=0.5, h*=0.5, destLOD++ ); double destTileWidth, destTileHeight; getTileDimensions( destLOD, destTileWidth, destTileHeight ); destTileWidth = round(destTileWidth, precision); destTileHeight = round(destTileHeight, precision); tileMinX = quantize( ((key_ext.xMin() - _extent.xMin()) / destTileWidth), eps ); tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth); tileMinY = quantize( ((_extent.yMax() - key_ext.yMax()) / destTileHeight), eps ); tileMaxY = (int) ((_extent.yMax() - key_ext.yMin()) / destTileHeight); } else { double destTileWidth, destTileHeight; getTileDimensions(localLOD, destTileWidth, destTileHeight); //OE_DEBUG << std::fixed << " Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl; //OE_DEBUG << std::fixed << " Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl; tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth); tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth); tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight); } unsigned int numWide, numHigh; getNumTiles(localLOD, numWide, numHigh); // bail out if the tiles are out of bounds. if ( tileMinX >= (int)numWide || tileMinY >= (int)numHigh || tileMaxX < 0 || tileMaxY < 0 ) { return; } tileMinX = osg::clampBetween(tileMinX, 0, (int)numWide-1); tileMaxX = osg::clampBetween(tileMaxX, 0, (int)numWide-1); tileMinY = osg::clampBetween(tileMinY, 0, (int)numHigh-1); tileMaxY = osg::clampBetween(tileMaxY, 0, (int)numHigh-1); OE_DEBUG << std::fixed << " Dest Tiles: " << tileMinX << "," << tileMinY << " => " << tileMaxX << "," << tileMaxY << std::endl; for (int i = tileMinX; i <= tileMaxX; ++i) { for (int j = tileMinY; j <= tileMaxY; ++j) { //TODO: does not support multi-face destination keys. out_intersectingKeys.push_back( TileKey(localLOD, i, j, this) ); } } }
void TFSPackager::package( FeatureSource* features, const std::string& destination, const std::string& layername, const std::string& description ) { if (!_destSRSString.empty()) { _srs = SpatialReference::create( _destSRSString ); } //Get the destination SRS from the feature source if it's not already set if (!_srs.valid()) { _srs = features->getFeatureProfile()->getSRS(); } //Get the extent of the dataset, or use the custom extent value GeoExtent srsExtent = _customExtent; if (!srsExtent.isValid()) srsExtent = features->getFeatureProfile()->getExtent(); //Transform to lat/lon extents GeoExtent extent = srsExtent.transform( _srs.get() ); osg::ref_ptr< const osgEarth::Profile > profile = osgEarth::Profile::create(extent.getSRS(), extent.xMin(), extent.yMin(), extent.xMax(), extent.yMax(), 1, 1); TileKey rootKey = TileKey(0, 0, 0, profile ); osg::ref_ptr< FeatureTile > root = new FeatureTile( rootKey ); //Loop through all the features and try to insert them into the quadtree osg::ref_ptr< FeatureCursor > cursor = features->createFeatureCursor( _query ); int added = 0; int failed = 0; int skipped = 0; int highestLevel = 0; while (cursor.valid() && cursor->hasMore()) { osg::ref_ptr< Feature > feature = cursor->nextFeature(); //Reproject the feature to the dest SRS if it's not already if (!feature->getSRS()->isEquivalentTo( _srs ) ) { feature->transform( _srs ); } if (feature->getGeometry() && feature->getGeometry()->getBounds().valid() && feature->getGeometry()->isValid()) { AddFeatureVisitor v(feature.get(), _maxFeatures, _firstLevel, _maxLevel, _method); root->accept( &v ); if (!v._added) { OE_NOTICE << "Failed to add feature " << feature->getFID() << std::endl; failed++; } else { if (highestLevel < v._levelAdded) { highestLevel = v._levelAdded; } added++; OE_DEBUG << "Added " << added << std::endl; } } else { OE_NOTICE << "Skipping feature " << feature->getFID() << " with null or invalid geometry" << std::endl; skipped++; } } OE_NOTICE << "Added=" << added << " Skipped=" << skipped << " Failed=" << failed << std::endl; #if 1 // Print the width of tiles at each level for (int i = 0; i <= highestLevel; ++i) { TileKey tileKey(i, 0, 0, profile); GeoExtent tileExtent = tileKey.getExtent(); OE_NOTICE << "Level " << i << " tile size: " << tileExtent.width() << std::endl; } #endif WriteFeaturesVisitor write(features, destination, _method, _srs); root->accept( &write ); //Write out the meta doc TFSLayer layer; layer.setTitle( layername ); layer.setAbstract( description ); layer.setFirstLevel( _firstLevel ); layer.setMaxLevel( highestLevel ); layer.setExtent( profile->getExtent() ); layer.setSRS( _srs.get() ); TFSReaderWriter::write( layer, osgDB::concatPaths( destination, "tfs.xml")); }
void SerialKeyNodeFactory::addTile(Tile* tile, bool tileHasRealData, bool tileHasLodBlending, osg::Group* parent ) { // associate this tile with the terrain: tile->setTerrainTechnique( _terrain->cloneTechnique() ); tile->attachToTerrain( _terrain ); // assemble a URI for this tile's child group: std::stringstream buf; buf << tile->getKey().str() << "." << _engineUID << ".osgearth_osgterrain_tile"; std::string uri; uri = buf.str(); osg::Node* result = 0L; // Only add the next tile if all the following are true: // 1. Either there's real tile data, or a maxLOD is explicity set in the options; // 2. The tile isn't blacklisted; and // 3. We are still below the max LOD. bool wrapInPagedLOD = (tileHasRealData || _options.maxLOD().isSet()) && !osgEarth::Registry::instance()->isBlacklisted( uri ) && tile->getKey().getLevelOfDetail() < (unsigned)*_options.maxLOD(); if ( wrapInPagedLOD ) { osg::BoundingSphere bs = tile->getBound(); double maxRange = 1e10; #if 0 //Compute the min range based on the actual bounds of the tile. This can break down if you have very high resolution //data with elevation variations and you can run out of memory b/c the elevation change is greater than the actual size of the tile so you end up //inifinitely subdividing (or at least until you run out of data or memory) double minRange = bs.radius() * _options.minTileRangeFactor().value(); #else //double origMinRange = bs.radius() * _options.minTileRangeFactor().value(); //Compute the min range based on the 2D size of the tile GeoExtent extent = tile->getKey().getExtent(); GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin()); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax()); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radius = (ur - ll).length() / 2.0; double minRange = radius * _options.minTileRangeFactor().value(); #endif // create a PLOD so we can keep subdividing: osg::PagedLOD* plod = new osg::PagedLOD(); plod->setCenter( bs.center() ); plod->addChild( tile, minRange, maxRange ); plod->setFileName( 1, uri ); plod->setRange ( 1, 0, minRange ); plod->setUserData( new MapNode::TileRangeData(minRange, maxRange) ); #if USE_FILELOCATIONCALLBACK osgDB::Options* options = Registry::instance()->cloneOrCreateOptions(); options->setFileLocationCallback( new FileLocationCallback() ); plod->setDatabaseOptions( options ); #endif result = plod; if ( tileHasLodBlending ) { // Make the LOD transition distance, and a measure of how // close the tile is to an LOD change, to shaders. result->addCullCallback(new Drivers::LODFactorCallback); } } else { result = tile; } // this cull callback dynamically adjusts the LOD scale based on distance-to-camera: if ( _options.lodFallOff().isSet() && *_options.lodFallOff() > 0.0 ) { result->addCullCallback( new DynamicLODScaleCallback(*_options.lodFallOff()) ); } // this one rejects back-facing tiles: if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true ) { result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( static_cast<osgTerrain::HeightFieldLayer*>(tile->getElevationLayer())->getHeightField(), tile->getLocator()->getEllipsoidModel(), tile->getVerticalScale() ) ); } parent->addChild( result ); }
void SerialKeyNodeFactory::addTile(TileModel* model, bool tileHasRealData, bool tileHasLodBlending, osg::Group* parent ) { // create a node: TileNode* tileNode = new TileNode( model->_tileKey, model->_tileLocator ); // install the tile model and compile it: tileNode->setTileModel( model ); tileNode->compile( _modelCompiler.get() ); // assemble a URI for this tile's child group: std::string uri = Stringify() << model->_tileKey.str() << "." << _engineUID << ".osgearth_engine_mp_tile"; osg::Node* result = 0L; // Only add the next tile if all the following are true: // 1. Either there's real tile data, or a minLOD is explicity set in the options; // 2. The tile isn't blacklisted; and // 3. We are still below the maximim LOD. bool wrapInPagedLOD = (tileHasRealData || (_options.minLOD().isSet() && model->_tileKey.getLOD() < *_options.minLOD())) && !osgEarth::Registry::instance()->isBlacklisted( uri ) && model->_tileKey.getLOD() < *_options.maxLOD(); if ( wrapInPagedLOD ) { osg::BoundingSphere bs = tileNode->getBound(); float maxRange = FLT_MAX; //Compute the min range based on the 2D size of the tile GeoExtent extent = model->_tileKey.getExtent(); GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radius = (ur - ll).length() / 2.0; float minRange = (float)(radius * _options.minTileRangeFactor().value()); // create a PLOD so we can keep subdividing: osg::PagedLOD* plod = new CustomPagedLOD( _liveTiles.get(), _deadTiles.get() ); plod->setCenter( bs.center() ); plod->addChild( tileNode ); plod->setRangeMode( *_options.rangeMode() ); plod->setFileName( 1, uri ); if (plod->getRangeMode() == osg::LOD::PIXEL_SIZE_ON_SCREEN) { static const float sqrt2 = sqrt(2.0f); minRange = 0; maxRange = (*_options.tilePixelSize()) * sqrt2; plod->setRange( 0, minRange, maxRange ); plod->setRange( 1, maxRange, FLT_MAX ); } else { plod->setRange( 0, minRange, maxRange ); plod->setRange( 1, 0, minRange ); } plod->setUserData( new MapNode::TileRangeData(minRange, maxRange) ); #if USE_FILELOCATIONCALLBACK osgDB::Options* options = Registry::instance()->cloneOrCreateOptions(); options->setFileLocationCallback( new FileLocationCallback() ); plod->setDatabaseOptions( options ); #endif result = plod; if ( tileHasLodBlending ) { // Make the LOD transition distance, and a measure of how // close the tile is to an LOD change, to shaders. result->addCullCallback(new LODFactorCallback); } } else { result = tileNode; } // this cull callback dynamically adjusts the LOD scale based on distance-to-camera: if ( _options.lodFallOff().isSet() && *_options.lodFallOff() > 0.0 ) { result->addCullCallback( new DynamicLODScaleCallback(*_options.lodFallOff()) ); } // this one rejects back-facing tiles: if ( _mapInfo.isGeocentric() && _options.clusterCulling() == true ) { osg::HeightField* hf = model->_elevationData.getHFLayer()->getHeightField(); result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( hf, tileNode->getLocator()->getEllipsoidModel(), *_options.verticalScale() ) ); } parent->addChild( result ); }
//override bool renderFeaturesForStyle( const Style& style, const FeatureList& inFeatures, osg::Referenced* buildData, const GeoExtent& imageExtent, osg::Image* image ) { // local copy of the features that we can process FeatureList features = inFeatures; BuildData* bd = static_cast<BuildData*>( buildData ); // A processing context to use with the filters: FilterContext context; context.profile() = getFeatureSource()->getFeatureProfile(); const LineSymbol* masterLine = style.getSymbol<LineSymbol>(); const PolygonSymbol* masterPoly = style.getSymbol<PolygonSymbol>(); //bool embeddedStyles = getFeatureSource()->hasEmbeddedStyles(); // if only a line symbol exists, and there are polygons in the mix, draw them // as outlines (line rings). //OE_INFO << LC << "Line Symbol = " << (masterLine == 0L ? "null" : masterLine->getConfig().toString()) << std::endl; //OE_INFO << LC << "Poly SYmbol = " << (masterPoly == 0L ? "null" : masterPoly->getConfig().toString()) << std::endl; //bool convertPolysToRings = poly == 0L && line != 0L; //if ( convertPolysToRings ) // OE_INFO << LC << "No PolygonSymbol; will draw polygons to rings" << std::endl; // initialize: double xmin = imageExtent.xMin(); double ymin = imageExtent.yMin(); //double s = (double)image->s(); //double t = (double)image->t(); double xf = (double)image->s() / imageExtent.width(); double yf = (double)image->t() / imageExtent.height(); // strictly speaking we should iterate over the features and buffer each one that's a line, // rather then checking for the existence of a LineSymbol. FeatureList linesToBuffer; for(FeatureList::iterator i = features.begin(); i != features.end(); i++) { Feature* feature = i->get(); Geometry* geom = feature->getGeometry(); if ( geom ) { // check for an embedded style: const LineSymbol* line = feature->style().isSet() ? feature->style()->getSymbol<LineSymbol>() : masterLine; const PolygonSymbol* poly = feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly; // if we have polygons but only a LineSymbol, draw the poly as a line. if ( geom->getComponentType() == Geometry::TYPE_POLYGON ) { if ( !poly && line ) { Feature* outline = new Feature( *feature ); geom = geom->cloneAs( Geometry::TYPE_RING ); outline->setGeometry( geom ); *i = outline; feature = outline; } //TODO: fix to enable outlined polys. doesn't work, not sure why -gw //else if ( poly && line ) //{ // Feature* outline = new Feature(); // geom = geom->cloneAs( Geometry::TYPE_LINESTRING ); // outline->setGeometry( geom ); // features.push_back( outline ); //} } bool needsBuffering = geom->getComponentType() == Geometry::TYPE_LINESTRING || geom->getComponentType() == Geometry::TYPE_RING; if ( needsBuffering ) { linesToBuffer.push_back( feature ); } } } if ( linesToBuffer.size() > 0 ) { //We are buffering in the features native extent, so we need to use the transform extent to get the proper "resolution" for the image GeoExtent transformedExtent = imageExtent.transform(context.profile()->getSRS()); double trans_xf = (double)image->s() / transformedExtent.width(); double trans_yf = (double)image->t() / transformedExtent.height(); // resolution of the image (pixel extents): double xres = 1.0/trans_xf; double yres = 1.0/trans_yf; // downsample the line data so that it is no higher resolution than to image to which // we intend to rasterize it. If you don't do this, you run the risk of the buffer // operation taking forever on very high-res input data. if ( _options.optimizeLineSampling() == true ) { ResampleFilter resample; resample.minLength() = osg::minimum( xres, yres ); context = resample.push( linesToBuffer, context ); } // now run the buffer operation on all lines: BufferFilter buffer; float lineWidth = 0.5; if ( masterLine ) { buffer.capStyle() = masterLine->stroke()->lineCap().value(); if ( masterLine->stroke()->width().isSet() ) lineWidth = masterLine->stroke()->width().value(); } // "relative line size" means that the line width is expressed in (approx) pixels // rather than in map units if ( _options.relativeLineSize() == true ) buffer.distance() = xres * lineWidth; else buffer.distance() = lineWidth; buffer.push( linesToBuffer, context ); } // First, transform the features into the map's SRS: TransformFilter xform( imageExtent.getSRS() ); xform.setLocalizeCoordinates( false ); context = xform.push( features, context ); // set up the AGG renderer: agg::rendering_buffer rbuf( image->data(), image->s(), image->t(), image->s()*4 ); // Create the renderer and the rasterizer agg::renderer<agg::span_abgr32> ren(rbuf); agg::rasterizer ras; // Setup the rasterizer ras.gamma(1.3); ras.filling_rule(agg::fill_even_odd); GeoExtent cropExtent = GeoExtent(imageExtent); cropExtent.scale(1.1, 1.1); osg::ref_ptr<Symbology::Polygon> cropPoly = new Symbology::Polygon( 4 ); cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMin(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMin(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMax(), cropExtent.yMax(), 0 )); cropPoly->push_back( osg::Vec3d( cropExtent.xMin(), cropExtent.yMax(), 0 )); double lineWidth = 1.0; if ( masterLine ) lineWidth = (double)masterLine->stroke()->width().value(); osg::Vec4 color = osg::Vec4(1, 1, 1, 1); if ( masterLine ) color = masterLine->stroke()->color(); // render the features for(FeatureList::iterator i = features.begin(); i != features.end(); i++) { Feature* feature = i->get(); //bool first = bd->_pass == 0 && i == features.begin(); Geometry* geometry = feature->getGeometry(); osg::ref_ptr< Geometry > croppedGeometry; if ( ! geometry->crop( cropPoly.get(), croppedGeometry ) ) continue; // set up a default color: osg::Vec4 c = color; unsigned int a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up agg::rgba8 fgColor( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a ); GeometryIterator gi( croppedGeometry.get() ); while( gi.hasMore() ) { c = color; Geometry* g = gi.next(); const LineSymbol* line = feature->style().isSet() ? feature->style()->getSymbol<LineSymbol>() : masterLine; const PolygonSymbol* poly = feature->style().isSet() ? feature->style()->getSymbol<PolygonSymbol>() : masterPoly; if (g->getType() == Geometry::TYPE_RING || g->getType() == Geometry::TYPE_LINESTRING) { if ( line ) c = line->stroke()->color(); else if ( poly ) c = poly->fill()->color(); } else if ( g->getType() == Geometry::TYPE_POLYGON ) { if ( poly ) c = poly->fill()->color(); else if ( line ) c = line->stroke()->color(); } a = (unsigned int)(127+(c.a()*255)/2); // scale alpha up fgColor = agg::rgba8( (unsigned int)(c.r()*255), (unsigned int)(c.g()*255), (unsigned int)(c.b()*255), a ); ras.filling_rule( agg::fill_even_odd ); for( Geometry::iterator p = g->begin(); p != g->end(); p++ ) { const osg::Vec3d& p0 = *p; double x0 = xf*(p0.x()-xmin); double y0 = yf*(p0.y()-ymin); //const osg::Vec3d& p1 = p+1 != g->end()? *(p+1) : g->front(); //double x1 = xf*(p1.x()-xmin); //double y1 = yf*(p1.y()-ymin); if ( p == g->begin() ) ras.move_to_d( x0, y0 ); else ras.line_to_d( x0, y0 ); } } ras.render(ren, fgColor); ras.reset(); } bd->_pass++; return true; }
void Profile::addIntersectingTiles(const GeoExtent& key_ext, std::vector<TileKey>& out_intersectingKeys) const { // assume a non-crossing extent here. if ( key_ext.crossesAntimeridian() ) { OE_WARN << "Profile::addIntersectingTiles cannot process date-line cross" << std::endl; return; } #if 0 // works for meracator; does NOT work for cube int precision = 5; double eps = 0.001; double keyWidth = round(key_ext.width(), precision); int destLOD = 0; double w, h; getTileDimensions(0, w, h); for(; (round(w,precision) - keyWidth) > eps; w*=0.5, h*=0.5, destLOD++ ); double destTileWidth, destTileHeight; getTileDimensions( destLOD, destTileWidth, destTileHeight ); destTileWidth = round(destTileWidth, precision); destTileHeight = round(destTileHeight, precision); int tileMinX = quantize( ((key_ext.xMin() - _extent.xMin()) / destTileWidth), eps ); int tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth); int tileMinY = quantize( ((_extent.yMax() - key_ext.yMax()) / destTileHeight), eps ); int tileMaxY = (int) ((_extent.yMax() - key_ext.yMin()) / destTileHeight); #else double keyWidth = key_ext.width(); double keyHeight = key_ext.height(); // bail out if the key has a null extent. This might happen is the original key represents an // area in one profile that is out of bounds in this profile. if ( keyWidth <= 0.0 && keyHeight <= 0.0 ) return; double keySpan = std::min( keyWidth, keyHeight ); double keyArea = keyWidth * keyHeight; double keyAvg = 0.5*(keyWidth+keyHeight); int destLOD = 1; double destTileWidth, destTileHeight; int currLOD = 0; destLOD = currLOD; getTileDimensions(destLOD, destTileWidth, destTileHeight); while( true ) { currLOD++; double w, h; getTileDimensions(currLOD, w, h); if ( w < keyAvg || h < keyAvg ) break; destLOD = currLOD; destTileWidth = w; destTileHeight = h; } //OE_DEBUG << std::fixed << " Source Tile: " << key.getLevelOfDetail() << " (" << keyWidth << ", " << keyHeight << ")" << std::endl; //OE_DEBUG << std::fixed << " Dest Size: " << destLOD << " (" << destTileWidth << ", " << destTileHeight << ")" << std::endl; int tileMinX = (int)((key_ext.xMin() - _extent.xMin()) / destTileWidth); int tileMaxX = (int)((key_ext.xMax() - _extent.xMin()) / destTileWidth); int tileMinY = (int)((_extent.yMax() - key_ext.yMax()) / destTileHeight); int tileMaxY = (int)((_extent.yMax() - key_ext.yMin()) / destTileHeight); #endif unsigned int numWide, numHigh; getNumTiles(destLOD, numWide, numHigh); // bail out if the tiles are out of bounds. if ( tileMinX >= (int)numWide || tileMinY >= (int)numHigh || tileMaxX < 0 || tileMaxY < 0 ) { return; } tileMinX = osg::clampBetween(tileMinX, 0, (int)numWide-1); tileMaxX = osg::clampBetween(tileMaxX, 0, (int)numWide-1); tileMinY = osg::clampBetween(tileMinY, 0, (int)numHigh-1); tileMaxY = osg::clampBetween(tileMaxY, 0, (int)numHigh-1); OE_DEBUG << std::fixed << " Dest Tiles: " << tileMinX << "," << tileMinY << " => " << tileMaxX << "," << tileMaxY << std::endl; for (int i = tileMinX; i <= tileMaxX; ++i) { for (int j = tileMinY; j <= tileMaxY; ++j) { //TODO: does not support multi-face destination keys. out_intersectingKeys.push_back( TileKey(destLOD, i, j, this) ); } } //OE_INFO << " Found " << out_intersectingKeys.size() << " keys " << std::endl; }
osg::Node* SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary, ProgressCallback* progress) { #ifdef EXPERIMENTAL_TILE_NODE_CACHE osg::ref_ptr<TileNode> tileNode; TileNodeCache::Record rec; cache.get(model->_tileKey, rec); if ( rec.valid() ) { tileNode = rec.value().get(); } else { tileNode = _modelCompiler->compile( model, _frame ); cache.insert(model->_tileKey, tileNode); } #else // compile the model into a node: TileNode* tileNode = _modelCompiler->compile(model, _frame, progress); #endif // see if this tile might have children. bool prepareForChildren = setupChildrenIfNecessary && model->_tileKey.getLOD() < *_options.maxLOD(); osg::Node* result = 0L; if ( prepareForChildren ) { osg::BoundingSphere bs = tileNode->getBound(); TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles ); plod->setCenter ( bs.center() ); plod->addChild ( tileNode ); plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" ); if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT ) { //Compute the min range based on the 2D size of the tile GeoExtent extent = model->_tileKey.getExtent(); GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radius = (ur - ll).length() / 2.0; float minRange = (float)(radius * _options.minTileRangeFactor().value()); plod->setRange( 0, minRange, FLT_MAX ); plod->setRange( 1, 0, minRange ); plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT ); } else { plod->setRange( 0, 0.0f, _options.tilePixelSize().value() ); plod->setRange( 1, _options.tilePixelSize().value(), FLT_MAX ); plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN ); } // DBPager will set a priority based on the ratio range/maxRange. // This will offset that number with a full LOD #, giving LOD precedence. // Experimental. //plod->setPriorityScale( 1, model->_tileKey.getLOD()+1 ); #if USE_FILELOCATIONCALLBACK osgDB::Options* options = plod->getOrCreateDBOptions(); options->setFileLocationCallback( new FileLocationCallback() ); #endif result = plod; // this one rejects back-facing tiles: if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true ) { osg::HeightField* hf = model->_elevationData.getHeightField(); result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( hf, tileNode->getKey().getProfile()->getSRS()->getEllipsoid(), *_options.verticalScale() ) ); } } else { result = tileNode; } return result; }
osg::Node* MGRSGraticule::buildSQIDTiles( const std::string& gzd ) { const GeoExtent& extent = _gzd[gzd]; // parse the GZD into its components: unsigned zone; char letter; sscanf( gzd.c_str(), "%u%c", &zone, &letter ); TextSymbol* textSym = _options->secondaryStyle()->get<TextSymbol>(); if ( !textSym ) textSym = _options->primaryStyle()->getOrCreate<TextSymbol>(); AltitudeSymbol* alt = _options->secondaryStyle()->get<AltitudeSymbol>(); double h = 0.0; TextSymbolizer ts( textSym ); MGRSFormatter mgrs(MGRSFormatter::PRECISION_100000M); osg::Geode* textGeode = new osg::Geode(); textGeode->getOrCreateStateSet()->setRenderBinDetails( 9999, "DepthSortedBin" ); textGeode->getOrCreateStateSet()->setAttributeAndModes( _depthAttribute, 1 ); const SpatialReference* ecefSRS = extent.getSRS()->getECEF(); osg::Vec3d centerMap, centerECEF; extent.getCentroid(centerMap.x(), centerMap.y()); extent.getSRS()->transform(centerMap, ecefSRS, centerECEF); //extent.getSRS()->transformToECEF(centerMap, centerECEF); osg::Matrix local2world; ecefSRS->createLocalToWorld( centerECEF, local2world ); //= ECEF::createLocalToWorld(centerECEF); osg::Matrix world2local; world2local.invert(local2world); FeatureList features; std::vector<GeoExtent> sqidExtents; // UTM: if ( letter > 'B' && letter < 'Y' ) { // grab the SRS for the current UTM zone: // TODO: AL/AA designation?? const SpatialReference* utm = SpatialReference::create( Stringify() << "+proj=utm +zone=" << zone << " +north +units=m" ); // transform the four corners of the tile to UTM. osg::Vec3d gzdUtmSW, gzdUtmSE, gzdUtmNW, gzdUtmNE; extent.getSRS()->transform( osg::Vec3d(extent.xMin(),extent.yMin(),h), utm, gzdUtmSW ); extent.getSRS()->transform( osg::Vec3d(extent.xMin(),extent.yMax(),h), utm, gzdUtmNW ); extent.getSRS()->transform( osg::Vec3d(extent.xMax(),extent.yMin(),h), utm, gzdUtmSE ); extent.getSRS()->transform( osg::Vec3d(extent.xMax(),extent.yMax(),h), utm, gzdUtmNE ); // find the southern boundary of the first full SQID tile in the GZD tile. double southSQIDBoundary = gzdUtmSW.y(); //extentUTM.yMin(); double remainder = fmod( southSQIDBoundary, 100000.0 ); if ( remainder > 0.0 ) southSQIDBoundary += (100000.0 - remainder); // find the min/max X for this cell in UTM: double xmin = extent.yMin() >= 0.0 ? gzdUtmSW.x() : gzdUtmNW.x(); double xmax = extent.yMin() >= 0.0 ? gzdUtmSE.x() : gzdUtmNE.x(); // Record the UTM extent of each SQID cell in this tile. // Go from the south boundary northwards: for( double y = southSQIDBoundary; y < gzdUtmNW.y(); y += 100000.0 ) { // start at the central meridian (500K) and go west: for( double x = 500000.0; x > xmin; x -= 100000.0 ) { sqidExtents.push_back( GeoExtent(utm, x-100000.0, y, x, y+100000.0) ); } // then start at the central meridian and go east: for( double x = 500000.0; x < xmax; x += 100000.0 ) { sqidExtents.push_back( GeoExtent(utm, x, y, x+100000.0, y+100000.0) ); } } for( std::vector<GeoExtent>::iterator i = sqidExtents.begin(); i != sqidExtents.end(); ++i ) { GeoExtent utmEx = *i; // now, clamp each of the points in the UTM SQID extent to the map-space // boundaries of the GZD tile. (We only need to clamp in the X dimension, // Y geometry is allowed to overflow.) Also, skip NE, we don't need it. double r, xlimit; osg::Vec3d sw(utmEx.xMin(), utmEx.yMin(), 0); r = (sw.y()-gzdUtmSW.y())/(gzdUtmNW.y()-gzdUtmSW.y()); xlimit = gzdUtmSW.x() + r * (gzdUtmNW.x() - gzdUtmSW.x()); if ( sw.x() < xlimit ) sw.x() = xlimit; osg::Vec3d nw(utmEx.xMin(), utmEx.yMax(), 0); r = (nw.y()-gzdUtmSW.y())/(gzdUtmNW.y()-gzdUtmSW.y()); xlimit = gzdUtmSW.x() + r * (gzdUtmNW.x() - gzdUtmSW.x()); if ( nw.x() < xlimit ) nw.x() = xlimit; osg::Vec3d se(utmEx.xMax(), utmEx.yMin(), 0); r = (se.y()-gzdUtmSE.y())/(gzdUtmNE.y()-gzdUtmSE.y()); xlimit = gzdUtmSE.x() + r * (gzdUtmNE.x() - gzdUtmSE.x()); if ( se.x() > xlimit ) se.x() = xlimit; // at the northernmost GZD (lateral band X), clamp the northernmost SQIDs to the upper latitude. if ( letter == 'X' && nw.y() > gzdUtmNW.y() ) nw.y() = gzdUtmNW.y(); // need this in order to calculate the font size: double utmWidth = se.x() - sw.x(); // now transform the corner points back into the map SRS: utm->transform( sw, extent.getSRS(), sw ); utm->transform( nw, extent.getSRS(), nw ); utm->transform( se, extent.getSRS(), se ); // and draw valid sqid geometry. if ( sw.x() < se.x() ) { Feature* lat = new Feature(new LineString(2), extent.getSRS()); lat->geoInterp() = GEOINTERP_RHUMB_LINE; lat->getGeometry()->push_back( sw ); lat->getGeometry()->push_back( se ); features.push_back(lat); Feature* lon = new Feature(new LineString(2), extent.getSRS()); lon->geoInterp() = GEOINTERP_GREAT_CIRCLE; lon->getGeometry()->push_back( sw ); lon->getGeometry()->push_back( nw ); features.push_back(lon); // and the text label: osg::Vec3d sqidTextMap = (nw + se) * 0.5; sqidTextMap.z() += 1000.0; osg::Vec3d sqidTextECEF; extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF); //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF); osg::Vec3d sqidLocal; sqidLocal = sqidTextECEF * world2local; MGRSCoord mgrsCoord; if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) ) { textSym->size() = utmWidth/3.0; osgText::Text* d = ts.create( mgrsCoord.sqid ); osg::Matrixd textLocal2World; ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World ); d->setPosition( sqidLocal ); textGeode->addDrawable( d ); } } } } else if ( letter == 'A' || letter == 'B' ) { // SRS for south polar region UPS projection. This projection has (0,0) at the // south pole, with +X extending towards 90 degrees E longitude and +Y towards // 0 degrees longitude. const SpatialReference* ups = SpatialReference::create( "+proj=stere +lat_ts=-90 +lat_0=-90 +lon_0=0 +k_0=1 +x_0=0 +y_0=0"); osg::Vec3d gtemp; double r = GeoMath::distance(-osg::PI_2, 0.0, -1.3962634, 0.0); // -90 => -80 latitude double r2 = r*r; if ( letter == 'A' ) { for( double x = 0.0; x < 1200000.0; x += 100000.0 ) { double yminmax = sqrt( r2 - x*x ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d(-x, -yminmax, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d(-x, yminmax, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double y = -1100000.0; y < 1200000.0; y += 100000.0 ) { double xmax = sqrt( r2 - y*y ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d(-xmax, y, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d( 0, y, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double x = -1200000.0; x < 0.0; x += 100000.0 ) { for( double y = -1200000.0; y < 1200000.0; y += 100000.0 ) { osg::Vec3d sqidTextMap; ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap); if ( sqidTextMap.y() < -80.0 ) { sqidTextMap.z() += 1000.0; osg::Vec3d sqidTextECEF; extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF); //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF); osg::Vec3d sqidLocal = sqidTextECEF * world2local; MGRSCoord mgrsCoord; if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) ) { textSym->size() = 33000.0; osgText::Text* d = ts.create( mgrsCoord.sqid ); osg::Matrixd textLocal2World; ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World ); d->setPosition( sqidLocal ); textGeode->addDrawable( d ); } } } } } else if ( letter == 'B' ) { for( double x = 100000.0; x < 1200000.0; x += 100000.0 ) { double yminmax = sqrt( r2 - x*x ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d(x, -yminmax, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d(x, yminmax, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double y = -1100000.0; y < 1200000.0; y += 100000.0 ) { double xmax = sqrt( r2 - y*y ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d( 0, y, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d( xmax, y, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double x = 0.0; x < 1200000.0; x += 100000.0 ) { for( double y = -1200000.0; y < 1200000.0; y += 100000.0 ) { osg::Vec3d sqidTextMap; ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap); if ( sqidTextMap.y() < -80.0 ) { sqidTextMap.z() += 1000.0; osg::Vec3d sqidTextECEF; extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF); //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF); osg::Vec3d sqidLocal = sqidTextECEF * world2local; MGRSCoord mgrsCoord; if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) ) { textSym->size() = 33000.0; osgText::Text* d = ts.create( mgrsCoord.sqid ); osg::Matrixd textLocal2World; ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World ); d->setPosition( sqidLocal ); textGeode->addDrawable( d ); } } } } } } else if ( letter == 'Y' || letter == 'Z' ) { // SRS for north polar region UPS projection. This projection has (0,0) at the // south pole, with +X extending towards 90 degrees E longitude and +Y towards // 180 degrees longitude. const SpatialReference* ups = SpatialReference::create( "+proj=stere +lat_ts=90 +lat_0=90 +lon_0=0 +k_0=1 +x_0=0 +y_0=0"); osg::Vec3d gtemp; double r = GeoMath::distance(osg::PI_2, 0.0, 1.46607657, 0.0); // 90 -> 84 latitude double r2 = r*r; if ( letter == 'Y' ) { for( double x = 0.0; x < 700000.0; x += 100000.0 ) { double yminmax = sqrt( r2 - x*x ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d(-x, -yminmax, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d(-x, yminmax, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double y = -600000.0; y < 700000.0; y += 100000.0 ) { double xmax = sqrt( r2 - y*y ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d(-xmax, y, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d( 0, y, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double x = -700000.0; x < 0.0; x += 100000.0 ) { for( double y = -700000.0; y < 700000.0; y += 100000.0 ) { osg::Vec3d sqidTextMap; ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap); if ( sqidTextMap.y() > 84.0 ) { sqidTextMap.z() += 1000.0; osg::Vec3d sqidTextECEF; extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF); //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF); osg::Vec3d sqidLocal = sqidTextECEF * world2local; MGRSCoord mgrsCoord; if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) ) { textSym->size() = 33000.0; osgText::Text* d = ts.create( mgrsCoord.sqid ); osg::Matrixd textLocal2World; ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World ); d->setPosition( sqidLocal ); textGeode->addDrawable( d ); } } } } } else if ( letter == 'Z' ) { for( double x = 100000.0; x < 700000.0; x += 100000.0 ) { double yminmax = sqrt( r2 - x*x ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d(x, -yminmax, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d(x, yminmax, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double y = -600000.0; y < 700000.0; y += 100000.0 ) { double xmax = sqrt( r2 - y*y ); Feature* f = new Feature( new LineString(2), extent.getSRS() ); f->geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Vec3d p0, p1; ups->transform( osg::Vec3d( 0, y, 0), extent.getSRS(), p0 ); ups->transform( osg::Vec3d( xmax, y, 0), extent.getSRS(), p1 ); f->getGeometry()->push_back( p0 ); f->getGeometry()->push_back( p1 ); features.push_back( f ); } for( double x = 0.0; x < 700000.0; x += 100000.0 ) { for( double y = -700000.0; y < 700000.0; y += 100000.0 ) { osg::Vec3d sqidTextMap; ups->transform( osg::Vec3d(x+50000.0, y+50000.0, 0), extent.getSRS(), sqidTextMap); if ( sqidTextMap.y() > 84.0 ) { sqidTextMap.z() += 1000.0; osg::Vec3d sqidTextECEF; extent.getSRS()->transform(sqidTextMap, ecefSRS, sqidTextECEF); //extent.getSRS()->transformToECEF(sqidTextMap, sqidTextECEF); osg::Vec3d sqidLocal = sqidTextECEF * world2local; MGRSCoord mgrsCoord; if ( mgrs.transform( GeoPoint(extent.getSRS(),sqidTextMap,ALTMODE_ABSOLUTE), mgrsCoord) ) { textSym->size() = 33000.0; osgText::Text* d = ts.create( mgrsCoord.sqid ); osg::Matrixd textLocal2World; ecefSRS->createLocalToWorld( sqidTextECEF, textLocal2World ); d->setPosition( sqidLocal ); textGeode->addDrawable( d ); } } } } } } osg::Group* group = new osg::Group(); Style lineStyle; lineStyle.add( _options->secondaryStyle()->get<LineSymbol>() ); lineStyle.add( _options->secondaryStyle()->get<AltitudeSymbol>() ); GeometryCompiler compiler; osg::ref_ptr<Session> session = new Session( getMapNode()->getMap() ); FilterContext context( session.get(), _featureProfile.get(), extent ); // make sure we get sufficient tessellation: compiler.options().maxGranularity() = 0.25; osg::Node* geomNode = compiler.compile(features, lineStyle, context); if ( geomNode ) group->addChild( geomNode ); osg::MatrixTransform* mt = new osg::MatrixTransform(local2world); mt->addChild(textGeode); group->addChild( mt ); // prep for depth offset: DepthOffsetUtils::prepareGraph( group ); group->getOrCreateStateSet()->addUniform( _minDepthOffset.get() ); return group; }
osg::Node* GeodeticGraticule::buildTile( const TileKey& key, Map* map ) const { if ( _options->levels().size() <= key.getLevelOfDetail() ) { OE_WARN << LC << "Tried to create cell at non-existant level " << key.getLevelOfDetail() << std::endl; return 0L; } const GeodeticGraticuleOptions::Level& level = _options->levels()[key.getLevelOfDetail()]; //_levels[key.getLevelOfDetail()]; // the "-2" here is because normal tile paging gives you one subdivision already, // so we only need to account for > 1 subdivision factor. unsigned cellsPerTile = level._subdivisionFactor <= 2u ? 1u : 1u << (level._subdivisionFactor-2u); unsigned cellsPerTileX = std::max(1u, cellsPerTile); unsigned cellsPerTileY = std::max(1u, cellsPerTile); GeoExtent tileExtent = key.getExtent(); FeatureList latLines; FeatureList lonLines; static LatLongFormatter s_llf(LatLongFormatter::FORMAT_DECIMAL_DEGREES); double cellWidth = tileExtent.width() / cellsPerTileX; double cellHeight = tileExtent.height() / cellsPerTileY; const Style& lineStyle = level._lineStyle.isSet() ? *level._lineStyle : *_options->lineStyle(); const Style& textStyle = level._textStyle.isSet() ? *level._textStyle : *_options->textStyle(); bool hasText = textStyle.get<TextSymbol>() != 0L; osg::ref_ptr<osg::Group> labels; if ( hasText ) { labels = new osg::Group(); //TODO: This is a bug, if you don't turn on decluttering the text labels are giant. Need to determine what is wrong with LabelNodes without decluttering. Decluttering::setEnabled( labels->getOrCreateStateSet(), true ); } // spatial ref for features: const SpatialReference* geoSRS = tileExtent.getSRS()->getGeographicSRS(); // longitude lines for( unsigned cx = 0; cx < cellsPerTileX; ++cx ) { double clon = tileExtent.xMin() + cellWidth * (double)cx; LineString* lon = new LineString(2); lon->push_back( osg::Vec3d(clon, tileExtent.yMin(), 0) ); lon->push_back( osg::Vec3d(clon, tileExtent.yMax(), 0) ); lonLines.push_back( new Feature(lon, geoSRS) ); if ( hasText ) { for( unsigned cy = 0; cy < cellsPerTileY; ++cy ) { double clat = tileExtent.yMin() + (0.5*cellHeight) + cellHeight*(double)cy; LabelNode* label = new LabelNode( _mapNode.get(), GeoPoint(geoSRS, clon, clat), s_llf.format(clon), textStyle ); labels->addChild( label ); } } } // latitude lines for( unsigned cy = 0; cy < cellsPerTileY; ++cy ) { double clat = tileExtent.yMin() + cellHeight * (double)cy; if ( clat == key.getProfile()->getExtent().yMin() ) continue; LineString* lat = new LineString(2); lat->push_back( osg::Vec3d(tileExtent.xMin(), clat, 0) ); lat->push_back( osg::Vec3d(tileExtent.xMax(), clat, 0) ); latLines.push_back( new Feature(lat, geoSRS) ); if ( hasText ) { for( unsigned cx = 0; cx < cellsPerTileX; ++cx ) { double clon = tileExtent.xMin() + (0.5*cellWidth) + cellWidth*(double)cy; LabelNode* label = new LabelNode( _mapNode.get(), GeoPoint(geoSRS, clon, clat), s_llf.format(clat), textStyle ); labels->addChild( label ); } } } osg::Group* group = new osg::Group(); GeometryCompiler compiler; osg::ref_ptr<Session> session = new Session( map ); FilterContext context( session.get(), _featureProfile.get(), tileExtent ); // make sure we get sufficient tessellation: compiler.options().maxGranularity() = std::min(cellWidth, cellHeight) / 16.0; compiler.options().geoInterp() = GEOINTERP_GREAT_CIRCLE; osg::Node* lonNode = compiler.compile(lonLines, lineStyle, context); if ( lonNode ) group->addChild( lonNode ); compiler.options().geoInterp() = GEOINTERP_RHUMB_LINE; osg::Node* latNode = compiler.compile(latLines, lineStyle, context); if ( latNode ) group->addChild( latNode ); // add the labels. if ( labels.valid() ) group->addChild( labels.get() ); // get the geocentric tile center: osg::Vec3d tileCenter; tileExtent.getCentroid( tileCenter.x(), tileCenter.y() ); const SpatialReference* ecefSRS = tileExtent.getSRS()->getECEF(); osg::Vec3d centerECEF; tileExtent.getSRS()->transform( tileCenter, ecefSRS, centerECEF ); //tileExtent.getSRS()->transformToECEF( tileCenter, centerECEF ); osg::NodeCallback* ccc = 0L; // set up cluster culling. if ( tileExtent.getSRS()->isGeographic() && tileExtent.width() < 90.0 && tileExtent.height() < 90.0 ) { ccc = ClusterCullingFactory::create( group, centerECEF ); } // add a paging node for higher LODs: if ( key.getLevelOfDetail() + 1 < _options->levels().size() ) { const GeodeticGraticuleOptions::Level& nextLevel = _options->levels()[key.getLevelOfDetail()+1]; osg::BoundingSphere bs = group->getBound(); std::string uri = Stringify() << key.str() << "_" << getID() << "." << GRID_MARKER << "." << GRATICULE_EXTENSION; osg::PagedLOD* plod = new osg::PagedLOD(); plod->setCenter( bs.center() ); plod->addChild( group, std::max(level._minRange,nextLevel._maxRange), FLT_MAX ); plod->setFileName( 1, uri ); plod->setRange( 1, 0, nextLevel._maxRange ); group = plod; } // or, if this is the deepest level and there's a minRange set, we need an LOD: else if ( level._minRange > 0.0f ) { osg::LOD* lod = new osg::LOD(); lod->addChild( group, level._minRange, FLT_MAX ); group = lod; } if ( ccc ) { osg::Group* cccGroup = new osg::Group(); cccGroup->addCullCallback( ccc ); cccGroup->addChild( group ); group = cccGroup; } return group; }
osg::Node* SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary, ProgressCallback* progress) { #ifdef EXPERIMENTAL_TILE_NODE_CACHE osg::ref_ptr<TileNode> tileNode; TileNodeCache::Record rec; cache.get(model->_tileKey, rec); if ( rec.valid() ) { tileNode = rec.value().get(); } else { tileNode = _modelCompiler->compile( model, _frame ); cache.insert(model->_tileKey, tileNode); } #else // compile the model into a node: TileNode* tileNode = _modelCompiler->compile(model, _frame, progress); tileNode->setEngineUID( _engineUID ); #endif // see if this tile might have children. bool prepareForChildren = setupChildrenIfNecessary && model->_tileKey.getLOD() < *_options.maxLOD(); osg::Node* result = 0L; if ( prepareForChildren ) { osg::BoundingSphere bs = tileNode->getBound(); TilePagedLOD* plod = new TilePagedLOD( _engineUID, _liveTiles, _deadTiles ); plod->setCenter ( bs.center() ); plod->addChild ( tileNode ); plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engineUID << ".osgearth_engine_mp_tile" ); plod->setDebug ( _debug ); if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT ) { //Compute the min range based on the 2D size of the tile GeoExtent extent = model->_tileKey.getExtent(); double radius = 0.0; #if 0 // Test code to use the equitorial radius so that all of the tiles at the same level // have the same range. This will make the poles page in more appropriately. if (_frame.getMapInfo().isGeocentric()) { GeoExtent equatorialExtent( extent.getSRS(), extent.west(), -extent.height()/2.0, extent.east(), extent.height()/2.0 ); radius = equatorialExtent.getBoundingGeoCircle().getRadius(); } else #endif { GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); radius = (ur - ll).length() / 2.0; } float minRange = (float)(radius * _options.minTileRangeFactor().value()); plod->setRange( 0, minRange, FLT_MAX ); plod->setRange( 1, 0, minRange ); plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT ); } else { // the *2 is because we page in 4-tile sets, not individual tiles. float size = 2.0f * _options.tilePixelSize().value(); plod->setRange( 0, 0.0f, size ); plod->setRange( 1, size, FLT_MAX ); plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN ); } // Install a tile-aligned bounding box in the pager node itself so we can do // visibility testing before paging in subtiles. plod->setChildBoundingBoxAndMatrix( 1, tileNode->getTerrainBoundingBox(), tileNode->getMatrix() ); // DBPager will set a priority based on the ratio range/maxRange. // This will offset that number with a full LOD #, giving LOD precedence. // Experimental. //plod->setPriorityScale( 1, model->_tileKey.getLOD()+1 ); #if USE_FILELOCATIONCALLBACK osgDB::Options* options = plod->getOrCreateDBOptions(); options->setFileLocationCallback( new FileLocationCallback() ); #endif result = plod; // this one rejects back-facing tiles: if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true ) { #if 1 osg::HeightField* hf = model->_elevationData.getHeightField(); result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( hf, tileNode->getKey().getProfile()->getSRS()->getEllipsoid(), *_options.verticalScale() ) ); #else // This works, but isn't quite as tight at the cluster culler. // Re-evaluate down the road. result->addCullCallback( new HorizonTileCuller( _frame.getMapInfo().getSRS(), tileNode->getTerrainBoundingBox(), tileNode->getMatrix(), tileNode->getKey() ) ); #endif } } else { result = tileNode; } return result; }
TileNode* TileGroupFactory::createTileNodeGraph(TerrainTileModel* model, bool setupChildrenIfNecessary, ProgressCallback* progress) { // TODO: fix this const unsigned tileSize = 17; // Build the surface node. SurfaceNodeFactory factory(model, _frame, _renderBindings, _geometryPool, tileSize, _options); SurfaceNode* surfaceNode = factory.createSurfaceNode(); surfaceNode->setEngineUID( _terrainEngine->getUID() ); // see if this tile might have children. bool prepareForChildren = setupChildrenIfNecessary && model->getKey().getLOD() < *_options.maxLOD(); // Build the Tile Node that will hold all the textures and texture matrices. TileNode* tileNode = createTileNode( model, progress ); // Build the paging node that will load subtiles, if necessary: if ( prepareForChildren ) { osg::BoundingSphere bs = surfaceNode->getBound(); TilePagedLOD* plod = new TilePagedLOD( _terrainEngine->getUID(), _liveTiles, _deadTiles ); plod->setCenter ( bs.center() ); plod->addChild ( surfaceNode ); plod->setFileName( 1, Stringify() << model->getKey().str() << "." << _terrainEngine->getUID() << ".osgearth_engine_mp_tile" ); if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT ) { //Compute the min range based on the 2D size of the tile GeoExtent extent = model->getKey().getExtent(); GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radius = (ur - ll).length() / 2.0; float minRange = (float)(radius * _options.minTileRangeFactor().value()); plod->setRange( 0, minRange, FLT_MAX ); plod->setRange( 1, 0, minRange ); plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT ); } else { plod->setRange( 0, 0.0f, _options.tilePixelSize().value() ); plod->setRange( 1, _options.tilePixelSize().value(), FLT_MAX ); plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN ); } #if 0 // TODO: reinstate this! // Install a tile-aligned bounding box in the pager node itself so we can do // visibility testing before paging in subtiles. plod->setChildBoundingBoxAndMatrix( 1, surfaceNode->getTerrainBoundingBox(), surfaceNode->getLocalToWorldMatrix() ); #endif #if USE_FILELOCATIONCALLBACK osgDB::Options* options = plod->getOrCreateDBOptions(); options->setFileLocationCallback( new FileLocationCallback() ); #endif tileNode->addChild( plod ); // Install a callback to reject back-facing tiles. if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true ) { const osg::HeightField* heightField = model->elevationModel()->getHeightField(); if ( heightField ) { tileNode->addCullCallback( HeightFieldUtils::createClusterCullingCallback( heightField, tileNode->getKey().getProfile()->getSRS()->getEllipsoid(), *_options.verticalScale() ) ); } } } else { tileNode->addChild( surfaceNode ); } return tileNode; }
//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; }
void GeodeticGraticule::updateLabels() { const osgEarth::SpatialReference* srs = osgEarth::SpatialReference::create("wgs84"); Threading::ScopedMutexLock lock(_cameraDataMapMutex); for (CameraDataMap::iterator i = _cameraDataMap.begin(); i != _cameraDataMap.end(); ++i) { CameraData& cdata = i->second; std::vector< GeoExtent > extents; if (cdata._viewExtent.crossesAntimeridian()) { GeoExtent first, second; cdata._viewExtent.splitAcrossAntimeridian(first, second); extents.push_back(first); extents.push_back(second); } else { extents.push_back( cdata._viewExtent ); } double resDegrees = cdata._resolution * 180.0; // We want half the resolution so the labels don't appear as often as the grid lines resDegrees *= 2.0; // Hide all the labels for (unsigned int i = 0; i < cdata._labelPool.size(); i++) { cdata._labelPool[i]->setNodeMask(0); } // Approximate offset in degrees double degOffset = cdata._metersPerPixel / 111000.0; unsigned int labelIndex = 0; bool done = false; for (unsigned int extentIndex = 0; extentIndex < extents.size() && !done; extentIndex++) { GeoExtent extent = extents[extentIndex]; int minLonIndex = floor(((extent.xMin() + 180.0)/resDegrees)); int maxLonIndex = ceil(((extent.xMax() + 180.0)/resDegrees)); int minLatIndex = floor(((extent.yMin() + 90)/resDegrees)); int maxLatIndex = ceil(((extent.yMax() + 90)/resDegrees)); // Generate horizontal labels for (int i = minLonIndex; i <= maxLonIndex && !done; i++) { GeoPoint point(srs, -180.0 + (double)i * resDegrees, cdata._lat + (_centerOffset.y() * degOffset), 0, ALTMODE_ABSOLUTE); LabelNode* label = cdata._labelPool[labelIndex++].get(); label->setNodeMask(~0u); label->setPosition(point); std::string text = getText( point, false); label->setText( text ); if (labelIndex == cdata._labelPool.size() - 1) { done = true; } } // Generate the vertical labels for (int i = minLatIndex; i <= maxLatIndex && !done; i++) { GeoPoint point(srs, cdata._lon + (_centerOffset.x() * degOffset), -90.0 + (double)i * resDegrees, 0, ALTMODE_ABSOLUTE); // Skip drawing labels at the poles if (osg::equivalent(osg::absolute( point.y()), 90.0, 0.1)) { continue; } LabelNode* label = cdata._labelPool[labelIndex++].get(); label->setNodeMask(~0u); label->setPosition(point); std::string text = getText( point, true); label->setText( text ); if (labelIndex == cdata._labelPool.size() - 1) { done = true; } } } } }
osgTerrain::ImageLayer* MapEngine::createImageLayer(Map* map, MapLayer* layer, const TileKey* key, ProgressCallback* progress) { Threading::ScopedReadLock lock( map->getMapDataMutex() ); osg::ref_ptr< GeoImage > geoImage; //If the key is valid, try to get the image from the MapLayer if (layer->isKeyValid( key ) ) { geoImage = layer->createImage(key, progress); } else { //If the key is not valid, simply make a transparent tile geoImage = new GeoImage(ImageUtils::createEmptyImage(), key->getGeoExtent()); } if (geoImage.valid()) { bool isProjected = map->getCoordinateSystemType() == Map::CSTYPE_PROJECTED; bool isPlateCarre = isProjected && map->getProfile()->getSRS()->isGeographic(); bool isGeocentric = !isProjected; osg::ref_ptr<GeoLocator> imgLocator; // Use a special locator for mercator images (instead of reprojecting) if (map->getProfile()->getSRS()->isGeographic() && geoImage->getSRS()->isMercator() && layer->useMercatorFastPath() == true ) { GeoExtent gx = geoImage->getExtent().transform( geoImage->getExtent().getSRS()->getGeographicSRS() ); imgLocator = key->getProfile()->getSRS()->createLocator( gx.xMin(), gx.yMin(), gx.xMax(), gx.yMax() ); imgLocator = new MercatorLocator( *imgLocator.get(), geoImage->getExtent() ); } else { const GeoExtent& gx = geoImage->getExtent(); imgLocator = GeoLocator::createForKey( key, map ); } if ( isGeocentric ) imgLocator->setCoordinateSystemType( osgTerrain::Locator::GEOCENTRIC ); //osgTerrain::ImageLayer* imgLayer = new osgTerrain::ImageLayer( geoImage->getImage() ); TransparentLayer* imgLayer = new TransparentLayer(geoImage->getImage(), layer); imgLayer->setLocator( imgLocator.get() ); imgLayer->setLevelOfDetail( key->getLevelOfDetail() ); imgLayer->setMinFilter( layer->getMinFilter().value()); imgLayer->setMagFilter( layer->getMagFilter().value()); return imgLayer; } return NULL; }
osg::Node* SingleKeyNodeFactory::createTile(TileModel* model, bool setupChildrenIfNecessary, ProgressCallback* progress) { #ifdef EXPERIMENTAL_TILE_NODE_CACHE osg::ref_ptr<TileNode> tileNode; TileNodeCache::Record rec; cache.get(model->_tileKey, rec); if ( rec.valid() ) { tileNode = rec.value().get(); } else { tileNode = _modelCompiler->compile( model, _frame ); cache.insert(model->_tileKey, tileNode); } #else // compile the model into a node: osg::ref_ptr<TileNode> tileNode = _modelCompiler->compile(model, _frame, progress); #endif // see if this tile might have children. bool prepareForChildren = setupChildrenIfNecessary && model->_tileKey.getLOD() < *_options.maxLOD(); osg::Node* result = 0L; if ( prepareForChildren ) { osg::BoundingSphere bs = tileNode->getBound(); TilePagedLOD* plod = new TilePagedLOD( _engine->getUID(), _liveTiles.get(), _releaser.get() ); plod->setCenter ( bs.center() ); plod->addChild ( tileNode.get() ); plod->setFileName( 1, Stringify() << tileNode->getKey().str() << "." << _engine->getUID() << ".osgearth_engine_mp_tile" ); double rangeFactor = _options.minTileRangeFactor().get(); //if (_options.adaptivePolarRangeFactor() == true) //{ // double lat = model->_tileKey.getExtent().yMin() < 0 ? -model->_tileKey.getExtent().yMax() : model->_tileKey.getExtent().yMin(); // double latRad = osg::DegreesToRadians(lat); // rangeFactor -= (rangeFactor - 1.0)*sin(latRad)*sin(latRad); //} plod->setRangeFactor(rangeFactor); // Setup expiration. if (_options.minExpiryFrames().isSet()) { plod->setMinimumExpiryFrames(1, *_options.minExpiryFrames()); } if (_options.minExpiryTime().isSet()) { plod->setMinimumExpiryTime(1, *_options.minExpiryTime()); } if ( _options.rangeMode().value() == osg::LOD::DISTANCE_FROM_EYE_POINT ) { //Compute the min range based on the 2D size of the tile GeoExtent extent = model->_tileKey.getExtent(); double radius = 0.0; GeoPoint lowerLeft(extent.getSRS(), extent.xMin(), extent.yMin(), 0.0, ALTMODE_ABSOLUTE); GeoPoint upperRight(extent.getSRS(), extent.xMax(), extent.yMax(), 0.0, ALTMODE_ABSOLUTE); osg::Vec3d ll, ur; lowerLeft.toWorld( ll ); upperRight.toWorld( ur ); double radiusDiag = (ur - ll).length() / 2.0; if (_options.adaptivePolarRangeFactor() == true ) { GeoPoint left(extent.getSRS(), extent.xMin(), extent.yMin()+extent.height()*0.5, 0.0, ALTMODE_ABSOLUTE); GeoPoint right(extent.getSRS(), extent.xMax(), extent.yMin()+extent.height()*0.5, 0.0, ALTMODE_ABSOLUTE); osg::Vec3d l, r; left.toWorld(l); right.toWorld(r); double radiusHoriz = 1.4142 * (r - l).length() / 2.0; double lat = model->_tileKey.getExtent().yMin() < 0 ? -model->_tileKey.getExtent().yMax() : model->_tileKey.getExtent().yMin(); double latRad = osg::DegreesToRadians(lat); // mix between diagonal radius and horizontal radius based on latitude double t = cos(latRad); t = 1.0-(1.0-t)*(1.0-t); // decelerate t to weight the mix in favor of equator (diag radius) radius = t*radiusDiag + (1.0-t)*radiusHoriz; } else { radius = radiusDiag; } float minRange = radius; plod->setRange( 0, minRange, FLT_MAX ); plod->setRange( 1, 0, minRange ); plod->setRangeMode( osg::LOD::DISTANCE_FROM_EYE_POINT ); } else { // the *2 is because we page in 4-tile sets, not individual tiles. float size = 2.0f * _options.tilePixelSize().value(); plod->setRange( 0, 0.0f, size ); plod->setRange( 1, size, FLT_MAX ); plod->setRangeMode( osg::LOD::PIXEL_SIZE_ON_SCREEN ); } // Install a tile-aligned bounding box in the pager node itself so we can do // visibility testing before paging in subtiles. plod->setChildBoundingBoxAndMatrix( 1, tileNode->getTerrainBoundingBox(), tileNode->getMatrix() ); // DBPager will set a priority based on the ratio range/maxRange. // This will offset that number with a full LOD #, giving LOD precedence. // Experimental. //plod->setPriorityScale( 1, model->_tileKey.getLOD()+1 ); #if USE_FILELOCATIONCALLBACK osgDB::Options* options = plod->getOrCreateDBOptions(); options->setFileLocationCallback( new FileLocationCallback() ); #endif result = plod; // this one rejects back-facing tiles: if ( _frame.getMapInfo().isGeocentric() && _options.clusterCulling() == true ) { osg::HeightField* hf = model->_elevationData.getHeightField(); result->addCullCallback( HeightFieldUtils::createClusterCullingCallback( hf, tileNode->getKey().getProfile()->getSRS()->getEllipsoid(), *_options.verticalScale() ) ); } } else { result = tileNode.release(); } return result; }