bool CacheTileHandler::handleTile(const TileKey& key, const TileVisitor& tv) { ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() ); ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() ); // Just call createImage or createHeightField on the layer and the it will be cached! if (imageLayer) { GeoImage image = imageLayer->createImage( key ); if (image.valid()) { return true; } } else if (elevationLayer ) { GeoHeightField hf = elevationLayer->createHeightField(key, 0L); if (hf.valid()) { return true; } } // If we didn't produce a result but the key isn't within range then we should continue to // traverse the children b/c a min level was set. if (!_layer->isKeyInLegalRange(key)) { return true; } return false; }
osg::Image* createImage( const TileKey& key, ProgressCallback* progress ) { // Use the underlying ElevationLayer to create a heightfield and then color it. GeoHeightField geoHF = _layer->createHeightField(key, progress); if (geoHF.valid()) { osg::HeightField* hf = geoHF.getHeightField(); osg::Image* image = new osg::Image(); image->allocateImage(hf->getNumColumns(),hf->getNumRows(),1, GL_RGBA, GL_UNSIGNED_BYTE); memset(image->data(), 0, image->getImageSizeInBytes()); ImageUtils::PixelWriter writer(image); for (unsigned int c = 0; c < hf->getNumColumns(); c++) { for (unsigned int r = 0; r < hf->getNumRows(); r++) { float v = hf->getHeight(c, r ); if (v != NO_DATA_VALUE) { osg::Vec4 color = _transferFunction->getColor(v); writer(color, c, r); } } } return image; } return NULL; }
bool handleTile(const TileKey& key) { bool ok = false; GeoHeightField hf = _source->createHeightField(key, 0L); if ( hf.valid() ) ok = _dest->storeHeightField(key, hf.getHeightField(), 0L); return ok; }
osg::Vec3d getWorld( const GeoHeightField& geoHF, unsigned int c, unsigned int r) { double x = geoHF.getExtent().xMin() + (double)c * geoHF.getXInterval(); double y = geoHF.getExtent().yMin() + (double)r * geoHF.getYInterval(); double h = geoHF.getHeightField()->getHeight(c,r); osg::Vec3d world; GeoPoint point(geoHF.getExtent().getSRS(), x, y, h ); point.toWorld( world ); return world; }
bool CacheTileHandler::handleTile( const TileKey& key ) { ImageLayer* imageLayer = dynamic_cast< ImageLayer* >( _layer.get() ); ElevationLayer* elevationLayer = dynamic_cast< ElevationLayer* >( _layer.get() ); // Just call createImage or createHeightField on the layer and the it will be cached! if (imageLayer) { GeoImage image = imageLayer->createImage( key ); if (image.valid()) { return true; } } else if (elevationLayer ) { GeoHeightField hf = elevationLayer->createHeightField( key ); if (hf.valid()) { return true; } } return false; }
bool ElevationLayerVector::populateHeightFieldAndNormalMap(osg::HeightField* hf, NormalMap* normalMap, const TileKey& key, const Profile* haeProfile, ElevationInterpolation interpolation, ProgressCallback* progress ) const { // heightfield must already exist. if ( !hf ) return false; METRIC_SCOPED("ElevationLayer.populateHeightField"); // 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.getLOD(), key.getTileX(), key.getTileY(), haeProfile ); } // Collect the valid layers for this tile. LayerDataVector contenders; LayerDataVector offsets; #ifdef ANALYZE struct LayerAnalysis { LayerAnalysis() : samples(0), used(false), failed(false), fallback(false), actualKeyValid(true) { } int samples; bool used; bool failed; bool fallback; bool actualKeyValid; std::string message; }; std::map<ElevationLayer*, LayerAnalysis> layerAnalysis; #endif // Track the number of layers that would return fallback data. unsigned numFallbackLayers = 0; // Check them in reverse order since the highest priority is last. for (int i = size()-1; i>=0; --i) //for(ElevationLayerVector::const_reverse_iterator i = this->rbegin(); i != this->rend(); ++i) { ElevationLayer* layer = (*this)[i].get(); //i->get(); if ( layer->getEnabled() && layer->getVisible() ) { // calculate the resolution-mapped key (adjusted for tile resolution differential). TileKey mappedKey = keyToUse.mapResolution( hf->getNumColumns(), layer->getTileSize() ); bool useLayer = true; TileKey bestKey( mappedKey ); // Check whether the non-mapped key is valid according to the user's min/max level settings: if ( !layer->isKeyInLegalRange(key) ) { useLayer = false; } // Find the "best available" mapped key from the tile source: else { bestKey = layer->getBestAvailableTileKey(mappedKey); if (bestKey.valid()) { // If the bestKey is not the mappedKey, this layer is providing // fallback data (data at a lower resolution than requested) if ( mappedKey != bestKey ) { numFallbackLayers++; } } else { useLayer = false; } } if ( useLayer ) { if ( layer->isOffset() ) { offsets.push_back(LayerData()); LayerData& ld = offsets.back(); ld.layer = layer; ld.key = bestKey; ld.index = i; } else { contenders.push_back(LayerData()); LayerData& ld = contenders.back(); ld.layer = layer; ld.key = bestKey; ld.index = i; } #ifdef ANALYZE layerAnalysis[layer].used = true; #endif } } } // nothing? bail out. if ( contenders.empty() && offsets.empty() ) { return false; } // if everything is fallback data, bail out. if ( contenders.size() + offsets.size() == numFallbackLayers ) { return false; } // Sample the layers into our target. unsigned numColumns = hf->getNumColumns(); unsigned numRows = hf->getNumRows(); double xmin = key.getExtent().xMin(); double ymin = key.getExtent().yMin(); double dx = key.getExtent().width() / (double)(numColumns-1); double dy = key.getExtent().height() / (double)(numRows-1); // We will load the actual heightfields on demand. We might not need them all. GeoHeightFieldVector heightFields(contenders.size()); GeoHeightFieldVector offsetFields(offsets.size()); std::vector<bool> heightFallback(contenders.size(), false); std::vector<bool> heightFailed(contenders.size(), false); std::vector<bool> offsetFailed(offsets.size(), false); // The maximum number of heightfields to keep in this local cache const unsigned maxHeightFields = 50; unsigned numHeightFieldsInCache = 0; const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); bool realData = false; unsigned int total = numColumns * numRows; // query resolution interval (x, y) of each sample. osg::ref_ptr<osg::ShortArray> deltaLOD = new osg::ShortArray(total); int nodataCount = 0; TileKey scratchKey; // Storage if a new key needs to be constructed bool requiresResample = true; // If we only have a single contender layer, and the tile is the same size as the requested // heightfield then we just use it directly and avoid having to resample it if (contenders.size() == 1 && offsets.empty()) { ElevationLayer* layer = contenders[0].layer.get(); TileKey& contenderKey = contenders[0].key; GeoHeightField layerHF = layer->createHeightField(contenderKey, 0); if (layerHF.valid()) { if (layerHF.getHeightField()->getNumColumns() == hf->getNumColumns() && layerHF.getHeightField()->getNumRows() == hf->getNumRows()) { requiresResample = false; memcpy(hf->getFloatArray()->asVector().data(), layerHF.getHeightField()->getFloatArray()->asVector().data(), sizeof(float) * hf->getFloatArray()->size() ); deltaLOD->resize(hf->getFloatArray()->size(), 0); realData = true; } } } // If we need to mosaic multiple layers or resample it to a new output tilesize go through a resampling loop. if (requiresResample) { for (unsigned c = 0; c < numColumns; ++c) { double x = xmin + (dx * (double)c); // periodically check for cancelation if (progress && progress->isCanceled()) { return false; } for (unsigned r = 0; r < numRows; ++r) { double y = ymin + (dy * (double)r); // Collect elevations from each layer as necessary. int resolvedIndex = -1; osg::Vec3 normal_sum(0, 0, 0); for (int i = 0; i < contenders.size() && resolvedIndex < 0; ++i) { ElevationLayer* layer = contenders[i].layer.get(); TileKey& contenderKey = contenders[i].key; int index = contenders[i].index; if (heightFailed[i]) continue; TileKey* actualKey = &contenderKey; GeoHeightField& layerHF = heightFields[i]; if (!layerHF.valid()) { // We couldn't get the heightfield from the cache, so try to create it. // We also fallback on parent layers to make sure that we have data at the location even if it's fallback. while (!layerHF.valid() && actualKey->valid() && layer->isKeyInLegalRange(*actualKey)) { layerHF = layer->createHeightField(*actualKey, progress); if (!layerHF.valid()) { if (actualKey != &scratchKey) { scratchKey = *actualKey; actualKey = &scratchKey; } *actualKey = actualKey->createParentKey(); } } // Mark this layer as fallback if necessary. if (layerHF.valid()) { heightFallback[i] = (*actualKey != contenderKey); // actualKey != contenders[i].second; numHeightFieldsInCache++; } else { heightFailed[i] = true; #ifdef ANALYZE layerAnalysis[layer].failed = true; layerAnalysis[layer].actualKeyValid = actualKey->valid(); if (progress) layerAnalysis[layer].message = progress->message(); #endif continue; } } if (layerHF.valid()) { bool isFallback = heightFallback[i]; #ifdef ANALYZE layerAnalysis[layer].fallback = isFallback; #endif // We only have real data if this is not a fallback heightfield. if (!isFallback) { realData = true; } float elevation; if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation)) { if (elevation != NO_DATA_VALUE) { // remember the index so we can only apply offset layers that // sit on TOP of this layer. resolvedIndex = index; hf->setHeight(c, r, elevation); #ifdef ANALYZE layerAnalysis[layer].samples++; #endif if (deltaLOD) { (*deltaLOD)[r*numColumns + c] = key.getLOD() - actualKey->getLOD(); } } else { ++nodataCount; } } } // Clear the heightfield cache if we have too many heightfields in the cache. if (numHeightFieldsInCache >= maxHeightFields) { //OE_NOTICE << "Clearing cache" << std::endl; for (unsigned int k = 0; k < heightFields.size(); k++) { heightFields[k] = GeoHeightField::INVALID; heightFallback[k] = false; } numHeightFieldsInCache = 0; } } for (int i = offsets.size() - 1; i >= 0; --i) { // Only apply an offset layer if it sits on top of the resolved layer // (or if there was no resolved layer). if (resolvedIndex >= 0 && offsets[i].index < resolvedIndex) continue; TileKey &contenderKey = offsets[i].key; if (offsetFailed[i] == true) continue; GeoHeightField& layerHF = offsetFields[i]; if (!layerHF.valid()) { ElevationLayer* offset = offsets[i].layer.get(); layerHF = offset->createHeightField(contenderKey, progress); if (!layerHF.valid()) { offsetFailed[i] = true; continue; } } // If we actually got a layer then we have real data realData = true; float elevation = 0.0f; if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) && elevation != NO_DATA_VALUE) { hf->getHeight(c, r) += elevation; // Update the resolution tracker to account for the offset. Sadly this // will wipe out the resolution of the actual data, and might result in // normal faceting. See the comments on "createNormalMap" for more info if (deltaLOD) { (*deltaLOD)[r*numColumns + c] = key.getLOD() - contenderKey.getLOD(); } } } } } } if (normalMap) { // periodically check for cancelation if (progress && progress->isCanceled()) { return false; } createNormalMap(key.getExtent(), hf, deltaLOD.get(), normalMap); } #ifdef ANALYZE { static Threading::Mutex m; Threading::ScopedMutexLock lock(m); std::cout << key.str() << ": "; for (std::map<ElevationLayer*, LayerAnalysis>::const_iterator i = layerAnalysis.begin(); i != layerAnalysis.end(); ++i) { std::cout << i->first->getName() << " used=" << i->second.used << " failed=" << i->second.failed << " akv=" << i->second.actualKeyValid << " fallback=" << i->second.fallback << " samples=" << i->second.samples << " msg=" << i->second.message << "; "; } std::cout << std::endl; } #endif if (progress && progress->isCanceled()) { return false; } // Return whether or not we actually read any real data return realData; }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { METRIC_SCOPED_EX("ElevationLayer::createHeightField", 2, "key", key.str().c_str(), "name", getName().c_str()); if (getStatus().isError()) { return GeoHeightField::INVALID; } // If the layer is disabled, bail out. if ( getEnabled() == false ) { return GeoHeightField::INVALID; } GeoHeightField result; osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<NormalMap> normalMap; // Check the memory cache first bool fromMemCache = false; // cache key combines the key with the full signature (incl vdatum) // the cache key combines the Key and the horizontal profile. std::string cacheKey = Cache::makeCacheKey( Stringify() << key.str() << "-" << key.getProfile()->getHorizSignature(), "elevation"); const CachePolicy& policy = getCacheSettings()->cachePolicy().get(); if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); ReadResult cacheResult = bin->readObject(cacheKey, 0L); if ( cacheResult.succeeded() ) { result = GeoHeightField( static_cast<osg::HeightField*>(cacheResult.releaseObject()), key.getExtent()); fromMemCache = true; } } if ( !result.valid() ) { // See if there's a persistent cache. CacheBin* cacheBin = getCacheBin( key.getProfile() ); // Can we continue? Only if either: // a) there is a valid tile source plugin; // b) a tile source is not expected, meaning the subclass overrides getHeightField; or // c) we are in cache-only mode and there is a valid cache bin. bool canContinue = getTileSource() || !isTileSourceExpected() || (policy.isCacheOnly() && cacheBin != 0L); if (!canContinue) { disable("Error: layer does not have a valid TileSource, cannot create heightfield"); return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !policy.isCacheOnly() && !getProfile() ) { disable("Could not establish a valid profile.. did you set one?"); return GeoHeightField::INVALID; } // Now attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; osg::ref_ptr< osg::HeightField > cachedHF; if ( cacheBin && policy.isCacheReadable() ) { ReadResult r = cacheBin->readObject(cacheKey, 0L); if ( r.succeeded() ) { bool expired = policy.isExpired(r.lastModifiedTime()); cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF.get()) ) { if (!expired) { hf = cachedHF; fromCache = true; } } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !hf.valid() && policy.isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !hf.valid() ) { if ( !isKeyInLegalRange(key) ) return GeoHeightField::INVALID; // If no tile source is expected, create a height field by calling // the raw inheritable method. if (!isTileSourceExpected()) { createImplementation(key, hf, normalMap, progress); //hf = createHeightFieldImplementation(key, progress); } else { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; // build a HF from the TileSource. //hf = createHeightFieldImplementation( key, progress ); createImplementation(key, hf, normalMap, progress); } // Check for cancelation before writing to a cache if (progress && progress->isCanceled()) { return GeoHeightField::INVALID; } // validate it to make sure it's legal. if ( hf.valid() && !validateHeightField(hf.get()) ) { OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl; hf = 0L; // to fall back on cached data if possible. } // cache if necessary if ( hf && cacheBin && !fromCache && policy.isCacheWriteable() ) { cacheBin->write(cacheKey, hf.get(), 0L); } // We have an expired heightfield from the cache and no new data from the TileSource. So just return the cached data. if (!hf.valid() && cachedHF.valid()) { OE_DEBUG << LC << "Using cached but expired heightfield for " << key.str() << std::endl; hf = cachedHF; } if ( !hf.valid() ) { return GeoHeightField::INVALID; } // Set up the heightfield params. double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(hf->getNumColumns()-1); double dy = (maxy - miny)/(double)(hf->getNumRows()-1); hf->setXInterval( dx ); hf->setYInterval( dy ); hf->setBorderWidth( 0 ); } if ( hf.valid() ) { result = GeoHeightField( hf.get(), normalMap.get(), key.getExtent() ); } } // Check for cancelation before writing to a cache: if ( progress && progress->isCanceled() ) { return GeoHeightField::INVALID; } // post-processing -- must be done before caching because it may alter the heightfield data if ( result.valid() && !fromMemCache && hf.valid() ) { if ( options().noDataPolicy() == NODATA_MSL ) { // requested VDatum: const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum(); const Geoid* geoid = 0L; // if there's an output vdatum, just set all invalid's to zero MSL. if ( outputVDatum == 0L ) { // if the output is geodetic (HAE), but the input has a geoid, // use that geoid to populate the invalid data at sea level. const VerticalDatum* profileDatum = getProfile()->getSRS()->getVerticalDatum(); if ( profileDatum ) geoid = profileDatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( hf.get(), result.getExtent(), NO_DATA_VALUE, geoid ); } } // write to mem cache if needed: if ( result.valid() && !fromMemCache && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); bin->write(cacheKey, result.getHeightField(), 0L); } return result; }
bool ElevationQuery::getElevationImpl(const GeoPoint& point, double& out_elevation, double desiredResolution, double* out_actualResolution) { osg::Timer_t start = osg::Timer::instance()->tick(); if ( _mapf.elevationLayers().empty() ) { // this means there are no heightfields. out_elevation = 0.0; return true; } // tile size (resolution of elevation tiles) unsigned tileSize = std::max(_mapf.getMapOptions().elevationTileSize().get(), 2u); //This is the max resolution that we actually have data at this point unsigned int bestAvailLevel = getMaxLevel( point.x(), point.y(), point.getSRS(), _mapf.getProfile()); if (desiredResolution > 0.0) { unsigned int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, tileSize ); if (desiredLevel < bestAvailLevel) bestAvailLevel = desiredLevel; } OE_DEBUG << LC << "Best available data level " << point.x() << ", " << point.y() << " = " << bestAvailLevel << std::endl; // transform the input coords to map coords: GeoPoint mapPoint = point; if ( point.isValid() && !point.getSRS()->isHorizEquivalentTo( _mapf.getProfile()->getSRS() ) ) { mapPoint = point.transform(_mapf.getProfile()->getSRS()); if ( !mapPoint.isValid() ) { OE_WARN << LC << "Fail: coord transform failed" << std::endl; return false; } } // get the tilekey corresponding to the tile we need: TileKey key = _mapf.getProfile()->createTileKey( mapPoint.x(), mapPoint.y(), bestAvailLevel ); if ( !key.valid() ) { OE_WARN << LC << "Fail: coords fall outside map" << std::endl; return false; } bool result = false; while (!result) { GeoHeightField geoHF; TileCache::Record record; // Try to get the hf from the cache if ( _cache.get( key, record ) ) { geoHF = record.value(); } else { // Create it osg::ref_ptr<osg::HeightField> hf = new osg::HeightField(); hf->allocate( tileSize, tileSize ); // Initialize the heightfield to nodata for (unsigned int i = 0; i < hf->getFloatArray()->size(); i++) { hf->getFloatArray()->at( i ) = NO_DATA_VALUE; } if (_mapf.populateHeightField( hf, key ) ) { geoHF = GeoHeightField( hf.get(), key.getExtent() ); _cache.insert( key, geoHF ); } } if (geoHF.valid()) { float elevation = 0.0f; result = geoHF.getElevation( mapPoint.getSRS(), mapPoint.x(), mapPoint.y(), _mapf.getMapInfo().getElevationInterpolation(), mapPoint.getSRS(), elevation); if (result && elevation != NO_DATA_VALUE) { // see what the actual resolution of the heightfield is. if ( out_actualResolution ) *out_actualResolution = geoHF.getXInterval(); out_elevation = (double)elevation; break; } else { result = false; } } if (!result) { key = key.createParentKey(); if (!key.valid()) { break; } } } osg::Timer_t end = osg::Timer::instance()->tick(); _queries++; _totalTime += osg::Timer::instance()->delta_s( start, end ); return result; }
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; //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. unsigned defElevSize = 8; for( ElevationLayerVector::const_iterator i = this->begin(); i != this->end(); i++ ) { ElevationLayer* layer = i->get(); if ( layer->getVisible() ) { GeoHeightField 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() ) { 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 ) { out_result = HeightFieldUtils::createReferenceHeightField( keyToUse.getExtent(), defElevSize, defElevSize ); 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; } } else { //If we have multiple heightfields, we need to composite them together. unsigned int width = 0; unsigned int height = 0; 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(); } out_result = new osg::HeightField(); out_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)(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 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); //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 ); //ReplaceInvalidDataOperator o; //o.setValidDataOperator(new osgTerrain::NoDataValue(NO_DATA_VALUE)); //o( out_result.get() ); } //Initialize the HF values for osgTerrain 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 ); } return out_result.valid(); }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { GeoHeightField result; osg::ref_ptr<osg::HeightField> hf; // If the layer is disabled, bail out. if ( getEnabled() == false ) { return GeoHeightField::INVALID; } // Check the memory cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); ReadResult cacheResult = bin->readObject(key.str() ); if ( cacheResult.succeeded() ) { result = GeoHeightField( static_cast<osg::HeightField*>(cacheResult.releaseObject()), key.getExtent()); } //_memCache->dumpStats(key.getProfile()->getFullSignature()); } if ( !result.valid() ) { // See if there's a persistent cache. CacheBin* cacheBin = getCacheBin( key.getProfile() ); // validate that we have either a valid tile source, or we're cache-only. if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) ) { OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create heightfield" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // Now attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; osg::ref_ptr< osg::HeightField > cachedHF; if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readObject( key.str() ); if ( r.succeeded() ) { bool expired = getCachePolicy().isExpired(r.lastModifiedTime()); cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF) ) { if (!expired) { hf = cachedHF; fromCache = true; } } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !hf.valid() && isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !hf.valid() ) { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; if ( !isKeyInRange(key) ) return GeoHeightField::INVALID; // build a HF from the TileSource. hf = createHeightFieldFromTileSource( key, progress ); // validate it to make sure it's legal. if ( hf.valid() && !validateHeightField(hf.get()) ) { OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl; hf = 0L; // to fall back on cached data if possible. } // memory cache first: if ( hf && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); bin->write(key.str(), hf.get()); } // cache if necessary if ( hf && cacheBin && !fromCache && getCachePolicy().isCacheWriteable() ) { cacheBin->write( key.str(), hf ); } // We have an expired heightfield from the cache and no new data from the TileSource. So just return the cached data. if (!hf.valid() && cachedHF.valid()) { OE_DEBUG << LC << "Using cached but expired heightfield for " << key.str() << std::endl; hf = cachedHF; } if ( !hf.valid() ) { return GeoHeightField::INVALID; } // 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); hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(hf->getNumColumns()-1); double dy = (maxy - miny)/(double)(hf->getNumRows()-1); hf->setXInterval( dx ); hf->setYInterval( dy ); hf->setBorderWidth( 0 ); } if ( hf.valid() ) { result = GeoHeightField( hf.get(), key.getExtent() ); } } // post-processing: if ( result.valid() ) { if ( _runtimeOptions.noDataPolicy() == NODATA_MSL ) { // requested VDatum: const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum(); const Geoid* geoid = 0L; // if there's an output vdatum, just set all invalid's to zero MSL. if ( outputVDatum == 0L ) { // if the output is geodetic (HAE), but the input has a geoid, // use that geoid to populate the invalid data at sea level. const VerticalDatum* profileDatum = getProfile()->getSRS()->getVerticalDatum(); if ( profileDatum ) geoid = profileDatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( result.getHeightField(), result.getExtent(), NO_DATA_VALUE, geoid ); } } return result; }
bool ElevationLayerVector::populateHeightField(osg::HeightField* hf, const TileKey& key, const Profile* haeProfile, ElevationInterpolation interpolation, ProgressCallback* progress ) const { //osg::Timer_t startTime = osg::Timer::instance()->tick(); // heightfield must already exist. if ( !hf ) return false; // 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.getLOD(), key.getTileX(), key.getTileY(), haeProfile ); } // Collect the valid layers for this tile. LayerAndKeyVector contenders; LayerAndKeyVector offsets; // Track the number of layers that would return fallback data. unsigned numFallbackLayers = 0; // Check them in reverse order since the highest priority is last. for(ElevationLayerVector::const_reverse_iterator i = this->rbegin(); i != this->rend(); ++i) { ElevationLayer* layer = i->get(); if ( layer->getEnabled() && layer->getVisible() ) { // calculate the resolution-mapped key (adjusted for tile resolution differential). TileKey mappedKey = keyToUse.mapResolution( hf->getNumColumns(), layer->getTileSize() ); bool useLayer = true; TileKey bestKey( mappedKey ); // Is there a tilesource? If not we are cache-only and cannot reject the layer. if ( layer->getTileSource() ) { // Check whether the non-mapped key is valid according to the user's min/max level settings: if ( !layer->isKeyInRange(key) ) { useLayer = false; } // Find the "best available" mapped key from the tile source: else { if ( layer->getTileSource()->getBestAvailableTileKey(mappedKey, bestKey) ) { // If the bestKey is not the mappedKey, this layer is providing // fallback data (data at a lower resolution than requested) if ( mappedKey != bestKey ) { numFallbackLayers++; } } else { useLayer = false; } } } if ( useLayer ) { if ( layer->isOffset() ) { offsets.push_back( std::make_pair(layer, bestKey) ); } else { contenders.push_back( std::make_pair(layer, bestKey) ); } } } } // nothing? bail out. if ( contenders.empty() && offsets.empty() ) { return false; } // if everything is fallback data, bail out. if ( contenders.size() + offsets.size() == numFallbackLayers ) { return false; } // Sample the layers into our target. unsigned numColumns = hf->getNumColumns(); unsigned numRows = hf->getNumRows(); double xmin = key.getExtent().xMin(); double ymin = key.getExtent().yMin(); double dx = key.getExtent().width() / (double)(numColumns-1); double dy = key.getExtent().height() / (double)(numRows-1); // We will load the actual heightfields on demand. We might not need them all. GeoHeightFieldVector heightFields(contenders.size()); GeoHeightFieldVector offsetFields(offsets.size()); std::vector<bool> heightFailed(contenders.size(), false); std::vector<bool> offsetFailed(offsets.size(), false); // The maximum number of heightfields to keep in this local cache unsigned int maxHeightFields = 50; unsigned numHeightFieldsInCache = 0; //double fallBackTime = 0; const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); bool realData = false; //unsigned int numFallback = 0; unsigned int total = numColumns * numRows; unsigned int completed = 0; for (unsigned c = 0; c < numColumns; ++c) { double x = xmin + (dx * (double)c); for (unsigned r = 0; r < numRows; ++r) { double y = ymin + (dy * (double)r); // Collect elevations from each layer as necessary. bool resolved = false; for(int i=0; i<contenders.size() && !resolved; ++i) { if ( heightFailed[i] ) continue; ElevationLayer* layer = contenders[i].first.get(); GeoHeightField& layerHF = heightFields[i]; if ( !layerHF.valid() ) { layerHF = layer->createHeightField(contenders[i].second, progress); if ( !layerHF.valid() ) { // This layer potentially has data or it wouldn't have ended up in the contendors list, so try falling back on the parent TileKey parentKey = contenders[i].second.createParentKey(); while (!layerHF.valid() && parentKey.valid()) { //numFallback++; //osg::Timer_t fbStartTime = osg::Timer::instance()->tick(); GeoHeightField parentHF = layer->createHeightField(parentKey, progress); //osg::Timer_t fbEndTime = osg::Timer::instance()->tick(); // Only penalize time wasted actually falling back. //if (!parentHF.valid()) // { // fallBackTime += osg::Timer::instance()->delta_m(fbStartTime, fbEndTime); //} if (parentHF.valid()) { layerHF = parentHF; break; } else { parentKey = parentKey.createParentKey(); } } if (!layerHF.valid()) { heightFailed[i] = true; continue; } } else { numHeightFieldsInCache++; } } // If we actually got a layer then we have real data realData = true; float elevation; if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) && elevation != NO_DATA_VALUE) { resolved = true; hf->setHeight(c, r, elevation); } // Clear the heightfield cache if we have too many heightfields in the cache. if (numHeightFieldsInCache >= maxHeightFields) { //OE_NOTICE << "Clearing cache" << std::endl; for (unsigned int k = 0; k < heightFields.size(); k++) { heightFields[k] = GeoHeightField::INVALID; } numHeightFieldsInCache = 0; } } for(int i=offsets.size()-1; i>=0; --i) { if ( offsetFailed[i] ) continue; GeoHeightField& layerHF = offsetFields[i]; if ( !layerHF.valid() ) { ElevationLayer* offset = offsets[i].first.get(); layerHF = offset->createHeightField(offsets[i].second, progress); if ( !layerHF.valid() ) { offsetFailed[i] = true; continue; } } // If we actually got a layer then we have real data realData = true; float elevation = 0.0f; if (layerHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) && elevation != NO_DATA_VALUE) { hf->getHeight(c, r) += elevation; } } completed++; //OE_NOTICE << "Completed " << completed << " of " << total << std::endl; } } //osg::Timer_t endTime = osg::Timer::instance()->tick(); //double totalTime = osg::Timer::instance()->delta_m(startTime, endTime); // double fallbackPercentage = fallBackTime / totalTime; //if (fallBackTime > 0) //{ // OE_NOTICE << "populateHeightField took " << totalTime << "ms fallbacktime=" << fallBackTime << "ms count=" << numFallback << " percentage=" << fallbackPercentage << std::endl; //} //else //{ // OE_NOTICE << "populateHeightField took " << totalTime << "ms" << std::endl; //} // Return whether or not we actually read any real data return realData; }
bool ElevationQuery::getElevationImpl(const GeoPoint& point, /* abs */ double& out_elevation, double desiredResolution, double* out_actualResolution) { // assertion. if ( !point.isAbsolute() ) { OE_WARN << LC << "Assertion failure; input must be absolute" << std::endl; return false; } osg::Timer_t begin = osg::Timer::instance()->tick(); // first try the terrain patches. if ( _patchLayers.size() > 0 ) { osgUtil::IntersectionVisitor iv; for(std::vector<ModelLayer*>::iterator i = _patchLayers.begin(); i != _patchLayers.end(); ++i) { // find the scene graph for this layer: osg::Node* node = (*i)->getSceneGraph( _mapf.getUID() ); if ( node ) { // configure for intersection: osg::Vec3d surface; point.toWorld( surface ); // trivial bounds check: if ( node->getBound().contains(surface) ) { osg::Vec3d nvector; point.createWorldUpVector(nvector); osg::Vec3d start( surface + nvector*5e5 ); osg::Vec3d end ( surface - nvector*5e5 ); // first time through, set up the intersector on demand if ( !_patchLayersLSI.valid() ) { _patchLayersLSI = new DPLineSegmentIntersector(start, end); _patchLayersLSI->setIntersectionLimit( _patchLayersLSI->LIMIT_NEAREST ); } else { _patchLayersLSI->reset(); _patchLayersLSI->setStart( start ); _patchLayersLSI->setEnd ( end ); } // try it. iv.setIntersector( _patchLayersLSI.get() ); node->accept( iv ); // check for a result!! if ( _patchLayersLSI->containsIntersections() ) { osg::Vec3d isect = _patchLayersLSI->getIntersections().begin()->getWorldIntersectPoint(); // transform back to input SRS: GeoPoint output; output.fromWorld( point.getSRS(), isect ); out_elevation = output.z(); if ( out_actualResolution ) *out_actualResolution = 0.0; return true; } } else { //OE_INFO << LC << "Trivial rejection (bounds check)" << std::endl; } } } } if ( _mapf.elevationLayers().empty() ) { // this means there are no heightfields. out_elevation = 0.0; return true; } // tile size (resolution of elevation tiles) unsigned tileSize = std::max(_mapf.getMapOptions().elevationTileSize().get(), 2u); //This is the max resolution that we actually have data at this point unsigned int bestAvailLevel = getMaxLevel( point.x(), point.y(), point.getSRS(), _mapf.getProfile()); if (desiredResolution > 0.0) { unsigned int desiredLevel = _mapf.getProfile()->getLevelOfDetailForHorizResolution( desiredResolution, tileSize ); if (desiredLevel < bestAvailLevel) bestAvailLevel = desiredLevel; } OE_DEBUG << LC << "Best available data level " << point.x() << ", " << point.y() << " = " << bestAvailLevel << std::endl; // transform the input coords to map coords: GeoPoint mapPoint = point; if ( point.isValid() && !point.getSRS()->isHorizEquivalentTo( _mapf.getProfile()->getSRS() ) ) { mapPoint = point.transform(_mapf.getProfile()->getSRS()); if ( !mapPoint.isValid() ) { OE_WARN << LC << "Fail: coord transform failed" << std::endl; return false; } } // get the tilekey corresponding to the tile we need: TileKey key = _mapf.getProfile()->createTileKey( mapPoint.x(), mapPoint.y(), bestAvailLevel ); if ( !key.valid() ) { OE_WARN << LC << "Fail: coords fall outside map" << std::endl; return false; } bool result = false; while (!result) { GeoHeightField geoHF; TileCache::Record record; // Try to get the hf from the cache if ( _cache.get( key, record ) ) { geoHF = record.value(); } else { // Create it osg::ref_ptr<osg::HeightField> hf = new osg::HeightField(); hf->allocate( tileSize, tileSize ); // Initialize the heightfield to nodata for (unsigned int i = 0; i < hf->getFloatArray()->size(); i++) { hf->getFloatArray()->at( i ) = NO_DATA_VALUE; } if (_mapf.populateHeightField(hf, key, false)) { geoHF = GeoHeightField( hf.get(), key.getExtent() ); _cache.insert( key, geoHF ); } } if (geoHF.valid()) { float elevation = 0.0f; result = geoHF.getElevation( mapPoint.getSRS(), mapPoint.x(), mapPoint.y(), _mapf.getMapInfo().getElevationInterpolation(), mapPoint.getSRS(), elevation); if (result && elevation != NO_DATA_VALUE) { // see what the actual resolution of the heightfield is. if ( out_actualResolution ) *out_actualResolution = geoHF.getXInterval(); out_elevation = (double)elevation; break; } else { result = false; } } if (!result) { key = key.createParentKey(); if (!key.valid()) { break; } } } osg::Timer_t end = osg::Timer::instance()->tick(); _queries++; _totalTime += osg::Timer::instance()->delta_s( begin, end ); return result; }