bool TileMap::intersectsKey(const TileKey& tilekey) { osg::Vec3d keyMin, keyMax; //double keyMinX, keyMinY, keyMaxX, keyMaxY; //Check to see if the key overlaps the bounding box using lat/lon. This is necessary to check even in //Mercator situations in case the BoundingBox is described using lat/lon coordinates such as those produced by GDAL2Tiles //This should be considered a bug on the TMS production side, but we can work around it for now... tilekey.getExtent().getBounds(keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y()); //tilekey.getExtent().getBounds(keyMinX, keyMinY, keyMaxX, keyMaxY); bool inter = intersects(_minX, _minY, _maxX, _maxY, keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y() ); //keyMinX, keyMinY, keyMaxX, keyMaxY); if (!inter && tilekey.getProfile()->getSRS()->isSphericalMercator()) { tilekey.getProfile()->getSRS()->transform(keyMin, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMin ); tilekey.getProfile()->getSRS()->transform(keyMax, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMax ); inter = intersects(_minX, _minY, _maxX, _maxY, keyMin.x(), keyMin.y(), keyMax.x(), keyMax.y() ); //tilekey.getProfile()->getSRS()->transform2D(keyMinX, keyMinY, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMinX, keyMinY); //tilekey.getProfile()->getSRS()->transform2D(keyMaxX, keyMaxY, tilekey.getProfile()->getSRS()->getGeographicSRS(), keyMaxX, keyMaxY); //inter = intersects(_minX, _minY, _maxX, _maxY, keyMinX, keyMinY, keyMaxX, keyMaxY); } return inter; }
bool TerrainLayer::isKeyInLegalRange(const TileKey& key) const { if ( !key.valid() ) { return false; } // 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(); // First check the key against the min/max level limits, it they are set. if ((options().maxLevel().isSet() && localLOD > options().maxLevel().value()) || (options().minLevel().isSet() && localLOD < options().minLevel().value())) { return false; } // Next check the maxDataLevel if that is set. if (options().maxDataLevel().isSet() && localLOD > options().maxDataLevel().get()) { return false; } // 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 false; } if (options().minResolution().isSet() && options().minResolution().value() < resLayer) { return false; } } } return true; }
void TerrainTileModelFactory::addNormalMap(TerrainTileModel* model, const Map* map, const TileKey& key, ProgressCallback* progress) { OE_START_TIMER(fetch_normalmap); if (model->elevationModel().valid()) { const osgEarth::ElevationInterpolation& interp = map->getMapOptions().elevationInterpolation().get(); // Can only generate the normal map if the center heightfield was built: osg::ref_ptr<osg::Image> image = HeightFieldUtils::convertToNormalMap( model->heightFields(), key.getProfile()->getSRS() ); if (image.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( image.get() ); layerModel->setTexture( texture ); model->normalModel() = layerModel; } } if (progress) progress->stats()["fetch_normalmap_time"] += OE_STOP_TIMER(fetch_normalmap); }
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; }
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); }
GeoImage ImageLayer::createImageFromTileSource(const TileKey& key, ProgressCallback* progress) { TileSource* source = getTileSource(); if ( !source ) return GeoImage::INVALID; // If the profiles are different, use a compositing method to assemble the tile. if ( !key.getProfile()->isHorizEquivalentTo( getProfile() ) ) { return assembleImage( key, progress ); } // Good to go, ask the tile source for an image: osg::ref_ptr<TileSource::ImageOperation> op = getOrCreatePreCacheOp(); // Fail is the image is blacklisted. if ( source->getBlacklist()->contains(key) ) { OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl; return GeoImage::INVALID; } if (!mayHaveData(key)) { OE_DEBUG << LC << "createImageFromTileSource: mayHaveData(" << key.str() << ") == false" << std::endl; return GeoImage::INVALID; } //if ( !source->hasData( key ) ) //{ // OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl; // return GeoImage::INVALID; //} // create an image from the tile source. osg::ref_ptr<osg::Image> result = source->createImage( key, op.get(), progress ); // Process images with full alpha to properly support MP blending. if (result.valid() && options().featherPixels() == true) { ImageUtils::featherAlphaRegions( result.get() ); } // If image creation failed (but was not intentionally canceled and // didn't time out or end for any other recoverable reason), then // blacklist this tile for future requests. if (result == 0L) { if ( progress == 0L || ( !progress->isCanceled() && !progress->needsRetry() ) ) { source->getBlacklist()->add( key ); } } return GeoImage(result.get(), key.getExtent()); }
bool TerrainLayer::isCached(const TileKey& key) const { CacheBin* bin = const_cast<TerrainLayer*>(this)->getCacheBin( key.getProfile() ); if ( !bin ) return false; TimeStamp minTime = this->getCachePolicy().getMinAcceptTime(); return bin->getRecordStatus( key.str(), minTime ) == CacheBin::STATUS_OK; }
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); }
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; } }
GeoImage ImageLayer::createImageFromTileSource(const TileKey& key, ProgressCallback* progress) { TileSource* source = getTileSource(); if ( !source ) return GeoImage::INVALID; // If the profiles are different, use a compositing method to assemble the tile. if ( !key.getProfile()->isHorizEquivalentTo( getProfile() ) ) { return assembleImageFromTileSource( key, progress ); } // Good to go, ask the tile source for an image: osg::ref_ptr<TileSource::ImageOperation> op = _preCacheOp; // Fail is the image is blacklisted. if ( source->getBlacklist()->contains(key) ) { OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl; return GeoImage::INVALID; } if ( !source->hasData( key ) ) { OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl; return GeoImage::INVALID; } // create an image from the tile source. osg::ref_ptr<osg::Image> result = source->createImage( key, op.get(), progress ); // Process images with full alpha to properly support MP blending. if ( result.valid() && *_runtimeOptions.featherPixels()) { ImageUtils::featherAlphaRegions( result.get() ); } // If image creation failed (but was not intentionally canceled), // blacklist this tile for future requests. if ( result == 0L && (!progress || !progress->isCanceled()) ) { source->getBlacklist()->add( key ); } return GeoImage(result.get(), key.getExtent()); }
bool TerrainLayer::isCached(const TileKey& key) const { // first consult the policy: if ( getCachePolicy() == CachePolicy::NO_CACHE ) return false; else if ( getCachePolicy() == CachePolicy::CACHE_ONLY ) return true; // next check for a bin: CacheBin* bin = const_cast<TerrainLayer*>(this)->getCacheBin( key.getProfile() ); if ( !bin ) return false; return bin->getRecordStatus( key.str() ) == CacheBin::STATUS_OK; }
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 ( isEquivalentTo( key.getProfile() ) ) { //Clear the incoming list out_intersectingKeys.clear(); out_intersectingKeys.push_back(key); return; } return getIntersectingTiles(key.getExtent(), out_intersectingKeys); }
bool TerrainLayer::isKeyInRange(const TileKey& key) const { if ( !key.valid() ) { return false; } // First check the key against the min/max level limits, it they are set. if ((_runtimeOptions->maxLevel().isSet() && key.getLOD() > _runtimeOptions->maxLevel().value()) || (_runtimeOptions->minLevel().isSet() && key.getLOD() < _runtimeOptions->minLevel().value())) { return false; } // Next, check against resolution limits (based on the source tile size). if (_runtimeOptions->minResolution().isSet() || _runtimeOptions->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 (_runtimeOptions->maxResolution().isSet() && _runtimeOptions->maxResolution().value() > resLayer) { return false; } if (_runtimeOptions->minResolution().isSet() && _runtimeOptions->minResolution().value() < resLayer) { return false; } } } return true; }
std::string TMSCache::getFilename( const TileKey& key,const CacheSpec& spec ) const { unsigned int x,y; key.getTileXY(x, y); unsigned int lod = key.getLevelOfDetail(); unsigned int numCols, numRows; key.getProfile()->getNumTiles(lod, numCols, numRows); if ( _options.invertY() == false ) { y = numRows - y - 1; } std::stringstream buf; buf << getPath() << "/" << spec.cacheId() << "/" << lod << "/" << x << "/" << y << "." << spec.format(); std::string bufStr; bufStr = buf.str(); return bufStr; }
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 ); }
GeoImage ImageLayer::assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress) { GeoImage mosaicedImage, result; // Scale the extent if necessary to apply an "edge buffer" GeoExtent ext = key.getExtent(); if ( _runtimeOptions.edgeBufferRatio().isSet() ) { double ratio = _runtimeOptions.edgeBufferRatio().get(); ext.scale(ratio, ratio); } // Get a set of layer tiles that intersect the requested extent. std::vector<TileKey> intersectingKeys; getProfile()->getIntersectingTiles( key, intersectingKeys ); if ( intersectingKeys.size() > 0 ) { double dst_minx, dst_miny, dst_maxx, dst_maxy; key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); // if we find at least one "real" tile in the mosaic, then the whole result tile is // "real" (i.e. not a fallback tile) bool retry = false; ImageMosaic mosaic; // keep track of failed tiles. std::vector<TileKey> failedKeys; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { GeoImage image = createImageFromTileSource( *k, progress ); if ( image.valid() ) { if ( !isCoverage() ) { ImageUtils::fixInternalFormat(image.getImage()); // Make sure all images in mosaic are based on "RGBA - unsigned byte" pixels. // This is not the smarter choice (in some case RGB would be sufficient) but // it ensure consistency between all images / layers. // // The main drawback is probably the CPU memory foot-print which would be reduced by allocating RGB instead of RGBA images. // On GPU side, this should not change anything because of data alignements : often RGB and RGBA textures have the same memory footprint // if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } } mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // the tile source did not return a tile, so make a note of it. failedKeys.push_back( *k ); if (progress && (progress->isCanceled() || progress->needsRetry())) { retry = true; break; } } } if ( mosaic.getImages().empty() || retry ) { // if we didn't get any data, fail. OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; return GeoImage::INVALID; } // We got at least one good tile, so go through the bad ones and try to fall back on // lower resolution data to fill in the gaps. The entire mosaic must be populated or // this qualifies as a bad tile. for(std::vector<TileKey>::iterator k = failedKeys.begin(); k != failedKeys.end(); ++k) { GeoImage image; for(TileKey parentKey = k->createParentKey(); parentKey.valid() && !image.valid(); parentKey = parentKey.createParentKey()) { image = createImageFromTileSource( parentKey, progress ); if ( image.valid() ) { GeoImage cropped; if ( !isCoverage() ) { ImageUtils::fixInternalFormat(image.getImage()); if ( (image.getImage()->getDataType() != GL_UNSIGNED_BYTE) || (image.getImage()->getPixelFormat() != GL_RGBA) ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } cropped = image.crop( k->getExtent(), false, image.getImage()->s(), image.getImage()->t() ); } else { // TODO: may not work.... test; tilekey extent will <> cropped extent cropped = image.crop( k->getExtent(), true, image.getImage()->s(), image.getImage()->t(), false ); } // and queue it. mosaic.getImages().push_back( TileImage(cropped.getImage(), *k) ); } } if ( !image.valid() ) { // a tile completely failed, even with fallback. Eject. OE_DEBUG << LC << "Couldn't fallback on tiles for ImageMosaic" << std::endl; // let it go. The empty areas will be filled with alpha by ImageMosaic. } } // all set. Mosaic all the images together. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); mosaicedImage = GeoImage( mosaic.createImage(), GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) ); } else { OE_DEBUG << LC << "assembleImageFromTileSource: no intersections (" << key.str() << ")" << std::endl; } // Final step: transform the mosaic into the requesting key's extent. if ( mosaicedImage.valid() ) { // GeoImage::reproject() will automatically crop the image to the correct extents. // so there is no need to crop after reprojection. Also note that if the SRS's are the // same (even though extents are different), then this operation is technically not a // reprojection but merely a resampling. result = mosaicedImage.reproject( key.getProfile()->getSRS(), &key.getExtent(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.driver()->bilinearReprojection() ); } // Process images with full alpha to properly support MP blending. if ( result.valid() && *_runtimeOptions.featherPixels() && !isCoverage() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
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; }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { METRIC_SCOPED_EX("ElevationLayer::createHeightField", 2, "key", key.str().c_str(), "name", getName().c_str()); if (getStatus().isError()) { return GeoHeightField::INVALID; } // If the layer is disabled, bail out. if ( getEnabled() == false ) { return GeoHeightField::INVALID; } GeoHeightField result; osg::ref_ptr<osg::HeightField> hf; osg::ref_ptr<NormalMap> normalMap; // Check the memory cache first bool fromMemCache = false; // cache key combines the key with the full signature (incl vdatum) // the cache key combines the Key and the horizontal profile. std::string cacheKey = Cache::makeCacheKey( Stringify() << key.str() << "-" << key.getProfile()->getHorizSignature(), "elevation"); const CachePolicy& policy = getCacheSettings()->cachePolicy().get(); if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); ReadResult cacheResult = bin->readObject(cacheKey, 0L); if ( cacheResult.succeeded() ) { result = GeoHeightField( static_cast<osg::HeightField*>(cacheResult.releaseObject()), key.getExtent()); fromMemCache = true; } } if ( !result.valid() ) { // See if there's a persistent cache. CacheBin* cacheBin = getCacheBin( key.getProfile() ); // Can we continue? Only if either: // a) there is a valid tile source plugin; // b) a tile source is not expected, meaning the subclass overrides getHeightField; or // c) we are in cache-only mode and there is a valid cache bin. bool canContinue = getTileSource() || !isTileSourceExpected() || (policy.isCacheOnly() && cacheBin != 0L); if (!canContinue) { disable("Error: layer does not have a valid TileSource, cannot create heightfield"); return GeoHeightField::INVALID; } // validate the existance of a valid layer profile. if ( !policy.isCacheOnly() && !getProfile() ) { disable("Could not establish a valid profile.. did you set one?"); return GeoHeightField::INVALID; } // Now attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. bool fromCache = false; osg::ref_ptr< osg::HeightField > cachedHF; if ( cacheBin && policy.isCacheReadable() ) { ReadResult r = cacheBin->readObject(cacheKey, 0L); if ( r.succeeded() ) { bool expired = policy.isExpired(r.lastModifiedTime()); cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF.get()) ) { if (!expired) { hf = cachedHF; fromCache = true; } } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !hf.valid() && policy.isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !hf.valid() ) { if ( !isKeyInLegalRange(key) ) return GeoHeightField::INVALID; // If no tile source is expected, create a height field by calling // the raw inheritable method. if (!isTileSourceExpected()) { createImplementation(key, hf, normalMap, progress); //hf = createHeightFieldImplementation(key, progress); } else { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; // build a HF from the TileSource. //hf = createHeightFieldImplementation( key, progress ); createImplementation(key, hf, normalMap, progress); } // Check for cancelation before writing to a cache if (progress && progress->isCanceled()) { return GeoHeightField::INVALID; } // validate it to make sure it's legal. if ( hf.valid() && !validateHeightField(hf.get()) ) { OE_WARN << LC << "Driver " << getTileSource()->getName() << " returned an illegal heightfield" << std::endl; hf = 0L; // to fall back on cached data if possible. } // cache if necessary if ( hf && cacheBin && !fromCache && policy.isCacheWriteable() ) { cacheBin->write(cacheKey, hf.get(), 0L); } // We have an expired heightfield from the cache and no new data from the TileSource. So just return the cached data. if (!hf.valid() && cachedHF.valid()) { OE_DEBUG << LC << "Using cached but expired heightfield for " << key.str() << std::endl; hf = cachedHF; } if ( !hf.valid() ) { return GeoHeightField::INVALID; } // Set up the heightfield params. double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); hf->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(hf->getNumColumns()-1); double dy = (maxy - miny)/(double)(hf->getNumRows()-1); hf->setXInterval( dx ); hf->setYInterval( dy ); hf->setBorderWidth( 0 ); } if ( hf.valid() ) { result = GeoHeightField( hf.get(), normalMap.get(), key.getExtent() ); } } // Check for cancelation before writing to a cache: if ( progress && progress->isCanceled() ) { return GeoHeightField::INVALID; } // post-processing -- must be done before caching because it may alter the heightfield data if ( result.valid() && !fromMemCache && hf.valid() ) { if ( options().noDataPolicy() == NODATA_MSL ) { // requested VDatum: const VerticalDatum* outputVDatum = key.getExtent().getSRS()->getVerticalDatum(); const Geoid* geoid = 0L; // if there's an output vdatum, just set all invalid's to zero MSL. if ( outputVDatum == 0L ) { // if the output is geodetic (HAE), but the input has a geoid, // use that geoid to populate the invalid data at sea level. const VerticalDatum* profileDatum = getProfile()->getSRS()->getVerticalDatum(); if ( profileDatum ) geoid = profileDatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( hf.get(), result.getExtent(), NO_DATA_VALUE, geoid ); } } // write to mem cache if needed: if ( result.valid() && !fromMemCache && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); bin->write(cacheKey, result.getHeightField(), 0L); } return result; }
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; } }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { osg::HeightField* result = 0L; // If the layer is disabled, bail out. if ( _runtimeOptions.enabled().isSetTo( false ) ) { 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() ); if ( r.succeeded() ) { result = r.release<osg::HeightField>(); if ( result ) fromCache = true; } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !result && isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !result ) { // 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 ); } // 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; }
GeoImage ImageLayer::createImageInNativeProfile( const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback) { out_isFallback = false; const Profile* nativeProfile = getProfile(); if ( !nativeProfile ) { OE_WARN << LC << "Could not establish the profile" << std::endl; return GeoImage::INVALID; } if ( key.getProfile()->isEquivalentTo(nativeProfile) ) { // requested profile matches native profile, move along. return createImageInKeyProfile( key, progress, forceFallback, out_isFallback ); } else { // find the intersection of keys. std::vector<TileKey> nativeKeys; nativeProfile->getIntersectingTiles(key.getExtent(), nativeKeys); //OE_INFO << "KEY = " << key.str() << ":" << std::endl; //for(int i=0; i<nativeKeys.size(); ++i) // OE_INFO << " " << nativeKeys[i].str() << std::endl; // build a mosaic of the images from the native profile keys: bool foundAtLeastOneRealTile = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k ) { bool isFallback = false; GeoImage image = createImageInKeyProfile( *k, progress, true, isFallback ); if ( image.valid() ) { mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); if ( !isFallback ) foundAtLeastOneRealTile = true; } else { // if we get EVEN ONE invalid tile, we have to abort because there will be // empty spots in the mosaic. (By "invalid" we mean a tile that could not // even be resolved through the fallback procedure.) return GeoImage::INVALID; } } // bail out if we got nothing. if ( mosaic.getImages().size() == 0 ) return GeoImage::INVALID; // if the mosaic is ALL fallback data, this tile is fallback data. if ( foundAtLeastOneRealTile ) { // assemble new GeoImage from the mosaic. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); GeoImage result( mosaic.createImage(), GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); #if 1 return result; #else // let's try this. why crop? Just leave it. Faster and more compatible with NPOT // systems (like iOS) // calculate a tigher extent that matches the original input key: GeoExtent tightExtent = nativeProfile->clampAndTransformExtent( key.getExtent() ); // a non-exact crop is critical here to avoid resampling the data return result.crop( tightExtent, false, 0, 0, *_runtimeOptions.driver()->bilinearReprojection() ); #endif } else // all fallback data { GeoImage result; if ( forceFallback && key.getLevelOfDetail() > 0 ) { result = createImageInNativeProfile( key.createParentKey(), progress, forceFallback, out_isFallback ); } out_isFallback = true; return result; } //if ( !foundAtLeastOneRealTile ) // out_isFallback = true; } }
GeoImage ImageLayer::createImageInKeyProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } // If the layer is disabled, bail out. if ( !getEnabled() ) { return GeoImage::INVALID; } // Make sure the request is in range. if ( !isKeyInRange(key) ) { return GeoImage::INVALID; } GeoImage result; OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // the cache key combines the Key and the horizontal profile. std::string cacheKey = Stringify() << key.str() << "_" << key.getProfile()->getHorizSignature(); const CachePolicy& policy = getCacheSettings()->cachePolicy().get(); // Check the layer L2 cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); ReadResult result = bin->readObject(cacheKey, 0L); if ( result.succeeded() ) return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent()); } // 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() || (cacheBin && policy.isCacheOnly())) { //nop = OK. } else { disable("Error: layer does not have a valid TileSource, cannot create image"); 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 ( !policy.isCacheOnly() && !getProfile() ) { disable("Could not establish a valid profile"); return GeoImage::INVALID; } osg::ref_ptr< osg::Image > cachedImage; // First, attempt to read from the cache. Since the cached data is stored in the // map profile, we can try this first. if ( cacheBin && policy.isCacheReadable() ) { ReadResult r = cacheBin->readImage(cacheKey, 0L); if ( r.succeeded() ) { cachedImage = r.releaseImage(); ImageUtils::fixInternalFormat( cachedImage.get() ); bool expired = policy.isExpired(r.lastModifiedTime()); if (!expired) { OE_DEBUG << "Got cached image for " << key.str() << std::endl; return GeoImage( cachedImage.get(), key.getExtent() ); } else { OE_DEBUG << "Expired image for " << key.str() << std::endl; } } } // The data was not in the cache. If we are cache-only, fail sliently if ( policy.isCacheOnly() ) { // If it's cache only and we have an expired but cached image, just return it. if (cachedImage.valid()) { return GeoImage( cachedImage.get(), key.getExtent() ); } else { return GeoImage::INVALID; } } // Get an image from the underlying TileSource. result = createImageFromTileSource( key, progress ); // Normalize the image if necessary if ( result.valid() ) { ImageUtils::fixInternalFormat( result.getImage() ); } // memory cache first: if ( result.valid() && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateDefaultBin(); bin->write(cacheKey, result.getImage(), 0L); } // 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() && cacheBin && policy.isCacheWriteable()) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write(cacheKey, result.getImage(), 0L); } if ( result.valid() ) { OE_DEBUG << LC << key.str() << " result OK" << std::endl; } else { OE_DEBUG << LC << key.str() << "result INVALID" << std::endl; // We couldn't get an image from the source. So see if we have an expired cached image if (cachedImage.valid()) { OE_DEBUG << LC << "Using cached but expired image for " << key.str() << std::endl; result = GeoImage( cachedImage.get(), key.getExtent()); } } return result; }
GeoImage ImageLayer::createImageInNativeProfile(const TileKey& key, ProgressCallback* progress) { if (getStatus().isError()) { return GeoImage::INVALID; } const Profile* nativeProfile = getProfile(); if ( !nativeProfile ) { OE_WARN << LC << "Could not establish the profile" << std::endl; return GeoImage::INVALID; } GeoImage result; if ( key.getProfile()->isHorizEquivalentTo(nativeProfile) ) { // requested profile matches native profile, move along. result = createImageInKeyProfile( key, progress ); } else { // find the intersection of keys. std::vector<TileKey> nativeKeys; nativeProfile->getIntersectingTiles(key, nativeKeys); // build a mosaic of the images from the native profile keys: bool foundAtLeastOneRealTile = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = nativeKeys.begin(); k != nativeKeys.end(); ++k ) { bool isFallback = false; GeoImage image = createImageInKeyProfile( *k, progress ); if ( image.valid() ) { mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); } else { // if we get EVEN ONE invalid tile, we have to abort because there will be // empty spots in the mosaic. (By "invalid" we mean a tile that could not // even be resolved through the fallback procedure.) return GeoImage::INVALID; //TODO: probably need to change this so the mosaic uses alpha. } } // bail out if we got nothing. if ( mosaic.getImages().size() > 0 ) { // assemble new GeoImage from the mosaic. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); result = GeoImage( mosaic.createImage(), GeoExtent( nativeProfile->getSRS(), rxmin, rymin, rxmax, rymax ) ); } } return result; }
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; }
GeoImage ImageLayer::createImageFromTileSource(const TileKey& key, ProgressCallback* progress, bool forceFallback, bool& out_isFallback) { // Results: // // * return an osg::Image matching the key extent is all goes well; // // * return NULL to indicate that the key exceeds the maximum LOD of the source data, // and that the engine may need to generate a "fallback" tile if necessary; // // deprecated: // * return an "empty image" if the LOD is valid BUT the key does not intersect the // source's data extents. out_isFallback = false; TileSource* source = getTileSource(); if ( !source ) return GeoImage::INVALID; // If the profiles are different, use a compositing method to assemble the tile. if ( !key.getProfile()->isEquivalentTo( getProfile() ) ) { return assembleImageFromTileSource( key, progress, out_isFallback ); } // Good to go, ask the tile source for an image: osg::ref_ptr<TileSource::ImageOperation> op = _preCacheOp; osg::ref_ptr<osg::Image> result; if ( forceFallback ) { // check if the tile source has any data coverage for the requested key. // the LOD is ignore here and checked later if ( !source->hasDataInExtent( key.getExtent() ) ) { OE_DEBUG << LC << "createImageFromTileSource: hasDataInExtent(" << key.str() << ") == false" << std::endl; return GeoImage::INVALID; } TileKey finalKey = key; while( !result.valid() && finalKey.valid() ) { if ( !source->getBlacklist()->contains( finalKey.getTileId() ) && source->hasDataForFallback(finalKey)) { result = source->createImage( finalKey, op.get(), progress ); if ( result.valid() ) { if ( finalKey.getLevelOfDetail() != key.getLevelOfDetail() ) { // crop the fallback image to match the input key, and ensure that it remains the // same pixel size; because chances are if we're requesting a fallback that we're // planning to mosaic it later, and the mosaicer requires same-size images. GeoImage raw( result.get(), finalKey.getExtent() ); GeoImage cropped = raw.crop( key.getExtent(), true, raw.getImage()->s(), raw.getImage()->t(), *_runtimeOptions.driver()->bilinearReprojection() ); result = cropped.takeImage(); } } } if ( !result.valid() ) { finalKey = finalKey.createParentKey(); out_isFallback = true; } } if ( !result.valid() ) { result = 0L; //result = _emptyImage.get(); finalKey = key; } } else { // Fail is the image is blacklisted. if ( source->getBlacklist()->contains( key.getTileId() ) ) { OE_DEBUG << LC << "createImageFromTileSource: blacklisted(" << key.str() << ")" << std::endl; return GeoImage::INVALID; } if ( !source->hasData( key ) ) { OE_DEBUG << LC << "createImageFromTileSource: hasData(" << key.str() << ") == false" << std::endl; return GeoImage::INVALID; } result = source->createImage( key, op.get(), progress ); } // Process images with full alpha to properly support MP blending. if ( result != 0L && *_runtimeOptions.featherPixels()) { ImageUtils::featherAlphaRegions( result.get() ); } // If image creation failed (but was not intentionally canceled), // blacklist this tile for future requests. if ( result == 0L && (!progress || !progress->isCanceled()) ) { source->getBlacklist()->add( key.getTileId() ); } return GeoImage(result.get(), key.getExtent()); }
GeoImage ImageLayer::assembleImageFromTileSource(const TileKey& key, ProgressCallback* progress, bool& out_isFallback) { GeoImage mosaicedImage, result; out_isFallback = false; // Scale the extent if necessary to apply an "edge buffer" GeoExtent ext = key.getExtent(); if ( _runtimeOptions.edgeBufferRatio().isSet() ) { double ratio = _runtimeOptions.edgeBufferRatio().get(); ext.scale(ratio, ratio); } // Get a set of layer tiles that intersect the requested extent. std::vector<TileKey> intersectingKeys; getProfile()->getIntersectingTiles( ext, intersectingKeys ); if ( intersectingKeys.size() > 0 ) { double dst_minx, dst_miny, dst_maxx, dst_maxy; key.getExtent().getBounds(dst_minx, dst_miny, dst_maxx, dst_maxy); // if we find at least one "real" tile in the mosaic, then the whole result tile is // "real" (i.e. not a fallback tile) bool foundAtLeastOneRealTile = false; bool retry = false; ImageMosaic mosaic; for( std::vector<TileKey>::iterator k = intersectingKeys.begin(); k != intersectingKeys.end(); ++k ) { double minX, minY, maxX, maxY; k->getExtent().getBounds(minX, minY, maxX, maxY); bool isFallback = false; GeoImage image = createImageFromTileSource( *k, progress, true, isFallback ); if ( image.valid() ) { // make sure the image is RGBA. // (TODO: investigate whether we still need this -gw 6/25/2012) if (image.getImage()->getPixelFormat() != GL_RGBA || image.getImage()->getDataType() != GL_UNSIGNED_BYTE || image.getImage()->getInternalTextureFormat() != GL_RGBA8 ) { osg::ref_ptr<osg::Image> convertedImg = ImageUtils::convertToRGBA8(image.getImage()); if (convertedImg.valid()) { image = GeoImage(convertedImg, image.getExtent()); } } mosaic.getImages().push_back( TileImage(image.getImage(), *k) ); if ( !isFallback ) foundAtLeastOneRealTile = true; } else { // the tile source did not return a tile, so make a note of it. if (progress && (progress->isCanceled() || progress->needsRetry())) { retry = true; break; } } } if ( mosaic.getImages().empty() || retry ) { // if we didn't get any data, fail OE_DEBUG << LC << "Couldn't create image for ImageMosaic " << std::endl; return GeoImage::INVALID; } // all set. Mosaic all the images together. double rxmin, rymin, rxmax, rymax; mosaic.getExtents( rxmin, rymin, rxmax, rymax ); mosaicedImage = GeoImage( mosaic.createImage(), GeoExtent( getProfile()->getSRS(), rxmin, rymin, rxmax, rymax ) ); if ( !foundAtLeastOneRealTile ) out_isFallback = true; } else { OE_DEBUG << LC << "assembleImageFromTileSource: no intersections (" << key.str() << ")" << std::endl; } // Final step: transform the mosaic into the requesting key's extent. if ( mosaicedImage.valid() ) { // GeoImage::reproject() will automatically crop the image to the correct extents. // so there is no need to crop after reprojection. Also note that if the SRS's are the // same (even though extents are different), then this operation is technically not a // reprojection but merely a resampling. result = mosaicedImage.reproject( key.getProfile()->getSRS(), &key.getExtent(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.reprojectedTileSize(), *_runtimeOptions.driver()->bilinearReprojection()); } // Process images with full alpha to properly support MP blending. if ( result.valid() && *_runtimeOptions.featherPixels() ) { ImageUtils::featherAlphaRegions( result.getImage() ); } return result; }
bool ElevationLayerVector::createHeightField(const TileKey& key, bool fallback, const Profile* haeProfile, ElevationInterpolation interpolation, ElevationSamplePolicy samplePolicy, osg::ref_ptr<osg::HeightField>& out_result, bool* out_isFallback, ProgressCallback* progress ) const { unsigned lowestLOD = key.getLevelOfDetail(); bool hfInitialized = false; //Get a HeightField for each of the enabled layers GeoHeightFieldVector heightFields; //The number of fallback heightfields we have int numFallbacks = 0; //Default to being fallback data. if ( out_isFallback ) { *out_isFallback = true; } // if the caller provided an "HAE map profile", he wants an HAE elevation grid even if // the map profile has a vertical datum. This is the usual case when building the 3D // terrain, for example. Construct a temporary key that doesn't have the vertical // datum info and use that to query the elevation data. TileKey keyToUse = key; if ( haeProfile ) { keyToUse = TileKey(key.getLevelOfDetail(), key.getTileX(), key.getTileY(), haeProfile ); } // Generate a heightfield for each elevation layer. unsigned defElevSize = 8; for( ElevationLayerVector::const_iterator i = this->begin(); i != this->end(); i++ ) { ElevationLayer* layer = i->get(); if ( layer->getVisible() ) { GeoHeightField geoHF = layer->createHeightField( keyToUse, progress ); // if "fallback" is set, try to fall back on lower LODs. if ( !geoHF.valid() && fallback ) { TileKey hf_key = keyToUse.createParentKey(); while ( hf_key.valid() && !geoHF.valid() ) { geoHF = layer->createHeightField( hf_key, progress ); if ( !geoHF.valid() ) hf_key = hf_key.createParentKey(); } if ( geoHF.valid() ) { if ( hf_key.getLevelOfDetail() < lowestLOD ) lowestLOD = hf_key.getLevelOfDetail(); //This HeightField is fallback data, so increment the count. numFallbacks++; } } if ( geoHF.valid() ) { heightFields.push_back( geoHF ); } } } //If any of the layers produced valid data then it's not considered a fallback if ( out_isFallback ) { *out_isFallback = (numFallbacks == heightFields.size()); //OE_NOTICE << "Num fallbacks=" << numFallbacks << " numHeightFields=" << heightFields.size() << " is fallback " << *out_isFallback << std::endl; } if ( heightFields.size() == 0 ) { //If we got no heightfields but were requested to fallback, create an empty heightfield. if ( fallback ) { out_result = HeightFieldUtils::createReferenceHeightField( keyToUse.getExtent(), defElevSize, defElevSize ); return true; } else { //We weren't requested to fallback so just return. return false; } } else if (heightFields.size() == 1) { if ( lowestLOD == key.getLevelOfDetail() ) { //If we only have on heightfield, just return it. out_result = heightFields[0].takeHeightField(); } else { GeoHeightField geoHF = heightFields[0].createSubSample( key.getExtent(), interpolation); out_result = geoHF.takeHeightField(); hfInitialized = true; } } else { //If we have multiple heightfields, we need to composite them together. unsigned int width = 0; unsigned int height = 0; for (GeoHeightFieldVector::const_iterator i = heightFields.begin(); i < heightFields.end(); ++i) { if (i->getHeightField()->getNumColumns() > width) width = i->getHeightField()->getNumColumns(); if (i->getHeightField()->getNumRows() > height) height = i->getHeightField()->getNumRows(); } out_result = new osg::HeightField(); out_result->allocate( width, height ); //Go ahead and set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1); double dy = (maxy - miny)/(double)(out_result->getNumRows()-1); const SpatialReference* keySRS = keyToUse.getProfile()->getSRS(); //Create the new heightfield by sampling all of them. for (unsigned int c = 0; c < width; ++c) { double x = minx + (dx * (double)c); for (unsigned r = 0; r < height; ++r) { double y = miny + (dy * (double)r); //Collect elevations from all of the layers. Iterate BACKWARDS because the last layer // is the highest priority. std::vector<float> elevations; for( GeoHeightFieldVector::reverse_iterator itr = heightFields.rbegin(); itr != heightFields.rend(); ++itr ) { const GeoHeightField& geoHF = *itr; float elevation = 0.0f; if ( geoHF.getElevation(keySRS, x, y, interpolation, keySRS, elevation) ) { if (elevation != NO_DATA_VALUE) { elevations.push_back(elevation); } } } float elevation = NO_DATA_VALUE; //The list of elevations only contains valid values if (elevations.size() > 0) { if (samplePolicy == SAMPLE_FIRST_VALID) { elevation = elevations[0]; } else if (samplePolicy == SAMPLE_HIGHEST) { elevation = -FLT_MAX; for (unsigned int i = 0; i < elevations.size(); ++i) { if (elevation < elevations[i]) elevation = elevations[i]; } } else if (samplePolicy == SAMPLE_LOWEST) { elevation = FLT_MAX; for (unsigned i = 0; i < elevations.size(); ++i) { if (elevation > elevations[i]) elevation = elevations[i]; } } else if (samplePolicy == SAMPLE_AVERAGE) { elevation = 0.0; for (unsigned i = 0; i < elevations.size(); ++i) { elevation += elevations[i]; } elevation /= (float)elevations.size(); } } out_result->setHeight(c, r, elevation); } } } // Replace any NoData areas with the reference value. This is zero for HAE datums, // and some geoid height for orthometric datums. if (out_result.valid()) { const Geoid* geoid = 0L; const VerticalDatum* vdatum = key.getProfile()->getSRS()->getVerticalDatum(); if ( haeProfile && vdatum ) { geoid = vdatum->getGeoid(); } HeightFieldUtils::resolveInvalidHeights( out_result.get(), key.getExtent(), NO_DATA_VALUE, geoid ); //ReplaceInvalidDataOperator o; //o.setValidDataOperator(new osgTerrain::NoDataValue(NO_DATA_VALUE)); //o( out_result.get() ); } //Initialize the HF values for osgTerrain if (out_result.valid() && !hfInitialized ) { //Go ahead and set up the heightfield so we don't have to worry about it later double minx, miny, maxx, maxy; key.getExtent().getBounds(minx, miny, maxx, maxy); out_result->setOrigin( osg::Vec3d( minx, miny, 0.0 ) ); double dx = (maxx - minx)/(double)(out_result->getNumColumns()-1); double dy = (maxy - miny)/(double)(out_result->getNumRows()-1); out_result->setXInterval( dx ); out_result->setYInterval( dy ); out_result->setBorderWidth( 0 ); } return out_result.valid(); }