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; }
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 ElevationLayerVector::populateHeightField(osg::HeightField* hf, const TileKey& key, const Profile* haeProfile, ElevationInterpolation interpolation, ProgressCallback* progress ) const { // 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. ElevationLayerVector contenders; ElevationLayerVector offsets; 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()); // Note: isKeyInRange tests the key, but haData tests the mapped key. // I think that's right! if ((layer->getTileSource() == 0L) || (layer->isKeyInRange(key) && layer->getTileSource()->hasData(mappedKey))) { if (layer->isOffset()) offsets.push_back(layer); else contenders.push_back(layer); } } } // nothing? bail out. if ( contenders.empty() && offsets.empty() ) { 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); const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); bool realData = false; 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; GeoHeightField& layerHF = heightFields[i]; if ( !layerHF.valid() ) { TileKey mappedKey = keyToUse.mapResolution(hf->getNumColumns(), contenders[i]->getTileSize()); layerHF = contenders[i]->createHeightField(mappedKey, progress); if ( !layerHF.valid() ) { heightFailed[i] = true; continue; } } // 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); } } for(int i=offsets.size()-1; i>=0; --i) { if ( offsetFailed[i] ) continue; GeoHeightField& layerHF = offsetFields[i]; if ( !layerHF.valid() ) { TileKey mappedKey = keyToUse.mapResolution(hf->getNumColumns(), offsets[i]->getTileSize()); layerHF = offsets[i]->createHeightField(mappedKey, 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; } } } } // Return whether or not we actually read any real data return realData; }
bool ElevationLayerVector::populateHeightField(osg::HeightField* hf, const TileKey& key, const Profile* haeProfile, ElevationInterpolation interpolation, ProgressCallback* progress ) const { // 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); // If the incoming heightfield requests a positive border width, // we need to adjust the extents so that we request data outside the // extent of the tile key: unsigned border = hf->getBorderWidth(); if (border > 0u) { dx = key.getExtent().width() / (double)(numColumns - (border*2+1)); dy = key.getExtent().height() / (double)(numRows - (border*2+1)); xmin -= dx * (double)border; ymin -= dy * (double)border; } // We will load the actual heightfields on demand. We might not need them all. #if 0 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); #else GeoHeightFieldVector heightFields[9]; GeoHeightFieldVector offsetFields[9]; //(offsets.size()); std::vector<bool> heightFallback[9]; //(contenders.size(), false); std::vector<bool> heightFailed[9]; //(contenders.size(), false); std::vector<bool> offsetFailed[9]; //(offsets.size(), false); for (int n = 0; n < 9; ++n) { heightFields[n].resize(contenders.size()); offsetFields[n].resize(offsets.size()); heightFallback[n].assign(9, false); heightFailed[n].assign(9, false); offsetFailed[n].assign(9, false); } #endif // The maximum number of heightfields to keep in this local cache unsigned int maxHeightFields = 50; unsigned numHeightFieldsInCache = 0; const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); bool realData = false; unsigned int total = numColumns * numRows; unsigned int completed = 0; int nodataCount = 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) { ElevationLayer* layer = contenders[i].first.get(); TileKey contenderKey = contenders[i].second; // If there is a border, the edge points may not fall within the key extents // and we may need to fetch a neighboring key. int n = 4; // index 4 is the center/default tile if (border > 0u && !contenderKey.getExtent().contains(x, y)) { int dTx = x < contenderKey.getExtent().xMin() ? -1 : x > contenderKey.getExtent().xMax() ? +1 : 0; int dTy = y < contenderKey.getExtent().yMin() ? +1 : y > contenderKey.getExtent().yMax() ? -1 : 0; contenderKey = contenderKey.createNeighborKey(dTx, dTy); n = (dTy+1)*3 + (dTx+1); } if ( heightFailed[n][i] ) continue; TileKey actualKey = contenderKey; GeoHeightField& layerHF = heightFields[n][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()) { layerHF = layer->createHeightField(actualKey, progress); if (!layerHF.valid()) { actualKey = actualKey.createParentKey(); } } // Mark this layer as fallback if necessary. if (layerHF.valid()) { heightFallback[n][i] = (actualKey != contenderKey); // actualKey != contenders[i].second; numHeightFieldsInCache++; } else { heightFailed[n][i] = true; continue; } } if (layerHF.valid()) { bool isFallback = heightFallback[n][i]; // 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 ) { resolved = true; hf->setHeight(c, r, elevation); } 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 j = 0; j < 9; ++j) { for (unsigned int k = 0; k < heightFields[j].size(); k++) { heightFields[j][k] = GeoHeightField::INVALID; heightFallback[j][k] = false; } } numHeightFieldsInCache = 0; } } for(int i=offsets.size()-1; i>=0; --i) { TileKey contenderKey = offsets[i].second; // If there is a border, the edge points may not fall within the key extents // and we may need to fetch a neighboring key. int n = 4; // index 4 is the center/default tile if (border > 0u && !contenderKey.getExtent().contains(x, y)) { int dTx = x < contenderKey.getExtent().xMin() ? -1 : x > contenderKey.getExtent().xMax() ? +1 : 0; int dTy = y < contenderKey.getExtent().yMin() ? +1 : x > contenderKey.getExtent().yMax() ? -1 : 0; contenderKey = contenderKey.createNeighborKey(dTx, dTy); n = (dTy+1)*3 + (dTx+1); } if ( offsetFailed[n][i] == true ) continue; GeoHeightField& layerHF = offsetFields[n][i]; if ( !layerHF.valid() ) { ElevationLayer* offset = offsets[i].first.get(); layerHF = offset->createHeightField(contenderKey, progress); if ( !layerHF.valid() ) { offsetFailed[n][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; } } } } // Return whether or not we actually read any real data return realData; }