/** * Gets or creates the heightfields that would be effected by the given deformation */ void getOrCreateHeightFieldsForDeformation(const Deformation& deformation, unsigned int level, HeightFieldMap& results) { OpenThreads::ScopedLock< OpenThreads::Mutex > lk(_mutex); // Get the extent of the deformation feature. GeoExtent extent(deformation._feature->getSRS(), deformation._feature->getGeometry()->getBounds()); TileKey ll = getProfile()->createTileKey(extent.xMin(), extent.yMin(), level); TileKey ur = getProfile()->createTileKey(extent.xMax(), extent.yMax(), level); for (unsigned int c = ll.getTileX(); c <= ur.getTileX(); c++) { for (unsigned int r = ur.getTileY(); r <= ll.getTileY(); r++) { TileKey key(level, c, r, getProfile()); osg::ref_ptr< osg::HeightField > hf; HeightFieldMap::iterator itr = _heightfields.find( key ); if (itr == _heightfields.end()) { //Allocate a new heightfield hf = new osg::HeightField; hf->allocate(getPixelsPerTile(), getPixelsPerTile()); for (unsigned int i = 0; i < hf->getHeightList().size(); ++i) hf->getHeightList()[i] = NO_DATA_VALUE; _heightfields[ key ] = hf.get(); results[ key ] = hf.get(); } else { results[ key ] = itr->second.get(); } } } }
unsigned int CacheEstimator::getNumTiles() const { unsigned int total = 0; for (unsigned int level = _minLevel; level <= _maxLevel; level++) { if (_extents.empty()) { unsigned int wide, high; _profile->getNumTiles( level, wide, high ); total += (wide * high); } else { for (std::vector< GeoExtent >::const_iterator itr = _extents.begin(); itr != _extents.end(); ++itr) { const GeoExtent& extent = *itr; TileKey ll = _profile->createTileKey(extent.xMin(), extent.yMin(), level); TileKey ur = _profile->createTileKey(extent.xMax(), extent.yMax(), level); if (!ll.valid() || !ur.valid()) continue; int tilesWide = ur.getTileX() - ll.getTileX() + 1; int tilesHigh = ll.getTileY() - ur.getTileY() + 1; int tilesAtLevel = tilesWide * tilesHigh; total += tilesAtLevel; } } } return total; }
MPGeometry::MPGeometry(const TileKey& key, const MapFrame& frame, int imageUnit) : osg::Geometry ( ), _frame ( frame ), _imageUnit ( imageUnit ) { _supportsGLSL = Registry::capabilities().supportsGLSL(); unsigned tw, th; key.getProfile()->getNumTiles(key.getLOD(), tw, th); _tileKeyValue.set( key.getTileX(), th-key.getTileY()-1.0f, key.getLOD(), -1.0f ); _imageUnitParent = _imageUnit + 1; // temp // establish uniform name IDs. _tileKeyUniformNameID = osg::Uniform::getNameID( "oe_tile_key" ); _birthTimeUniformNameID = osg::Uniform::getNameID( "oe_tile_birthtime" ); _uidUniformNameID = osg::Uniform::getNameID( "oe_layer_uid" ); _orderUniformNameID = osg::Uniform::getNameID( "oe_layer_order" ); _opacityUniformNameID = osg::Uniform::getNameID( "oe_layer_opacity" ); _texMatParentUniformNameID = osg::Uniform::getNameID( "oe_layer_parent_matrix" ); // we will set these later (in TileModelCompiler) this->setUseVertexBufferObjects(false); this->setUseDisplayList(false); }
FeatureCursor* createFeatureCursor( const Symbology::Query& query ) { TileKey key = *query.tileKey(); int z = key.getLevelOfDetail(); int tileX = key.getTileX(); int tileY = key.getTileY(); unsigned int numRows, numCols; key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows); tileY = numRows - tileY - 1; //Get the image sqlite3_stmt* select = NULL; std::string queryStr = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?"; int rc = sqlite3_prepare_v2( _database, queryStr.c_str(), -1, &select, 0L ); if ( rc != SQLITE_OK ) { OE_WARN << LC << "Failed to prepare SQL: " << queryStr << "; " << sqlite3_errmsg(_database) << std::endl; return NULL; } bool valid = true; sqlite3_bind_int( select, 1, z ); sqlite3_bind_int( select, 2, tileX ); sqlite3_bind_int( select, 3, tileY ); rc = sqlite3_step( select ); FeatureList features; if ( rc == SQLITE_ROW) { // the pointer returned from _blob gets freed internally by sqlite, supposedly const char* data = (const char*)sqlite3_column_blob( select, 0 ); int dataLen = sqlite3_column_bytes( select, 0 ); std::string dataBuffer( data, dataLen ); std::stringstream in(dataBuffer); MVT::read(in, key, features); } else { OE_DEBUG << LC << "SQL QUERY failed for " << queryStr << ": " << std::endl; valid = false; } sqlite3_finalize( select ); // apply filters before returning. applyFilters( features ); if (!features.empty()) { //OE_NOTICE << "Returning " << features.size() << " features" << std::endl; return new FeatureListCursor(features); } return 0; }
void TMSBackFiller::process( const std::string& tms, osgDB::Options* options ) { std::string fullPath = getFullPath( "", tms ); _options = options; //Read the tilemap _tileMap = TileMapReaderWriter::read( fullPath, 0 ); if (_tileMap) { //The max level is where we are going to read data from, so we need to start one level up. osg::ref_ptr< const Profile> profile = _tileMap->createProfile(); //If the bounds aren't valid just use the full extent of the profile. if (!_bounds.valid()) { _bounds = profile->getExtent().bounds(); } int firstLevel = _maxLevel-1; GeoExtent extent( profile->getSRS(), _bounds ); //Process each level in it's entirety for (int level = firstLevel; level >= static_cast<int>(_minLevel); level--) { if (_verbose) OE_NOTICE << "Processing level " << level << std::endl; TileKey ll = profile->createTileKey(extent.xMin(), extent.yMin(), level); TileKey ur = profile->createTileKey(extent.xMax(), extent.yMax(), level); for (unsigned int x = ll.getTileX(); x <= ur.getTileX(); x++) { for (unsigned int y = ur.getTileY(); y <= ll.getTileY(); y++) { TileKey key = TileKey(level, x, y, profile.get()); processKey( key ); } } } } else { OE_NOTICE << "Failed to load TileMap from " << _tmsPath << std::endl; } }
MPGeometry::MPGeometry(const TileKey& key, const MapFrame& frame, int imageUnit) : osg::Geometry ( ), _frame ( frame ), _imageUnit ( imageUnit ), _uidUniformNameID(0), _birthTimeUniformNameID(0u), _orderUniformNameID(0u), _opacityUniformNameID(0u), _texMatParentUniformNameID(0u), _tileKeyUniformNameID(0u), _minRangeUniformNameID(0u), _maxRangeUniformNameID(0u), _imageUnitParent(0), _elevUnit(0), _supportsGLSL(false) { _supportsGLSL = Registry::capabilities().supportsGLSL(); // Encode the tile key in a uniform. Note! The X and Y components are presented // modulo 2^16 form so they don't overrun single-precision space. unsigned tw, th; key.getProfile()->getNumTiles(key.getLOD(), tw, th); const double m = pow(2.0, 16.0); double x = (double)key.getTileX(); double y = (double)(th - key.getTileY()-1); _tileKeyValue.set( (float)fmod(x, m), (float)fmod(y, m), (float)key.getLOD(), -1.0f); _imageUnitParent = _imageUnit + 1; // temp _elevUnit = _imageUnit + 2; // temp // establish uniform name IDs. _tileKeyUniformNameID = osg::Uniform::getNameID( "oe_tile_key" ); _birthTimeUniformNameID = osg::Uniform::getNameID( "oe_tile_birthtime" ); _uidUniformNameID = osg::Uniform::getNameID( "oe_layer_uid" ); _orderUniformNameID = osg::Uniform::getNameID( "oe_layer_order" ); _opacityUniformNameID = osg::Uniform::getNameID( "oe_layer_opacity" ); _texMatParentUniformNameID = osg::Uniform::getNameID( "oe_layer_parent_texmat" ); _minRangeUniformNameID = osg::Uniform::getNameID( "oe_layer_minRange" ); _maxRangeUniformNameID = osg::Uniform::getNameID( "oe_layer_maxRange" ); // we will set these later (in TileModelCompiler) this->setUseDisplayList(false); this->setUseVertexBufferObjects(true); }
MPGeometry::MPGeometry(const TileKey& key, const MapFrame& frame, int imageUnit) : osg::Geometry ( ), _frame ( frame ), _imageUnit ( imageUnit ) { unsigned tw, th; key.getProfile()->getNumTiles(key.getLOD(), tw, th); _tileKeyValue.set( key.getTileX(), th-key.getTileY()-1.0f, key.getLOD(), -1.0f ); _imageUnitParent = _imageUnit + 1; // temp // establish uniform name IDs. _tileKeyUniformNameID = osg::Uniform::getNameID( "oe_tile_key" ); _uidUniformNameID = osg::Uniform::getNameID( "oe_layer_uid" ); _orderUniformNameID = osg::Uniform::getNameID( "oe_layer_order" ); _opacityUniformNameID = osg::Uniform::getNameID( "oe_layer_opacity" ); _texMatParentUniformNameID = osg::Uniform::getNameID( "oe_layer_parent_matrix" ); // Temporary solution to the OverlayDecorator techniques' inappropriate setting of // uniform values during the CULL traversal, which causes corruption of the RTT // camera matricies when DRAW overlaps the next frame's CULL. Please see my comments // in DrapingTechnique.cpp for more information. this->setDataVariance( osg::Object::DYNAMIC ); }
int UnifiedCubeProfile::getFace( const TileKey& key ) { return key.getTileX() >> key.getLevelOfDetail(); }
bool MBTilesTileSource::storeImage(const TileKey& key, osg::Image* image, ProgressCallback* progress) { if ( (getMode() & MODE_WRITE) == 0 ) return false; Threading::ScopedMutexLock exclusiveLock(_mutex); // encode the data stream: std::stringstream buf; osgDB::ReaderWriter::WriteResult wr; if ( _forceRGB && ImageUtils::hasAlphaChannel(image) ) { osg::ref_ptr<osg::Image> rgb = ImageUtils::convertToRGB8(image); wr = _rw->writeImage(*(rgb.get()), buf, _dbOptions.get()); } else { wr = _rw->writeImage(*image, buf, _dbOptions.get()); } if ( wr.error() ) { OE_WARN << LC << "Image encoding failed: " << wr.message() << std::endl; return false; } std::string value = buf.str(); // compress if necessary: if ( _compressor.valid() ) { std::ostringstream output; if ( !_compressor->compress(output, value) ) { OE_WARN << LC << "Compressor failed" << std::endl; return false; } value = output.str(); } int z = key.getLOD(); int x = key.getTileX(); int y = key.getTileY(); // flip Y axis unsigned int numRows, numCols; key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows); y = numRows - y - 1; // Prep the insert statement: sqlite3_stmt* insert = NULL; std::string query = "INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)"; int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &insert, 0L ); if ( rc != SQLITE_OK ) { OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl; return false; } // bind parameters: sqlite3_bind_int( insert, 1, z ); sqlite3_bind_int( insert, 2, x ); sqlite3_bind_int( insert, 3, y ); // bind the data blob: sqlite3_bind_blob( insert, 4, value.c_str(), value.length(), SQLITE_STATIC ); // run the sql. bool ok = true; int tries = 0; do { rc = sqlite3_step(insert); } while (++tries < 100 && (rc == SQLITE_BUSY || rc == SQLITE_LOCKED)); if (SQLITE_OK != rc && SQLITE_DONE != rc) { #if SQLITE_VERSION_NUMBER >= 3007015 OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << sqlite3_errstr(rc) << "; " << sqlite3_errmsg(_database) << std::endl; #else OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << rc << "; " << sqlite3_errmsg(_database) << std::endl; #endif ok = false; } sqlite3_finalize( insert ); return ok; }
FeatureCursor* createFeatureCursor( const Symbology::Query& query ) { TileKey key = *query.tileKey(); int z = key.getLevelOfDetail(); int tileX = key.getTileX(); int tileY = key.getTileY(); unsigned int numRows, numCols; key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows); tileY = numRows - tileY - 1; //Get the image sqlite3_stmt* select = NULL; std::string queryStr = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?"; int rc = sqlite3_prepare_v2( _database, queryStr.c_str(), -1, &select, 0L ); if ( rc != SQLITE_OK ) { OE_WARN << LC << "Failed to prepare SQL: " << queryStr << "; " << sqlite3_errmsg(_database) << std::endl; return NULL; } bool valid = true; sqlite3_bind_int( select, 1, z ); sqlite3_bind_int( select, 2, tileX ); sqlite3_bind_int( select, 3, tileY ); rc = sqlite3_step( select ); FeatureList features; if ( rc == SQLITE_ROW) { // the pointer returned from _blob gets freed internally by sqlite, supposedly const char* data = (const char*)sqlite3_column_blob( select, 0 ); int dataLen = sqlite3_column_bytes( select, 0 ); std::string dataBuffer( data, dataLen ); // decompress if necessary: if ( _compressor.valid() ) { std::istringstream inputStream(dataBuffer); std::string value; if ( !_compressor->decompress(inputStream, value) ) { OE_WARN << LC << "Decompression failed" << std::endl; valid = false; } else { dataBuffer = value; } } mapnik::vector::tile tile; if (tile.ParseFromString(dataBuffer)) { // Get the layer in question for (unsigned int i = 0; i < tile.layers().size(); i++) { const mapnik::vector::tile_layer &layer = tile.layers().Get(i); //OE_NOTICE << layer.name() << std::endl; //if (layer.name() != "road") continue; //if (layer.name() != "building") continue; for (unsigned int j = 0; j < layer.features().size(); j++) { const mapnik::vector::tile_feature &feature = layer.features().Get(j); osg::ref_ptr< osgEarth::Symbology::Geometry > geometry; eGeomType geomType = static_cast<eGeomType>(feature.type()); if (geomType == ::Polygon) { //OE_NOTICE << "Polygon " << std::endl; geometry = new osgEarth::Symbology::Polygon(); } else if (geomType == ::LineString) { //OE_NOTICE << "LineString" << std::endl; geometry = new osgEarth::Symbology::LineString(); } else if (geomType == ::Point) { //OE_NOTICE << "Point" << std::endl; geometry = new osgEarth::Symbology::PointSet(); } else { //OE_NOTICE << "uknown" << std::endl; geometry = new osgEarth::Symbology::LineString(); } osg::ref_ptr< Feature > oeFeature = new Feature(geometry, key.getProfile()->getSRS()); features.push_back(oeFeature.get()); // Read attributes for (unsigned int k = 0; k < feature.tags().size(); k+=2) { std::string key = layer.keys().Get(feature.tags().Get(k)); mapnik::vector::tile_value value = layer.values().Get(feature.tags().Get(k+1)); if (value.has_bool_value()) { oeFeature->set(key, value.bool_value()); } else if (value.has_double_value()) { oeFeature->set(key, value.double_value()); } else if (value.has_float_value()) { oeFeature->set(key, value.float_value()); } else if (value.has_int_value()) { oeFeature->set(key, (int)value.int_value()); } else if (value.has_sint_value()) { oeFeature->set(key, (int)value.sint_value()); } else if (value.has_string_value()) { oeFeature->set(key, value.string_value()); } else if (value.has_uint_value()) { oeFeature->set(key, (int)value.uint_value()); } // Special path for getting heights from our test dataset. if (key == "other_tags") { std::string other_tags = value.string_value(); StringTokenizer tok("=>"); StringVector tized; tok.tokenize(other_tags, tized); if (tized.size() == 3) { if (tized[0] == "height") { std::string value = tized[2]; // Remove quotes from the height float height = as<float>(value, FLT_MAX); if (height != FLT_MAX) { oeFeature->set("height", height); } } } } } unsigned int length = 0; int cmd = -1; const int cmd_bits = 3; unsigned int tileres = layer.extent(); int x = 0; int y = 0; for (int k = 0; k < feature.geometry_size();) { if (!length) { unsigned int cmd_length = feature.geometry(k++); cmd = cmd_length & ((1 << cmd_bits) - 1); length = cmd_length >> cmd_bits; } if (length > 0) { length--; if (cmd == SEG_MOVETO || cmd == SEG_LINETO) { int px = feature.geometry(k++); int py = feature.geometry(k++); px = zig_zag_decode(px); py = zig_zag_decode(py); x += px; y += py; double width = key.getExtent().width(); double height = key.getExtent().height(); double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x; double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y; geometry->push_back(geoX, geoY, 0); } else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1))) { geometry->push_back(geometry->front()); } } } if (geometry->getType() == Geometry::TYPE_POLYGON) { geometry->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW); } } } } else {
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; }
void CacheSeed::seed( Map* map ) { if ( !map->getCache() ) { OE_WARN << LC << "Warning: No cache defined; aborting." << std::endl; return; } std::vector<TileKey> keys; map->getProfile()->getRootKeys(keys); //Add the map's entire extent if we don't have one specified. if (_extents.empty()) { addExtent( map->getProfile()->getExtent() ); } bool hasCaches = false; int src_min_level = INT_MAX; unsigned int src_max_level = 0; MapFrame mapf( map, Map::TERRAIN_LAYERS, "CacheSeed::seed" ); //Assumes the the TileSource will perform the caching for us when we call createImage for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); i++ ) { ImageLayer* layer = i->get(); TileSource* src = layer->getTileSource(); const ImageLayerOptions& opt = layer->getImageLayerOptions(); if ( layer->isCacheOnly() ) { OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" is set to cache-only; skipping." << std::endl; } else if ( !src ) { OE_WARN << "Warning: Layer \"" << layer->getName() << "\" could not create TileSource; skipping." << std::endl; } //else if ( src->getCachePolicyHint(0L) == CachePolicy::NO_CACHE ) //{ // OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl; //} else if ( !layer->getCache() ) { OE_WARN << LC << "Notice: Layer \"" << layer->getName() << "\" has no cache defined; skipping." << std::endl; } else { hasCaches = true; if (opt.minLevel().isSet() && (int)opt.minLevel().get() < src_min_level) src_min_level = opt.minLevel().get(); if (opt.maxLevel().isSet() && opt.maxLevel().get() > src_max_level) src_max_level = opt.maxLevel().get(); } } for( ElevationLayerVector::const_iterator i = mapf.elevationLayers().begin(); i != mapf.elevationLayers().end(); i++ ) { ElevationLayer* layer = i->get(); TileSource* src = layer->getTileSource(); const ElevationLayerOptions& opt = layer->getElevationLayerOptions(); if ( layer->isCacheOnly() ) { OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" is set to cache-only; skipping." << std::endl; } else if (!src) { OE_WARN << "Warning: Layer \"" << layer->getName() << "\" could not create TileSource; skipping." << std::endl; } //else if ( src->getCachePolicyHint(0L) == CachePolicy::NO_CACHE ) //{ // OE_WARN << LC << "Warning: Layer \"" << layer->getName() << "\" does not support seeding; skipping." << std::endl; //} else if ( !layer->getCache() ) { OE_WARN << LC << "Notice: Layer \"" << layer->getName() << "\" has no cache defined; skipping." << std::endl; } else { hasCaches = true; if (opt.minLevel().isSet() && (int)opt.minLevel().get() < src_min_level) src_min_level = opt.minLevel().get(); if (opt.maxLevel().isSet() && opt.maxLevel().get() > src_max_level) src_max_level = opt.maxLevel().get(); } } if ( !hasCaches ) { OE_WARN << LC << "There are either no caches defined in the map, or no sources to cache; aborting." << std::endl; return; } if ( src_max_level > 0 && src_max_level < _maxLevel ) { _maxLevel = src_max_level; } OE_NOTICE << LC << "Maximum cache level will be " << _maxLevel << std::endl; osg::Timer_t startTime = osg::Timer::instance()->tick(); //Estimate the number of tiles _total = 0; for (unsigned int level = _minLevel; level <= _maxLevel; level++) { double coverageRatio = 0.0; if (_extents.empty()) { unsigned int wide, high; map->getProfile()->getNumTiles( level, wide, high ); _total += (wide * high); } else { for (std::vector< GeoExtent >::const_iterator itr = _extents.begin(); itr != _extents.end(); itr++) { const GeoExtent& extent = *itr; double boundsArea = extent.area(); TileKey ll = map->getProfile()->createTileKey(extent.xMin(), extent.yMin(), level); TileKey ur = map->getProfile()->createTileKey(extent.xMax(), extent.yMax(), level); if (!ll.valid() || !ur.valid()) continue; int tilesWide = ur.getTileX() - ll.getTileX() + 1; int tilesHigh = ll.getTileY() - ur.getTileY() + 1; int tilesAtLevel = tilesWide * tilesHigh; //OE_NOTICE << "Tiles at level " << level << "=" << tilesAtLevel << std::endl; /* bool hasData = false; for (ImageLayerVector::const_iterator itr = mapf.imageLayers().begin(); itr != mapf.imageLayers().end(); itr++) { TileSource* src = itr->get()->getTileSource(); if (src) { if (src->hasDataAtLOD( level )) { //Compute the percent coverage of this dataset on the current extent if (src->getDataExtents().size() > 0) { double cov = 0.0; for (unsigned int j = 0; j < src->getDataExtents().size(); j++) { GeoExtent b = src->getDataExtents()[j].transform( extent.getSRS()); GeoExtent intersection = b.intersectionSameSRS( extent ); if (intersection.isValid()) { double coverage = intersection.area() / boundsArea; cov += coverage; //Assumes the extents aren't overlapping } } if (coverageRatio < cov) coverageRatio = cov; } else { //We have no way of knowing how much coverage we have coverageRatio = 1.0; } hasData = true; break; } } } for (ElevationLayerVector::const_iterator itr = mapf.elevationLayers().begin(); itr != mapf.elevationLayers().end(); itr++) { TileSource* src = itr->get()->getTileSource(); if (src) { if (src->hasDataAtLOD( level )) { //Compute the percent coverage of this dataset on the current extent if (src->getDataExtents().size() > 0) { double cov = 0.0; for (unsigned int j = 0; j < src->getDataExtents().size(); j++) { GeoExtent b = src->getDataExtents()[j].transform( extent.getSRS()); GeoExtent intersection = b.intersectionSameSRS( extent ); if (intersection.isValid()) { double coverage = intersection.area() / boundsArea; cov += coverage; //Assumes the extents aren't overlapping } } if (coverageRatio < cov) coverageRatio = cov; } else { //We have no way of knowing how much coverage we have coverageRatio = 1.0; } hasData = true; break; } } } //Adjust the coverage ratio by a fudge factor to try to keep it from being too small, //tiles are either processed or not and the ratio is exact so will cover tiles partially //and potentially be too small double adjust = 4.0; coverageRatio = osg::clampBetween(coverageRatio * adjust, 0.0, 1.0); //OE_NOTICE << level << " CoverageRatio = " << coverageRatio << std::endl; if (hasData) { _total += (int)ceil(coverageRatio * (double)tilesAtLevel ); } */ _total += tilesAtLevel; } } } osg::Timer_t endTime = osg::Timer::instance()->tick(); //OE_NOTICE << "Counted tiles in " << osg::Timer::instance()->delta_s(startTime, endTime) << " s" << std::endl; OE_INFO << "Processing ~" << _total << " tiles" << std::endl; for (unsigned int i = 0; i < keys.size(); ++i) { processKey( mapf, keys[i] ); } _total = _completed; if ( _progress.valid()) _progress->reportProgress(_completed, _total, 0, 1, "Finished"); }
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(); }
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 { //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; }