bool TerrainLayer::isKeyValid(const TileKey& key) const { if (!key.valid()) return false; const TerrainLayerOptions& opt = getTerrainLayerOptions(); //Check to see if explicit levels of detail are set if ( opt.minLevel().isSet() && (int)key.getLevelOfDetail() < opt.minLevel().value() ) return false; if ( opt.maxLevel().isSet() && (int)key.getLevelOfDetail() > opt.maxLevel().value() ) return false; //Check to see if levels of detail based on resolution are set if (opt.minLevelResolution().isSet()) { unsigned int minLevel = getProfile()->getLevelOfDetailForHorizResolution( opt.minLevelResolution().value(), getTileSize()); OE_DEBUG << "Computed min level of " << minLevel << std::endl; if (key.getLevelOfDetail() < minLevel) return false; } if (opt.maxLevelResolution().isSet()) { unsigned int maxLevel = getProfile()->getLevelOfDetailForHorizResolution( opt.maxLevelResolution().value(), getTileSize()); OE_DEBUG << "Computed max level of " << maxLevel << std::endl; if (key.getLevelOfDetail() > maxLevel) return false; } return true; }
FeatureCursor* createFeatureCursor( const Symbology::Query& query ) { TileKey key = *query.tileKey(); int z = key.getLevelOfDetail(); int tileX = key.getTileX(); int tileY = key.getTileY(); unsigned int numRows, numCols; key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows); tileY = numRows - tileY - 1; //Get the image sqlite3_stmt* select = NULL; std::string queryStr = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?"; int rc = sqlite3_prepare_v2( _database, queryStr.c_str(), -1, &select, 0L ); if ( rc != SQLITE_OK ) { OE_WARN << LC << "Failed to prepare SQL: " << queryStr << "; " << sqlite3_errmsg(_database) << std::endl; return NULL; } bool valid = true; sqlite3_bind_int( select, 1, z ); sqlite3_bind_int( select, 2, tileX ); sqlite3_bind_int( select, 3, tileY ); rc = sqlite3_step( select ); FeatureList features; if ( rc == SQLITE_ROW) { // the pointer returned from _blob gets freed internally by sqlite, supposedly const char* data = (const char*)sqlite3_column_blob( select, 0 ); int dataLen = sqlite3_column_bytes( select, 0 ); std::string dataBuffer( data, dataLen ); std::stringstream in(dataBuffer); MVT::read(in, key, features); } else { OE_DEBUG << LC << "SQL QUERY failed for " << queryStr << ": " << std::endl; valid = false; } sqlite3_finalize( select ); // apply filters before returning. applyFilters( features ); if (!features.empty()) { //OE_NOTICE << "Returning " << features.size() << " features" << std::endl; return new FeatureListCursor(features); } return 0; }
void CacheSeed::processKey(const MapFrame& mapf, const TileKey& key ) const { unsigned int x, y, lod; key.getTileXY(x, y); lod = key.getLevelOfDetail(); bool gotData = true; if ( _minLevel <= lod && _maxLevel >= lod ) { gotData = cacheTile( mapf, key ); if (gotData) { incrementCompleted( 1 ); } if ( _progress.valid() && _progress->isCanceled() ) return; // Task has been cancelled by user if ( _progress.valid() && gotData && _progress->reportProgress(_completed, _total, std::string("Cached tile: ") + key.str()) ) return; // Canceled } if ( gotData && lod <= _maxLevel ) { TileKey k0 = key.createChildKey(0); TileKey k1 = key.createChildKey(1); TileKey k2 = key.createChildKey(2); TileKey k3 = key.createChildKey(3); bool intersectsKey = false; if (_extents.empty()) intersectsKey = true; else { for (unsigned int i = 0; i < _extents.size(); ++i) { if (_extents[i].intersects( k0.getExtent() ) || _extents[i].intersects( k1.getExtent() ) || _extents[i].intersects( k2.getExtent() ) || _extents[i].intersects( k3.getExtent() )) { intersectsKey = true; } } } //Check to see if the bounds intersects ANY of the tile's children. If it does, then process all of the children //for this level if (intersectsKey) { processKey(mapf, k0); processKey(mapf, k1); processKey(mapf, k2); processKey(mapf, k3); } } }
bool OSGTileFactory::hasMoreLevels( Map* map, const TileKey& key ) { //Threading::ScopedReadLock lock( map->getMapDataMutex() ); bool more_levels = false; ImageLayerVector imageLayers; map->getImageLayers( imageLayers ); for ( ImageLayerVector::const_iterator i = imageLayers.begin(); i != imageLayers.end(); i++ ) { const ImageLayerOptions& opt = i->get()->getImageLayerOptions(); if ( !opt.maxLevel().isSet() || key.getLevelOfDetail() < (unsigned int)*opt.maxLevel() ) { more_levels = true; break; } } if ( !more_levels ) { ElevationLayerVector elevLayers; map->getElevationLayers( elevLayers ); for( ElevationLayerVector::const_iterator j = elevLayers.begin(); j != elevLayers.end(); j++ ) { const ElevationLayerOptions& opt = j->get()->getElevationLayerOptions(); if ( !opt.maxLevel().isSet() || key.getLevelOfDetail() < (unsigned int)*opt.maxLevel() ) //if ( !j->get()->maxLevel().isSet() || key.getLevelOfDetail() < j->get()->maxLevel().get() ) { more_levels = true; break; } } } return more_levels; }
StreamingTile::StreamingTile(const TileKey& key, GeoLocator* keyLocator, bool quickReleaseGLObjects ) : Tile( key, keyLocator, quickReleaseGLObjects ), _requestsInstalled ( false ), _elevationLayerDirty ( false ), _colorLayersDirty ( false ), _elevationLayerUpToDate( true ), _elevationLOD ( key.getLevelOfDetail() ), _useTileGenRequest ( true ) { // because the lowest LOD (1) is always loaded fully: _elevationLayerUpToDate = _key.getLevelOfDetail() <= 1; }
std::string createURL( const TileKey& key ) const { unsigned int x, y; key.getTileXY(x, y); unsigned int lod = key.getLevelOfDetail()+1; //http://s0.tileservice.worldwindcentral.com/getTile?interface=map&version=1&dataset=bmng.topo.bathy.200401&level=0&x=0&y=0 return Stringify() << _options.url()->full() << "interface=map&version=1" << "&dataset=" << _options.dataset().value() << "&level=" << lod << "&x=" << x << "&y=" << y << "&." << _formatToUse; //Add this to trick osg into using the correct loader. }
void TileVisitor::processKey( const TileKey& key ) { // If we've been cancelled then just return. if (_progress && _progress->isCanceled()) { return; } unsigned int x, y, lod; key.getTileXY(x, y); lod = key.getLevelOfDetail(); // Only process this key if it has a chance of succeeding. if (_tileHandler && !_tileHandler->hasData(key)) { return; } bool traverseChildren = false; // If the key intersects the extent attempt to traverse if (intersects( key.getExtent() )) { // If the lod is less than the min level don't do anything but do traverse the children. if (lod < _minLevel) { traverseChildren = true; } else { // Process the key traverseChildren = handleTile( key ); } } // Traverse the children if (traverseChildren && lod < _maxLevel) { for (unsigned int i = 0; i < 4; i++) { TileKey k = key.createChildKey(i); processKey( k ); } } }
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; }
std::string getQuadKey(const TileKey& key) { unsigned int tile_x, tile_y; key.getTileXY(tile_x, tile_y); unsigned int lod = key.getLevelOfDetail(); std::stringstream ss; for( unsigned i = (int)lod+1; i > 0; i-- ) { char digit = '0'; unsigned mask = 1 << (i-1); if ( (tile_x & mask) != 0 ) { digit++; } if ( (tile_y & mask) != 0 ) { digit += 2; } ss << digit; } return ss.str(); }
int UnifiedCubeProfile::getFace( const TileKey& key ) { return key.getTileX() >> key.getLevelOfDetail(); }
bool MBTilesTileSource::storeImage(const TileKey& key, osg::Image* image, ProgressCallback* progress) { if ( (getMode() & MODE_WRITE) == 0 ) return false; Threading::ScopedMutexLock exclusiveLock(_mutex); // encode the data stream: std::stringstream buf; osgDB::ReaderWriter::WriteResult wr; if ( _forceRGB && ImageUtils::hasAlphaChannel(image) ) { osg::ref_ptr<osg::Image> rgb = ImageUtils::convertToRGB8(image); wr = _rw->writeImage(*(rgb.get()), buf, _dbOptions.get()); } else { wr = _rw->writeImage(*image, buf, _dbOptions.get()); } if ( wr.error() ) { OE_WARN << LC << "Image encoding failed: " << wr.message() << std::endl; return false; } std::string value = buf.str(); // compress if necessary: if ( _compressor.valid() ) { std::ostringstream output; if ( !_compressor->compress(output, value) ) { OE_WARN << LC << "Compressor failed" << std::endl; return false; } value = output.str(); } int z = key.getLOD(); int x = key.getTileX(); int y = key.getTileY(); // flip Y axis unsigned int numRows, numCols; key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows); y = numRows - y - 1; // Prep the insert statement: sqlite3_stmt* insert = NULL; std::string query = "INSERT OR REPLACE INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (?, ?, ?, ?)"; int rc = sqlite3_prepare_v2( _database, query.c_str(), -1, &insert, 0L ); if ( rc != SQLITE_OK ) { OE_WARN << LC << "Failed to prepare SQL: " << query << "; " << sqlite3_errmsg(_database) << std::endl; return false; } // bind parameters: sqlite3_bind_int( insert, 1, z ); sqlite3_bind_int( insert, 2, x ); sqlite3_bind_int( insert, 3, y ); // bind the data blob: sqlite3_bind_blob( insert, 4, value.c_str(), value.length(), SQLITE_STATIC ); // run the sql. bool ok = true; int tries = 0; do { rc = sqlite3_step(insert); } while (++tries < 100 && (rc == SQLITE_BUSY || rc == SQLITE_LOCKED)); if (SQLITE_OK != rc && SQLITE_DONE != rc) { #if SQLITE_VERSION_NUMBER >= 3007015 OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << sqlite3_errstr(rc) << "; " << sqlite3_errmsg(_database) << std::endl; #else OE_WARN << LC << "Failed query: " << query << "(" << rc << ")" << rc << "; " << sqlite3_errmsg(_database) << std::endl; #endif ok = false; } sqlite3_finalize( insert ); return ok; }
FeatureCursor* createFeatureCursor( const Symbology::Query& query ) { TileKey key = *query.tileKey(); int z = key.getLevelOfDetail(); int tileX = key.getTileX(); int tileY = key.getTileY(); unsigned int numRows, numCols; key.getProfile()->getNumTiles(key.getLevelOfDetail(), numCols, numRows); tileY = numRows - tileY - 1; //Get the image sqlite3_stmt* select = NULL; std::string queryStr = "SELECT tile_data from tiles where zoom_level = ? AND tile_column = ? AND tile_row = ?"; int rc = sqlite3_prepare_v2( _database, queryStr.c_str(), -1, &select, 0L ); if ( rc != SQLITE_OK ) { OE_WARN << LC << "Failed to prepare SQL: " << queryStr << "; " << sqlite3_errmsg(_database) << std::endl; return NULL; } bool valid = true; sqlite3_bind_int( select, 1, z ); sqlite3_bind_int( select, 2, tileX ); sqlite3_bind_int( select, 3, tileY ); rc = sqlite3_step( select ); FeatureList features; if ( rc == SQLITE_ROW) { // the pointer returned from _blob gets freed internally by sqlite, supposedly const char* data = (const char*)sqlite3_column_blob( select, 0 ); int dataLen = sqlite3_column_bytes( select, 0 ); std::string dataBuffer( data, dataLen ); // decompress if necessary: if ( _compressor.valid() ) { std::istringstream inputStream(dataBuffer); std::string value; if ( !_compressor->decompress(inputStream, value) ) { OE_WARN << LC << "Decompression failed" << std::endl; valid = false; } else { dataBuffer = value; } } mapnik::vector::tile tile; if (tile.ParseFromString(dataBuffer)) { // Get the layer in question for (unsigned int i = 0; i < tile.layers().size(); i++) { const mapnik::vector::tile_layer &layer = tile.layers().Get(i); //OE_NOTICE << layer.name() << std::endl; //if (layer.name() != "road") continue; //if (layer.name() != "building") continue; for (unsigned int j = 0; j < layer.features().size(); j++) { const mapnik::vector::tile_feature &feature = layer.features().Get(j); osg::ref_ptr< osgEarth::Symbology::Geometry > geometry; eGeomType geomType = static_cast<eGeomType>(feature.type()); if (geomType == ::Polygon) { //OE_NOTICE << "Polygon " << std::endl; geometry = new osgEarth::Symbology::Polygon(); } else if (geomType == ::LineString) { //OE_NOTICE << "LineString" << std::endl; geometry = new osgEarth::Symbology::LineString(); } else if (geomType == ::Point) { //OE_NOTICE << "Point" << std::endl; geometry = new osgEarth::Symbology::PointSet(); } else { //OE_NOTICE << "uknown" << std::endl; geometry = new osgEarth::Symbology::LineString(); } osg::ref_ptr< Feature > oeFeature = new Feature(geometry, key.getProfile()->getSRS()); features.push_back(oeFeature.get()); // Read attributes for (unsigned int k = 0; k < feature.tags().size(); k+=2) { std::string key = layer.keys().Get(feature.tags().Get(k)); mapnik::vector::tile_value value = layer.values().Get(feature.tags().Get(k+1)); if (value.has_bool_value()) { oeFeature->set(key, value.bool_value()); } else if (value.has_double_value()) { oeFeature->set(key, value.double_value()); } else if (value.has_float_value()) { oeFeature->set(key, value.float_value()); } else if (value.has_int_value()) { oeFeature->set(key, (int)value.int_value()); } else if (value.has_sint_value()) { oeFeature->set(key, (int)value.sint_value()); } else if (value.has_string_value()) { oeFeature->set(key, value.string_value()); } else if (value.has_uint_value()) { oeFeature->set(key, (int)value.uint_value()); } // Special path for getting heights from our test dataset. if (key == "other_tags") { std::string other_tags = value.string_value(); StringTokenizer tok("=>"); StringVector tized; tok.tokenize(other_tags, tized); if (tized.size() == 3) { if (tized[0] == "height") { std::string value = tized[2]; // Remove quotes from the height float height = as<float>(value, FLT_MAX); if (height != FLT_MAX) { oeFeature->set("height", height); } } } } } unsigned int length = 0; int cmd = -1; const int cmd_bits = 3; unsigned int tileres = layer.extent(); int x = 0; int y = 0; for (int k = 0; k < feature.geometry_size();) { if (!length) { unsigned int cmd_length = feature.geometry(k++); cmd = cmd_length & ((1 << cmd_bits) - 1); length = cmd_length >> cmd_bits; } if (length > 0) { length--; if (cmd == SEG_MOVETO || cmd == SEG_LINETO) { int px = feature.geometry(k++); int py = feature.geometry(k++); px = zig_zag_decode(px); py = zig_zag_decode(py); x += px; y += py; double width = key.getExtent().width(); double height = key.getExtent().height(); double geoX = key.getExtent().xMin() + (width/(double)tileres) * (double)x; double geoY = key.getExtent().yMax() - (height/(double)tileres) * (double)y; geometry->push_back(geoX, geoY, 0); } else if (cmd == (SEG_CLOSE & ((1 << cmd_bits) - 1))) { geometry->push_back(geometry->front()); } } } if (geometry->getType() == Geometry::TYPE_POLYGON) { geometry->rewind(osgEarth::Symbology::Geometry::ORIENTATION_CCW); } } } } else {
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::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; } }
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(); }
void TileModelFactory::createTileModel(const TileKey& key, osg::ref_ptr<TileModel>& out_model, bool& out_hasRealData, bool& out_hasLodBlendedLayers ) { MapFrame mapf( _map, Map::MASKED_TERRAIN_LAYERS ); const MapInfo& mapInfo = mapf.getMapInfo(); osg::ref_ptr<TileModel> model = new TileModel(); model->_tileKey = key; model->_tileLocator = GeoLocator::createForKey(key, mapInfo); // init this to false, then search for real data. "Real data" is data corresponding // directly to the key, as opposed to fallback data, which is derived from a lower // LOD key. out_hasRealData = false; out_hasLodBlendedLayers = false; // Fetch the image data and make color layers. for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() ) { BuildColorData build; build.init( key, layer, mapInfo, _terrainOptions, model.get() ); build.execute(); if ( layer->getImageLayerOptions().lodBlending() == true ) { out_hasLodBlendedLayers = true; } } } // make an elevation layer. BuildElevationData build; build.init( key, mapf, _terrainOptions, model.get(), _hfCache ); build.execute(); // Bail out now if there's no data to be had. if ( model->_colorData.size() == 0 && !model->_elevationData.getHFLayer() ) { return; } // OK we are making a tile, so if there's no heightfield yet, make an empty one. if ( !model->_elevationData.getHFLayer() ) { osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 8, 8 ); osgTerrain::HeightFieldLayer* hfLayer = new osgTerrain::HeightFieldLayer( hf ); hfLayer->setLocator( GeoLocator::createForKey(key, mapInfo) ); model->_elevationData = TileModel::ElevationData( hfLayer, true ); } // Now, if there are any color layers that did not get built, create them with an empty // image so the shaders have something to draw. osg::ref_ptr<osg::Image> emptyImage; osgTerrain::Locator* locator = model->_elevationData.getHFLayer()->getLocator(); for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() && !layer->isKeyValid(key) ) { if ( !emptyImage.valid() ) emptyImage = ImageUtils::createEmptyImage(); model->_colorData[i->get()->getUID()] = TileModel::ColorData( layer, emptyImage.get(), locator, key.getLevelOfDetail(), key, true ); } } // Ready to create the actual tile. //AssembleTile assemble; //assemble.init( key, mapInfo, _terrainOptions, model.get(), mapf.terrainMaskLayers() ); //assemble.execute(); // if we're using LOD blending, find and add the parent's state set. if ( out_hasLodBlendedLayers && key.getLevelOfDetail() > 0 && _liveTiles.valid() ) { osg::ref_ptr<TileNode> parent; if ( _liveTiles->get( key.createParentKey(), parent ) ) { model->_parentStateSet = parent->getPublicStateSet(); } } if (!out_hasRealData) { // Check the results and see if we have any real data. for( TileModel::ColorDataByUID::const_iterator i = model->_colorData.begin(); i != model->_colorData.end(); ++i ) { if ( !i->second.isFallbackData() ) { out_hasRealData = true; break; } } } if ( !out_hasRealData && !model->_elevationData.isFallbackData() ) { out_hasRealData = true; } out_model = model.release(); //out_tile = assemble._node; }
bool ElevationProxyImageLayer::isKeyInRange( const TileKey& key ) const { return key.getLevelOfDetail() <= *_runtimeOptions.maxLevel(); }
void TileBuilder::createTile(const TileKey& key, bool parallelize, osg::ref_ptr<Tile>& out_tile, bool& out_hasRealData, bool& out_hasLodBlendedLayers ) { MapFrame mapf( _map, Map::MASKED_TERRAIN_LAYERS ); SourceRepo repo; // init this to false, then search for real data. "Real data" is data corresponding // directly to the key, as opposed to fallback data, which is derived from a lower // LOD key. out_hasRealData = false; out_hasLodBlendedLayers = false; const MapInfo& mapInfo = mapf.getMapInfo(); // If we need more than one layer, fetch them in parallel. // TODO: change the test based on isKeyValid total. if ( parallelize && (mapf.imageLayers().size() + mapf.elevationLayers().size() > 1) ) { // count the valid layers. int jobCount = 0; for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { if ( i->get()->isKeyValid( key ) ) ++jobCount; if ( i->get()->getImageLayerOptions().lodBlending() == true ) out_hasLodBlendedLayers = true; } if ( mapf.elevationLayers().size() > 0 ) ++jobCount; // A thread job monitoring event: Threading::MultiEvent semaphore( jobCount ); // Start the image layer jobs: for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->isKeyValid(key) ) { ParallelTask<BuildColorLayer>* j = new ParallelTask<BuildColorLayer>( &semaphore ); j->init( key, layer, mapInfo, _terrainOptions, repo ); j->setPriority( -(float)key.getLevelOfDetail() ); _service->add( j ); } } // If we have elevation layers, start an elevation job as well. Otherwise just create an // empty one while we're waiting for the images to load. if ( mapf.elevationLayers().size() > 0 ) { ParallelTask<BuildElevLayer>* ej = new ParallelTask<BuildElevLayer>( &semaphore ); ej->init( key, mapf, _terrainOptions, repo ); ej->setPriority( -(float)key.getLevelOfDetail() ); _service->add( ej ); } else { BuildElevLayer build; build.init( key, mapf, _terrainOptions, repo ); build.execute(); } // Wait for all the jobs to finish. semaphore.wait(); } // Fetch the image data serially: else { // gather all the image layers serially. for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); //if ( layer->isKeyValid(key) ) // Wrong. no guarantee key is in the same profile. if ( layer->getEnabled() ) { BuildColorLayer build; build.init( key, layer, mapInfo, _terrainOptions, repo ); build.execute(); if ( layer->getImageLayerOptions().lodBlending() == true ) out_hasLodBlendedLayers = true; } } // make an elevation layer. BuildElevLayer build; build.init( key, mapf, _terrainOptions, repo ); build.execute(); } // Bail out now if there's no data to be had. if ( repo._colorLayers.size() == 0 && !repo._elevLayer.getHFLayer() ) { return; } // OK we are making a tile, so if there's no heightfield yet, make an empty one. if ( !repo._elevLayer.getHFLayer() ) { osg::HeightField* hf = HeightFieldUtils::createReferenceHeightField( key.getExtent(), 8, 8 ); //osg::HeightField* hf = key.getProfile()->getVerticalSRS()->createReferenceHeightField( key.getExtent(), 8, 8 ); osgTerrain::HeightFieldLayer* hfLayer = new osgTerrain::HeightFieldLayer( hf ); hfLayer->setLocator( GeoLocator::createForKey(key, mapInfo) ); repo._elevLayer = CustomElevLayer( hfLayer, true ); } // Now, if there are any color layers that did not get built, create them with an empty // image so the shaders have something to draw. osg::ref_ptr<osg::Image> emptyImage; osgTerrain::Locator* locator = repo._elevLayer.getHFLayer()->getLocator(); for( ImageLayerVector::const_iterator i = mapf.imageLayers().begin(); i != mapf.imageLayers().end(); ++i ) { ImageLayer* layer = i->get(); if ( layer->getEnabled() && !layer->isKeyValid(key) ) { if ( !emptyImage.valid() ) emptyImage = ImageUtils::createEmptyImage(); repo.add( CustomColorLayer( layer, emptyImage.get(), locator, key.getLevelOfDetail(), key, true ) ); } } //osg::Vec3dArray* maskBounds = 0L; //osgEarth::MaskLayer* mask = mapf.getTerrainMaskLayer(); //if (mask) // maskBounds = mask->getOrCreateBoundary(); // Ready to create the actual tile. AssembleTile assemble; assemble.init( key, mapInfo, _terrainOptions, repo, mapf.terrainMaskLayers() ); assemble.execute(); if (!out_hasRealData) { // Check the results and see if we have any real data. for( ColorLayersByUID::const_iterator i = repo._colorLayers.begin(); i != repo._colorLayers.end(); ++i ) { if ( !i->second.isFallbackData() ) { out_hasRealData = true; break; } } } if ( !out_hasRealData && !repo._elevLayer.isFallbackData() ) { out_hasRealData = true; } out_tile = assemble._tile; }