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. 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; }