bool TerrainLayer::isKeyValid(const TileKey& key) const { if (!key.valid()) return false; // Check to see if an explicity max LOD is set. Do NOT compare against the minLevel, // because we still need to create empty tiles until we get to the data. The ImageLayer // will deal with this. if ( _runtimeOptions->maxLevel().isSet() && key.getLOD() > _runtimeOptions->maxLevel().value() ) { return false; } // Check to see if levels of detail based on resolution are set const Profile* profile = getProfile(); if ( profile ) { if ( !profile->isEquivalentTo( key.getProfile() ) ) { OE_DEBUG << LC << "TerrainLayer::isKeyValid called with key of a different profile" << std::endl; //return true; } if ( _runtimeOptions->maxResolution().isSet() ) { double keyres = key.getExtent().width() / (double)getTileSize(); double keyresInLayerProfile = key.getProfile()->getSRS()->transformUnits(keyres, profile->getSRS()); if ( _runtimeOptions->maxResolution().isSet() && keyresInLayerProfile < _runtimeOptions->maxResolution().value() ) { return false; } } } return true; }
void Profile::getIntersectingTiles(const TileKey& key, std::vector<TileKey>& out_intersectingKeys) const { OE_DEBUG << "GET ISECTING TILES for key " << key.str() << " -----------------" << std::endl; //If the profiles are exactly equal, just add the given tile key. if ( isHorizEquivalentTo( key.getProfile() ) ) { //Clear the incoming list out_intersectingKeys.clear(); out_intersectingKeys.push_back(key); } else { // figure out which LOD in the local profile is a best match for the LOD // in the source LOD in terms of resolution. unsigned localLOD = getEquivalentLOD(key.getProfile(), key.getLOD()); getIntersectingTiles(key.getExtent(), localLOD, out_intersectingKeys); OE_DEBUG << LC << "GIT, key="<< key.str() << ", localLOD=" << localLOD << ", resulted in " << out_intersectingKeys.size() << " tiles" << std::endl; } }
void TileModelFactory::buildElevation(const TileKey& key, const MapFrame& frame, bool accumulate, bool buildTexture, TileModel* model, ProgressCallback* progress) { const MapInfo& mapInfo = frame.getMapInfo(); const osgEarth::ElevationInterpolation& interp = frame.getMapOptions().elevationInterpolation().get(); // Request a heightfield from the map, falling back on lower resolution tiles // if necessary (fallback=true) osg::ref_ptr<osg::HeightField> hf; bool isFallback = false; // look up the parent's heightfield to use as a template osg::ref_ptr<osg::HeightField> parentHF; TileKey parentKey = key.createParentKey(); if ( accumulate ) { osg::ref_ptr<TileNode> parentNode; if (_liveTiles->get(parentKey, parentNode)) { parentHF = parentNode->getTileModel()->_elevationData.getHeightField(); if ( _debug && key.getLOD() > 0 && !parentHF.valid() ) { OE_NOTICE << LC << "Could not find a parent tile HF for " << key.str() << "\n"; } } } // Make a new heightfield: if (_meshHFCache->getOrCreateHeightField(frame, key, parentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress)) { model->_elevationData = TileModel::ElevationData( hf, GeoLocator::createForKey( key, mapInfo ), isFallback ); // Edge normalization: requires adjacency information if ( _terrainOptions.normalizeEdges() == true ) { for( int x=-1; x<=1; x++ ) { for( int y=-1; y<=1; y++ ) { if ( x != 0 || y != 0 ) { TileKey neighborKey = key.createNeighborKey(x, y); if ( neighborKey.valid() ) { osg::ref_ptr<osg::HeightField> neighborParentHF; if ( accumulate ) { TileKey neighborParentKey = neighborKey.createParentKey(); if (neighborParentKey == parentKey) { neighborParentHF = parentHF; } else { osg::ref_ptr<TileNode> neighborParentNode; if (_liveTiles->get(neighborParentKey, neighborParentNode)) { neighborParentHF = neighborParentNode->getTileModel()->_elevationData.getHeightField(); } } } // only pull the tile if we have a valid parent HF for it -- otherwise // you might get a flat tile when upsampling data. if ( neighborParentHF.valid() ) { osg::ref_ptr<osg::HeightField> hf; if (_meshHFCache->getOrCreateHeightField(frame, neighborKey, neighborParentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress) ) { model->_elevationData.setNeighbor( x, y, hf.get() ); } } } } } } // parent too. if ( parentHF.valid() ) { model->_elevationData.setParent( parentHF.get() ); } } if ( buildTexture ) { model->generateElevationTexture(); } } }
void TileModelFactory::buildNormalMap(const TileKey& key, const MapFrame& frame, bool accumulate, TileModel* model, ProgressCallback* progress) { const MapInfo& mapInfo = frame.getMapInfo(); const osgEarth::ElevationInterpolation& interp = frame.getMapOptions().elevationInterpolation().get(); // Request a heightfield from the map, falling back on lower resolution tiles // if necessary (fallback=true) osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<osg::HeightField> parentHF; osg::ref_ptr<const TileModel> parentModel; bool isFallback = false; unsigned minNormalLOD = _terrainOptions.minNormalMapLOD().isSet() ? _terrainOptions.minNormalMapLOD().get() : 0u; if ( key.getLOD() >= minNormalLOD ) { // look up the parent's heightfield to use as a template TileKey parentKey = key.createParentKey(); if ( accumulate ) { osg::ref_ptr<TileNode> parentNode; if (_liveTiles->get(parentKey, parentNode)) { parentModel = parentNode->getTileModel(); parentHF = parentModel->_normalData.getHeightField(); if ( parentHF->getNumColumns() == EMPTY_NORMAL_MAP_SIZE ) parentHF = 0L; } } // Make a new heightfield: if (_normalHFCache->getOrCreateHeightField(frame, key, parentHF.get(), hf, isFallback, SAMPLE_FIRST_VALID, interp, progress)) { if ( isFallback && parentModel.valid() ) { model->_normalData = parentModel->_normalData; model->_normalData._fallbackData = true; } else { model->_normalData = TileModel::NormalData( hf, GeoLocator::createForKey( key, mapInfo ), isFallback ); } } } else { // empty HF must be at least 2x2 for normal texture gen to work hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), EMPTY_NORMAL_MAP_SIZE, EMPTY_NORMAL_MAP_SIZE, true ); model->_normalData = TileModel::NormalData( hf, GeoLocator::createForKey( key, mapInfo ), false ); } if ( isFallback && parentModel.valid() ) { model->_normalTexture = parentModel->_normalTexture.get(); } else { model->generateNormalTexture(); } }
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; }
bool HeightFieldCache::getOrCreateHeightField(const MapFrame& frame, const TileKey& key, //bool cummulative, const osg::HeightField* parent_hf, osg::ref_ptr<osg::HeightField>& out_hf, bool& out_isFallback, ElevationSamplePolicy samplePolicy, ElevationInterpolation interp, ProgressCallback* progress ) { // default out_isFallback = false; // check the quick cache. HFKey cachekey; cachekey._key = key; cachekey._revision = frame.getRevision(); cachekey._samplePolicy = samplePolicy; if (progress) progress->stats()["hfcache_try_count"] += 1; bool hit = false; LRUCache<HFKey,HFValue>::Record rec; if ( _cache.get(cachekey, rec) ) { out_hf = rec.value()._hf.get(); out_isFallback = rec.value()._isFallback; if (progress) { progress->stats()["hfcache_hit_count"] += 1; progress->stats()["hfcache_hit_rate"] = progress->stats()["hfcache_hit_count"]/progress->stats()["hfcache_try_count"]; } return true; } // Find the parent tile and start with its heightfield. if ( parent_hf ) { TileKey parentKey = key.createParentKey(); out_hf = HeightFieldUtils::createSubSample( parent_hf, parentKey.getExtent(), key.getExtent(), interp ); if ( !out_hf.valid() && ((int)key.getLOD())-1 > _firstLOD ) { // This most likely means that a parent tile expired while we were building the child. // No harm done in that case as this tile will soo be discarded as well. OE_DEBUG << "MP HFC: Unable to find tile " << key.str() << " in the live tile registry" << std::endl; return false; } } if ( !out_hf.valid() ) { //TODO. // This sets the elevation tile size; query size for all tiles. out_hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), _tileSize, _tileSize, true ); } bool populated = frame.populateHeightField( out_hf, key, true, // convertToHAE samplePolicy, progress ); // Treat Plate Carre specially by scaling the height values. (There is no need // to do this with an empty heightfield) const MapInfo& mapInfo = frame.getMapInfo(); if ( mapInfo.isPlateCarre() ) { HeightFieldUtils::scaleHeightFieldToDegrees( out_hf.get() ); } // cache it. HFValue cacheval; cacheval._hf = out_hf.get(); cacheval._isFallback = !populated; _cache.insert( cachekey, cacheval ); out_isFallback = !populated; return true; }
void ElevationLayer::assembleHeightField(const TileKey& key, osg::ref_ptr<osg::HeightField>& out_hf, osg::ref_ptr<NormalMap>& out_normalMap, ProgressCallback* progress) { // Collect the heightfields for each of the intersecting tiles. GeoHeightFieldVector heightFields; //Determine the intersecting keys std::vector< TileKey > intersectingTiles; if (key.getLOD() > 0u) { getProfile()->getIntersectingTiles(key, intersectingTiles); } else { // LOD is zero - check whether the LOD mapping went out of range, and if so, // fall back until we get valid tiles. This can happen when you have two // profiles with very different tile schemes, and the "equivalent LOD" // surpasses the max data LOD of the tile source. unsigned numTilesThatMayHaveData = 0u; int intersectionLOD = getProfile()->getEquivalentLOD(key.getProfile(), key.getLOD()); while (numTilesThatMayHaveData == 0u && intersectionLOD >= 0) { intersectingTiles.clear(); getProfile()->getIntersectingTiles(key.getExtent(), intersectionLOD, intersectingTiles); for (unsigned int i = 0; i < intersectingTiles.size(); ++i) { const TileKey& layerKey = intersectingTiles[i]; if (mayHaveData(layerKey) == true) { ++numTilesThatMayHaveData; } } --intersectionLOD; } } // collect heightfield for each intersecting key. Note, we're hitting the // underlying tile source here, so there's no vetical datum shifts happening yet. // we will do that later. if ( intersectingTiles.size() > 0 ) { for (unsigned int i = 0; i < intersectingTiles.size(); ++i) { const TileKey& layerKey = intersectingTiles[i]; if ( isKeyInLegalRange(layerKey) ) { osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<NormalMap> normalMap; createImplementation(layerKey, hf, normalMap, progress); if (hf.valid()) { heightFields.push_back( GeoHeightField(hf.get(), normalMap.get(), layerKey.getExtent()) ); } } } // If we actually got a HeightField, resample/reproject it to match the incoming TileKey's extents. if (heightFields.size() > 0) { unsigned int width = 0; unsigned int height = 0; for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { if (itr->getHeightField()->getNumColumns() > width) width = itr->getHeightField()->getNumColumns(); if (itr->getHeightField()->getNumRows() > height) height = itr->getHeightField()->getNumRows(); } //Now sort the heightfields by resolution to make sure we're sampling the highest resolution one first. std::sort( heightFields.begin(), heightFields.end(), GeoHeightField::SortByResolutionFunctor()); out_hf = new osg::HeightField(); out_hf->allocate(width, height); out_normalMap = new NormalMap(width, height); //Go ahead and set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); double dx = (maxx - minx)/(double)(width-1); double dy = (maxy - miny)/(double)(height-1); //Create the new heightfield by sampling all of them. for (unsigned int c = 0; c < width; ++c) { double x = minx + (dx * (double)c); for (unsigned r = 0; r < height; ++r) { double y = miny + (dy * (double)r); //For each sample point, try each heightfield. The first one with a valid elevation wins. float elevation = NO_DATA_VALUE; osg::Vec3 normal(0,0,1); for (GeoHeightFieldVector::iterator itr = heightFields.begin(); itr != heightFields.end(); ++itr) { // get the elevation value, at the same time transforming it vertically into the // requesting key's vertical datum. float e = 0.0; osg::Vec3 n; if (itr->getElevationAndNormal(key.getExtent().getSRS(), x, y, INTERP_BILINEAR, key.getExtent().getSRS(), e, n)) { elevation = e; normal = n; break; } } out_hf->setHeight( c, r, elevation ); out_normalMap->set( c, r, normal ); } } } else { //if (progress && progress->message().empty()) // progress->message() = "assemble yielded no heightfields"; } } else { //if (progress && progress->message().empty()) // progress->message() = "assemble yielded no intersecting tiles"; } // If the progress was cancelled clear out any of the output data. if (progress && progress->isCanceled()) { out_hf = 0; out_normalMap = 0; } }
bool ElevationLayerVector::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 TerrainTileModelFactory::addElevation(TerrainTileModel* model, const Map* map, const TileKey& key, const CreateTileModelFilter& filter, unsigned border, ProgressCallback* progress) { // make an elevation layer. OE_START_TIMER(fetch_elevation); if (!filter.empty() && !filter.elevation().isSetTo(true)) return; const osgEarth::ElevationInterpolation& interp = map->getMapOptions().elevationInterpolation().get(); // Request a heightfield from the map. osg::ref_ptr<osg::HeightField> mainHF; osg::ref_ptr<NormalMap> normalMap; bool hfOK = getOrCreateHeightField(map, key, SAMPLE_FIRST_VALID, interp, border, mainHF, normalMap, progress) && mainHF.valid(); if (hfOK == false && key.getLOD() == _options.firstLOD().get()) { OE_DEBUG << LC << "No HF at key " << key.str() << ", making placeholder" << std::endl; mainHF = new osg::HeightField(); mainHF->allocate(1, 1); mainHF->setHeight(0, 0, 0.0f); hfOK = true; } if (hfOK && mainHF.valid()) { osg::ref_ptr<TerrainTileElevationModel> layerModel = new TerrainTileElevationModel(); layerModel->setHeightField( mainHF.get() ); // pre-calculate the min/max heights: for( unsigned col = 0; col < mainHF->getNumColumns(); ++col ) { for( unsigned row = 0; row < mainHF->getNumRows(); ++row ) { float h = mainHF->getHeight(col, row); if ( h > layerModel->getMaxHeight() ) layerModel->setMaxHeight( h ); if ( h < layerModel->getMinHeight() ) layerModel->setMinHeight( h ); } } // needed for normal map generation model->heightFields().setNeighbor(0, 0, mainHF.get()); // convert the heightfield to a 1-channel 32-bit fp image: ImageToHeightFieldConverter conv; osg::Image* hfImage = conv.convertToR32F(mainHF.get()); if ( hfImage ) { // Made an image, so store this as a texture with no matrix. osg::Texture* texture = createElevationTexture( hfImage ); layerModel->setTexture( texture ); model->elevationModel() = layerModel.get(); } if (normalMap.valid()) { TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel(); layerModel->setName( "oe_normal_map" ); // Made an image, so store this as a texture with no matrix. osg::Texture* texture = createNormalTexture(normalMap.get()); layerModel->setTexture( texture ); model->normalModel() = layerModel; } } if (progress) progress->stats()["fetch_elevation_time"] += OE_STOP_TIMER(fetch_elevation); }
void TerrainTileModelFactory::addColorLayers(TerrainTileModel* model, const Map* map, const TerrainEngineRequirements* reqs, const TileKey& key, const CreateTileModelFilter& filter, ProgressCallback* progress) { OE_START_TIMER(fetch_image_layers); int order = 0; LayerVector layers; map->getLayers(layers); for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i) { Layer* layer = i->get(); if (layer->getRenderType() != layer->RENDERTYPE_TERRAIN_SURFACE) continue; if (!layer->getEnabled()) continue; if (!filter.accept(layer)) continue; ImageLayer* imageLayer = dynamic_cast<ImageLayer*>(layer); if (imageLayer) { osg::Texture* tex = 0L; osg::Matrixf textureMatrix; if (imageLayer->isKeyInLegalRange(key) && imageLayer->mayHaveDataInExtent(key.getExtent())) { if (imageLayer->createTextureSupported()) { tex = imageLayer->createTexture( key, progress, textureMatrix ); } else { GeoImage geoImage = imageLayer->createImage( key, progress ); if ( geoImage.valid() ) { if ( imageLayer->isCoverage() ) tex = createCoverageTexture(geoImage.getImage(), imageLayer); else tex = createImageTexture(geoImage.getImage(), imageLayer); } } } // if this is the first LOD, and the engine requires that the first LOD // be populated, make an empty texture if we didn't get one. if (tex == 0L && _options.firstLOD() == key.getLOD() && reqs && reqs->fullDataAtFirstLodRequired()) { tex = _emptyTexture.get(); } if (tex) { tex->setName(model->getKey().str()); TerrainTileImageLayerModel* layerModel = new TerrainTileImageLayerModel(); layerModel->setImageLayer(imageLayer); layerModel->setTexture(tex); layerModel->setMatrix(new osg::RefMatrixf(textureMatrix)); model->colorLayers().push_back(layerModel); if (imageLayer->isShared()) { model->sharedLayers().push_back(layerModel); } if (imageLayer->isDynamic()) { model->setRequiresUpdateTraverse(true); } } } else // non-image kind of TILE layer: { TerrainTileColorLayerModel* colorModel = new TerrainTileColorLayerModel(); colorModel->setLayer(layer); model->colorLayers().push_back(colorModel); } } if (progress) progress->stats()["fetch_imagery_time"] += OE_STOP_TIMER(fetch_image_layers); }
GeoImage ImageLayer::createImageInKeyProfile( const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback ) { GeoImage result; out_isFallback = false; // If the layer is disabled, bail out. if ( !getEnabled() ) { return GeoImage::INVALID; } // Check the max data level, which limits the LOD of available data. if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() ) { return GeoImage::INVALID; } // Check for a "Minumum level" setting on this layer. If we are before the // min level, just return the empty image. Do not cache empties if ( _runtimeOptions.minLevel().isSet() && key.getLOD() < _runtimeOptions.minLevel().value() ) { return GeoImage( _emptyImage.get(), key.getExtent() ); } // Check for a "Minimum resolution" setting on the layer. If we are before the // min resolution, return the empty image. Do not cache empties. if ( _runtimeOptions.minResolution().isSet() ) { double keyres = key.getExtent().width() / getTileSize(); double keyresInLayerProfile = key.getProfile()->getSRS()->transformUnits(keyres, getProfile()->getSRS()); if ( keyresInLayerProfile > _runtimeOptions.minResolution().value() ) { return GeoImage( _emptyImage.get(), key.getExtent() ); } } OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // locate the cache bin for the target profile for this layer: CacheBin* cacheBin = getCacheBin( key.getProfile() ); // validate that we have either a valid tile source, or we're cache-only. if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) ) { OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create image " << std::endl; _runtimeOptions.enabled() = false; return GeoImage::INVALID; } // validate the existance of a valid layer profile (unless we're in cache-only mode, in which // case there is no layer profile) if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoImage::INVALID; } // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readImage( key.str(), getCachePolicy().getMinAcceptTime() ); if ( r.succeeded() ) { ImageUtils::normalizeImage( r.getImage() ); return GeoImage( r.releaseImage(), key.getExtent() ); } //else if ( r.code() == ReadResult::RESULT_EXPIRED ) //{ // OE_INFO << LC << getName() << " : " << key.str() << " record expired!" << std::endl; //} } // The data was not in the cache. If we are cache-only, fail sliently if ( isCacheOnly() ) { return GeoImage::INVALID; } // Get an image from the underlying TileSource. result = createImageFromTileSource( key, progress, forceFallback, out_isFallback ); // Normalize the image if necessary if ( result.valid() ) { ImageUtils::normalizeImage( result.getImage() ); } // If we got a result, the cache is valid and we are caching in the map profile, write to the map cache. if (result.valid() && //JB: Removed the check to not write out fallback data. If you have a low resolution base dataset (max lod 3) and a high resolution insert (max lod 22) // then the low res data needs to "fallback" from LOD 4 - 22 so you can display the high res inset. If you don't cache these intermediate tiles then // performance can suffer generating all those fallback tiles, especially if you have to do reprojection or mosaicing. //!out_isFallback && cacheBin && getCachePolicy().isCacheWriteable() ) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write( key.str(), result.getImage() ); //OE_INFO << LC << "WRITING " << key.str() << " to the cache." << std::endl; } if ( result.valid() ) { OE_DEBUG << LC << key.str() << " result OK" << std::endl; } else { OE_DEBUG << LC << key.str() << "result INVALID" << std::endl; } return result; }
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; }
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; }
void ModelSplatter::operator()(const TileKey& key, osg::Node* node) { TerrainTileNode* tile = osgEarth::findTopMostNodeOfType<TerrainTileNode>(node); if ( !tile ) return; if ( key.getLOD() >= _minLOD && _model.valid() ) { // make sure the correct model is loaded establish(); // elevation texture and matrix are required osg::Texture* elevationTex = tile->getElevationTexture(); if ( !elevationTex ) { //OE_WARN << LC << "No elevation texture for key " << key.str() << "\n"; return; } osg::RefMatrix* elevationTexMat = tile->getElevationTextureMatrix(); if ( !elevationTexMat ) { //OE_WARN << LC << "No elevation texture matrix for key " << key.str() << "\n"; return; } tile->addChild( _model.get() ); osg::StateSet* ss = tile->getOrCreateStateSet(); // first, a rotation vector to make trees point up. GeoPoint p; key.getExtent().getCentroid(p); osg::Vec3d up; p.createWorldUpVector(up); osg::Quat q; q.makeRotate(osg::Vec3d(0,0,1), up); osg::Matrixd zup = osg::Matrixd::rotate(q); // matrices to resolve the weird terrain localization into a usable LTP. osg::Matrix tile2world = tile->getMatrix(); osg::Matrix world2ltp; p.createWorldToLocal(world2ltp); osg::Matrix local2ltp = tile2world * world2ltp; osg::Matrix ltp2local; ltp2local.invert(local2ltp); // after inverting the matrix, combine the ZUP (optimization) local2ltp.preMult( zup ); ss->addUniform( new osg::Uniform("oe_trees_local2ltp", osg::Matrixf(local2ltp)) ); ss->addUniform( new osg::Uniform("oe_trees_ltp2local", osg::Matrixf(ltp2local)) ); // calculate the scatter area: float h = key.getExtent().height() * 111320.0f; float w = key.getExtent().width() * 111320.0f * cos(fabs(osg::DegreesToRadians(p.y()))); ss->addUniform( new osg::Uniform("oe_trees_span", osg::Vec2f(w,h)) ); ss->setTextureAttributeAndModes(2, tile->getElevationTexture(), 1); ss->addUniform( new osg::Uniform("oe_terrain_tex_matrix", osg::Matrixf(*elevationTexMat)) ); } }
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; }
TileKey TerrainLayer::getBestAvailableTileKey(const TileKey& key) const { // trivial reject if ( !key.valid() ) return TileKey::INVALID; unsigned MDL = options().maxDataLevel().get(); // We must use the equivalent lod b/c the input key can be in any profile. unsigned localLOD = getProfile() ? getProfile()->getEquivalentLOD(key.getProfile(), key.getLOD()) : key.getLOD(); // Check against level extrema: if (localLOD < options().minLevel().get() || localLOD > options().maxLevel().get()) { return TileKey::INVALID; } // Next, check against resolution limits (based on the source tile size). if (options().minResolution().isSet() || options().maxResolution().isSet()) { const Profile* profile = getProfile(); if ( profile ) { // calculate the resolution in the layer's profile, which can // be different that the key's profile. double resKey = key.getExtent().width() / (double)getTileSize(); double resLayer = key.getProfile()->getSRS()->transformUnits(resKey, profile->getSRS()); if (options().maxResolution().isSet() && options().maxResolution().value() > resLayer) { return TileKey::INVALID; } if (options().minResolution().isSet() && options().minResolution().value() < resLayer) { return TileKey::INVALID; } } } // Next check against the data extents. const DataExtentList& de = getDataExtents(); // If we have mo data extents available, just return the MDL-limited input key. if (de.empty()) { return localLOD > MDL ? key.createAncestorKey(MDL) : key; } // Reject if the extents don't overlap at all. if (!getDataExtentsUnion().intersects(key.getExtent())) { return TileKey::INVALID; } bool intersects = false; unsigned highestLOD = 0; // Check each data extent in turn: for (DataExtentList::const_iterator itr = de.begin(); itr != de.end(); ++itr) { // check for 2D intersection: if (key.getExtent().intersects(*itr)) { // check that the extent isn't higher-resolution than our key: if ( !itr->minLevel().isSet() || localLOD >= (int)itr->minLevel().get() ) { // Got an intersetion; now test the LODs: intersects = true; // Is the high-LOD set? If not, there's not enough information // so just assume our key might be good. if ( itr->maxLevel().isSet() == false ) { return localLOD > MDL ? key.createAncestorKey(MDL) : key; } // Is our key at a lower or equal LOD than the max key in this extent? // If so, our key is good. else if ( localLOD <= (int)itr->maxLevel().get() ) { return localLOD > MDL ? key.createAncestorKey(MDL) : key; } // otherwise, record the highest encountered LOD that // intersects our key. else if ( itr->maxLevel().get() > highestLOD ) { highestLOD = itr->maxLevel().get(); } } } } if ( intersects ) { return key.createAncestorKey(std::min(highestLOD, MDL)); } return TileKey::INVALID; }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { osg::ref_ptr<osg::HeightField> result; // If the layer is disabled, bail out. if ( _runtimeOptions.enabled().isSetTo( false ) ) { return GeoHeightField::INVALID; } // Check the max data level, which limits the LOD of available data. if ( _runtimeOptions.maxDataLevel().isSet() && key.getLOD() > _runtimeOptions.maxDataLevel().value() ) { return GeoHeightField::INVALID; } CacheBin* cacheBin = getCacheBin( key.getProfile() ); // validate that we have either a valid tile source, or we're cache-only. if ( ! (getTileSource() || (isCacheOnly() && cacheBin) ) ) { OE_WARN << LC << "Error: layer does not have a valid TileSource, cannot create heightfield" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !isCacheOnly() && !getProfile() ) { OE_WARN << LC << "Could not establish a valid profile" << std::endl; _runtimeOptions.enabled() = false; return GeoHeightField::INVALID; } // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; if ( cacheBin && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readObject( key.str(), getCachePolicy().getMinAcceptTime() ); if ( r.succeeded() ) { osg::HeightField* cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF) ) { result = cachedHF; fromCache = true; } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !result.valid() && isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !result.valid() ) { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; if ( !isKeyValid(key) ) return GeoHeightField::INVALID; // build a HF from the TileSource. result = createHeightFieldFromTileSource( key, progress ); // validate it to make sure it's legal. if ( result.valid() && !validateHeightField(result.get()) ) { OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl; result = 0L; } } // cache if necessary if ( result && cacheBin && !fromCache && getCachePolicy().isCacheWriteable() ) { cacheBin->write( key.str(), result ); } if ( result ) { // 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); result->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(result->getNumColumns()-1); double dy = (maxy - miny)/(double)(result->getNumRows()-1); result->setXInterval( dx ); result->setYInterval( dy ); result->setBorderWidth( 0 ); } return result ? GeoHeightField( result, key.getExtent() ) : GeoHeightField::INVALID; }