osg::HeightField* ElevationLayer::assembleHeightFieldFromTileSource(const TileKey& key, ProgressCallback* progress) { osg::HeightField* result = 0L; // Collect the heightfields for each of the intersecting tiles. GeoHeightFieldVector heightFields; //Determine the intersecting keys std::vector< TileKey > intersectingTiles; getProfile()->getIntersectingTiles( key, intersectingTiles ); // collect heightfield for each intersecting key. Note, we're hitting the // underlying tile source here, so there's no vetical datum shifts happening yet. // we will do that later. if ( intersectingTiles.size() > 0 ) { for (unsigned int i = 0; i < intersectingTiles.size(); ++i) { const TileKey& layerKey = intersectingTiles[i]; if ( isKeyValid(layerKey) ) { osg::HeightField* hf = createHeightFieldFromTileSource( layerKey, progress ); if ( hf ) { heightFields.push_back( GeoHeightField(hf, layerKey.getExtent()) ); } else { //We couldn't get a heightfield at the given key so fall back on parent tiles TileKey parentKey = layerKey.createParentKey(); while (!hf && parentKey.valid()) { hf = createHeightFieldFromTileSource( parentKey, progress ); if (hf) { heightFields.push_back( GeoHeightField(hf, parentKey.getExtent()) ); break; } parentKey = parentKey.createParentKey(); } } } } } // If we actually got a HeightField, resample/reproject it to match the incoming TileKey's extents. if (heightFields.size() > 0) { unsigned int width = 0; unsigned int height = 0; for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { if (itr->getHeightField()->getNumColumns() > width) width = itr->getHeightField()->getNumColumns(); if (itr->getHeightField()->getNumRows() > height) height = itr->getHeightField()->getNumRows(); } result = new osg::HeightField(); result->allocate(width, height); //Go ahead and set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); double dx = (maxx - minx)/(double)(width-1); double dy = (maxy - miny)/(double)(height-1); //Create the new heightfield by sampling all of them. for (unsigned int c = 0; c < width; ++c) { double x = minx + (dx * (double)c); for (unsigned r = 0; r < height; ++r) { double y = miny + (dy * (double)r); //For each sample point, try each heightfield. The first one with a valid elevation wins. float elevation = NO_DATA_VALUE; for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { // get the elevation value, at the same time transforming it vertically into the // requesting key's vertical datum. float e = 0.0; if (itr->getElevation(key.getExtent().getSRS(), x, y, INTERP_BILINEAR, key.getExtent().getSRS(), e)) { elevation = e; break; } } result->setHeight( c, r, elevation ); } } } return result; }
void ElevationLayer::assembleHeightField(const TileKey& key, osg::ref_ptr<osg::HeightField>& out_hf, osg::ref_ptr<NormalMap>& out_normalMap, ProgressCallback* progress) { // Collect the heightfields for each of the intersecting tiles. GeoHeightFieldVector heightFields; //Determine the intersecting keys std::vector< TileKey > intersectingTiles; if (key.getLOD() > 0u) { getProfile()->getIntersectingTiles(key, intersectingTiles); } else { // LOD is zero - check whether the LOD mapping went out of range, and if so, // fall back until we get valid tiles. This can happen when you have two // profiles with very different tile schemes, and the "equivalent LOD" // surpasses the max data LOD of the tile source. unsigned numTilesThatMayHaveData = 0u; int intersectionLOD = getProfile()->getEquivalentLOD(key.getProfile(), key.getLOD()); while (numTilesThatMayHaveData == 0u && intersectionLOD >= 0) { intersectingTiles.clear(); getProfile()->getIntersectingTiles(key.getExtent(), intersectionLOD, intersectingTiles); for (unsigned int i = 0; i < intersectingTiles.size(); ++i) { const TileKey& layerKey = intersectingTiles[i]; if (mayHaveData(layerKey) == true) { ++numTilesThatMayHaveData; } } --intersectionLOD; } } // collect heightfield for each intersecting key. Note, we're hitting the // underlying tile source here, so there's no vetical datum shifts happening yet. // we will do that later. if ( intersectingTiles.size() > 0 ) { for (unsigned int i = 0; i < intersectingTiles.size(); ++i) { const TileKey& layerKey = intersectingTiles[i]; if ( isKeyInLegalRange(layerKey) ) { osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<NormalMap> normalMap; createImplementation(layerKey, hf, normalMap, progress); if (hf.valid()) { heightFields.push_back( GeoHeightField(hf.get(), normalMap.get(), layerKey.getExtent()) ); } } } // If we actually got a HeightField, resample/reproject it to match the incoming TileKey's extents. if (heightFields.size() > 0) { unsigned int width = 0; unsigned int height = 0; for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { if (itr->getHeightField()->getNumColumns() > width) width = itr->getHeightField()->getNumColumns(); if (itr->getHeightField()->getNumRows() > height) height = itr->getHeightField()->getNumRows(); } //Now sort the heightfields by resolution to make sure we're sampling the highest resolution one first. std::sort( heightFields.begin(), heightFields.end(), GeoHeightField::SortByResolutionFunctor()); out_hf = new osg::HeightField(); out_hf->allocate(width, height); out_normalMap = new NormalMap(width, height); //Go ahead and set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); double dx = (maxx - minx)/(double)(width-1); double dy = (maxy - miny)/(double)(height-1); //Create the new heightfield by sampling all of them. for (unsigned int c = 0; c < width; ++c) { double x = minx + (dx * (double)c); for (unsigned r = 0; r < height; ++r) { double y = miny + (dy * (double)r); //For each sample point, try each heightfield. The first one with a valid elevation wins. float elevation = NO_DATA_VALUE; osg::Vec3 normal(0,0,1); for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { // get the elevation value, at the same time transforming it vertically into the // requesting key's vertical datum. float e = 0.0; osg::Vec3 n; if (itr->getElevationAndNormal(key.getExtent().getSRS(), x, y, INTERP_BILINEAR, key.getExtent().getSRS(), e, n)) { elevation = e; normal = n; break; } } out_hf->setHeight( c, r, elevation ); out_normalMap->set( c, r, normal ); } } } else { //if (progress && progress->message().empty()) // progress->message() = "assemble yielded no heightfields"; } } else { //if (progress && progress->message().empty()) // progress->message() = "assemble yielded no intersecting tiles"; } // If the progress was cancelled clear out any of the output data. if (progress && progress->isCanceled()) { out_hf = 0; out_normalMap = 0; } }
bool ElevationLayerVector::createHeightField(const TileKey& key, bool fallback, const Profile* haeProfile, ElevationInterpolation interpolation, ElevationSamplePolicy samplePolicy, osg::ref_ptr<osg::HeightField>& out_result, bool* out_isFallback, ProgressCallback* progress ) const { unsigned lowestLOD = key.getLevelOfDetail(); bool hfInitialized = false; //Get a HeightField for each of the enabled layers GeoHeightFieldVector heightFields; GeoHeightFieldVector offsetHeightFields; //The number of fallback heightfields we have int numFallbacks = 0; //Default to being fallback data. if ( out_isFallback ) { *out_isFallback = true; } // if the caller provided an "HAE map profile", he wants an HAE elevation grid even if // the map profile has a vertical datum. This is the usual case when building the 3D // terrain, for example. Construct a temporary key that doesn't have the vertical // datum info and use that to query the elevation data. TileKey keyToUse = key; if ( haeProfile ) { keyToUse = TileKey(key.getLevelOfDetail(), key.getTileX(), key.getTileY(), haeProfile ); } // Generate a heightfield for each elevation layer. for( ElevationLayerVector::const_iterator i = this->begin(); i != this->end(); i++ ) { ElevationLayer* layer = i->get(); if ( layer->getEnabled() && layer->getVisible() ) { GeoHeightField geoHF; if ( layer->isKeyValid(keyToUse) ) { geoHF = layer->createHeightField( keyToUse, progress ); } // if "fallback" is set, try to fall back on lower LODs. if ( !geoHF.valid() && fallback ) { TileKey hf_key = keyToUse.createParentKey(); while ( hf_key.valid() && !geoHF.valid() ) { geoHF = layer->createHeightField( hf_key, progress ); if ( !geoHF.valid() ) hf_key = hf_key.createParentKey(); } if ( geoHF.valid() ) { if ( hf_key.getLevelOfDetail() < lowestLOD ) { lowestLOD = hf_key.getLevelOfDetail(); } //This HeightField is fallback data, so increment the count. numFallbacks++; } } if ( geoHF.valid() ) { //If the layer is offset, add it to the list of offset heightfields if (*layer->getElevationLayerOptions().offset()) { offsetHeightFields.push_back( geoHF ); } //Otherwise add it to the list of regular heightfields else { heightFields.push_back( geoHF ); } } } } //If any of the layers produced valid data then it's not considered a fallback if ( out_isFallback ) { *out_isFallback = (numFallbacks == heightFields.size()); //OE_NOTICE << "Num fallbacks=" << numFallbacks << " numHeightFields=" << heightFields.size() << " is fallback " << *out_isFallback << std::endl; } if ( heightFields.size() == 0 ) { //If we got no heightfields but were requested to fallback, create an empty heightfield. if ( fallback ) { unsigned defaultSize = _expressTileSize.getOrUse( 7 ); out_result = HeightFieldUtils::createReferenceHeightField( keyToUse.getExtent(), defaultSize, defaultSize ); if ( offsetHeightFields.size() == 0 ) return true; } else { //We weren't requested to fallback so just return. return false; } } else if (heightFields.size() == 1) { if ( lowestLOD == key.getLevelOfDetail() ) { // If we only have on heightfield, just return it. out_result = heightFields[0].takeHeightField(); } else { GeoHeightField geoHF = heightFields[0].createSubSample( key.getExtent(), interpolation); out_result = geoHF.takeHeightField(); hfInitialized = true; } // resample if necessary: if ( _expressTileSize.isSet() ) { out_result = HeightFieldUtils::resampleHeightField( out_result.get(), key.getExtent(), *_expressTileSize, *_expressTileSize, interpolation ); } } else { // If we have multiple heightfields, we need to composite them together. unsigned int width = 0; unsigned int height = 0; if ( _expressTileSize.isSet() ) { // user set a tile size; use it. width = *_expressTileSize; height = *_expressTileSize; } else { // user did not ask for a tile size; find the biggest among the layers. for (GeoHeightFieldVector::const_iterator i = heightFields.begin(); i < heightFields.end(); ++i) { if (i->getHeightField()->getNumColumns() > width) width = i->getHeightField()->getNumColumns(); if (i->getHeightField()->getNumRows() > height) height = i->getHeightField()->getNumRows(); } } // make the new heightfield. out_result = new osg::HeightField(); out_result->allocate( width, height ); // calculate the post spacings. double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1); double dy = (maxy - miny)/(double)(out_result->getNumRows()-1); const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); // Create the new heightfield by sampling all layer heightfields. for (unsigned int c = 0; c < width; ++c) { double x = minx + (dx * (double)c); for (unsigned r = 0; r < height; ++r) { double y = miny + (dy * (double)r); //Collect elevations from all of the layers. Iterate BACKWARDS because the last layer // is the highest priority. std::vector<float> elevations; for( GeoHeightFieldVector::reverse_iterator itr = heightFields.rbegin(); itr != heightFields.rend(); ++itr ) { const GeoHeightField& geoHF = *itr; float elevation = 0.0f; if ( geoHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) ) { if (elevation != NO_DATA_VALUE) { elevations.push_back(elevation); } } } float elevation = NO_DATA_VALUE; //The list of elevations only contains valid values if (elevations.size() > 0) { if (samplePolicy == SAMPLE_FIRST_VALID) { elevation = elevations[0]; } else if (samplePolicy == SAMPLE_HIGHEST) { elevation = -FLT_MAX; for (unsigned int i = 0; i < elevations.size(); ++i) { if (elevation < elevations[i]) elevation = elevations[i]; } } else if (samplePolicy == SAMPLE_LOWEST) { elevation = FLT_MAX; for (unsigned i = 0; i < elevations.size(); ++i) { if (elevation > elevations[i]) elevation = elevations[i]; } } else if (samplePolicy == SAMPLE_AVERAGE) { elevation = 0.0; for (unsigned i = 0; i < elevations.size(); ++i) { elevation += elevations[i]; } elevation /= (float)elevations.size(); } } out_result->setHeight(c, r, elevation); } } } // Replace any NoData areas with the reference value. This is zero for HAE datums, // and some geoid height for orthometric datums. if (out_result.valid()) { const Geoid* geoid = 0L; const VerticalDatum* vdatum = key.getProfile()->getSRS()->getVerticalDatum(); if ( haeProfile && vdatum ) { geoid = vdatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( out_result.get(), key.getExtent(), NO_DATA_VALUE, geoid ); } // Initialize the HF values if (out_result.valid() && !hfInitialized ) { //Go ahead and set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); out_result->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1); double dy = (maxy - miny)/(double)(out_result->getNumRows()-1); out_result->setXInterval( dx ); out_result->setYInterval( dy ); out_result->setBorderWidth( 0 ); } // Add any "offset" elevation layers to the resulting heightfield if (out_result.valid() && offsetHeightFields.size() ) { // calculate the post spacings. double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1); double dy = (maxy - miny)/(double)(out_result->getNumRows()-1); const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); for( GeoHeightFieldVector::iterator itr = offsetHeightFields.begin(); itr != offsetHeightFields.end(); ++itr ) { for (unsigned int c = 0; c < out_result->getNumColumns(); c++) { double x = minx + (dx * (double)c); for (unsigned int r = 0; r < out_result->getNumRows(); r++) { double y = miny + (dy * (double)r); float elevation = 0.0; if (itr->getElevation(keySRS, x, y, interpolation, keySRS, elevation)) { double h = out_result->getHeight( c, r ); h += elevation; out_result->setHeight( c, r, h ); } } } } } return out_result.valid(); }