osg::Node* ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const { osg::Node* node = 0L; ReadResult r = uri.readNode( dbOptions ); if ( r.succeeded() ) { node = r.releaseNode(); OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")" << std::endl; osgUtil::Optimizer o; o.optimize( node, o.DEFAULT_OPTIMIZATIONS | o.INDEX_MESH | o.VERTEX_PRETRANSFORM | o.VERTEX_POSTTRANSFORM ); } else // failing that, fall back on the old encoding format.. { StringVector tok; StringTokenizer( *uri, tok, "()" ); if (tok.size() >= 2) { node = createNodeFromURI( URI(tok[1]), dbOptions ); } } return node; }
osg::Node* IconResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const { osg::Node* node = 0L; ReadResult r = uri.readImage( dbOptions ); if ( r.succeeded() ) { OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")" << std::endl; if ( r.getImage() ) { node = buildIconModel( r.releaseImage() ); } } else // failing that, fall back on the old encoding format.. { StringVector tok; StringTokenizer( *uri, tok, "()" ); if (tok.size() >= 2) return createNodeFromURI( URI(tok[1]), dbOptions ); } return node; }
osg::HeightField* TileSource::createHeightField(const TileKey& key, HeightFieldOperation* prepOp, ProgressCallback* progress ) { if ( _status != STATUS_OK ) return 0L; // Try to get it from the memcache first: if (_memCache.valid()) { ReadResult r = _memCache->getOrCreateDefaultBin()->readObject( key.str() ); if ( r.succeeded() ) return r.release<osg::HeightField>(); } osg::ref_ptr<osg::HeightField> newHF = createHeightField( key, progress ); if ( prepOp ) (*prepOp)( newHF ); if ( newHF.valid() && _memCache.valid() ) { _memCache->getOrCreateDefaultBin()->write( key.str(), newHF.get() ); } //TODO: why not just newHF.release()? -gw return newHF.valid() ? new osg::HeightField( *newHF.get() ) : 0L; }
osg::Image* TileSource::createImage(const TileKey& key, ImageOperation* prepOp, ProgressCallback* progress ) { if ( _status != STATUS_OK ) return 0L; // Try to get it from the memcache fist if (_memCache.valid()) { ReadResult r = _memCache->getOrCreateDefaultBin()->readImage( key.str() ); if ( r.succeeded() ) return r.releaseImage(); } osg::ref_ptr<osg::Image> newImage = createImage(key, progress); if ( prepOp ) (*prepOp)( newImage ); if ( newImage.valid() && _memCache.valid() ) { // cache it to the memory cache. _memCache->getOrCreateDefaultBin()->write( key.str(), newImage.get() ); } return newImage.release(); }
// read the WKT geometry from a URL, then parse into a Geometry. Symbology::Geometry* parseGeometryUrl( const std::string& geomUrl, const osgDB::Options* dbOptions ) { ReadResult r = URI(geomUrl).readString( dbOptions ); if ( r.succeeded() ) { Config conf( "geometry", r.getString() ); return parseGeometry( conf ); } return 0L; }
TileService* TileServiceReader::read( const std::string &location, const osgDB::ReaderWriter::Options* options ) { TileService *tileService = NULL; ReadResult r = URI(location).readString( options ); if ( r.succeeded() ) { std::istringstream buf( r.getString() ); tileService = read( buf ); } return tileService; }
ReadResult LevelDBCacheBin::readString(const std::string& key) { ReadResult r = readObject(key); if ( r.succeeded() ) { if ( r.get<StringObject>() ) return r; else return ReadResult(); } else { return r; } }
XmlDocument* XmlDocument::load( const URI& uri, const osgDB::Options* dbOptions ) { XmlDocument* result = 0L; ReadResult r = uri.readString( dbOptions ); if ( r.succeeded() ) { std::stringstream buf( r.getString() ); result = load( buf ); if ( result ) result->_sourceURI = uri; } return result; }
MapNode* MapNode::load(osg::ArgumentParser& args) { for( int i=1; i<args.argc(); ++i ) { if ( args[i] && endsWith(args[i], ".earth") ) { ReadResult r = URI(args[i]).readNode(); if ( r.succeeded() ) { return r.release<MapNode>(); } } } return 0L; }
MapNode* MapNode::load(osg::ArgumentParser& args, const MapNodeOptions& defaults) { for( int i=1; i<args.argc(); ++i ) { if ( args[i] && endsWith(args[i], ".earth") ) { osg::ref_ptr<osgDB::Options> dbo = new osgDB::Options(); std::string optionsJSON = defaults.getConfig().toJSON(); dbo->setPluginStringData( "osgEarth.defaultOptions", optionsJSON ); ReadResult r = URI(args[i]).readNode( dbo.get() ); if ( r.succeeded() ) { return r.release<MapNode>(); } } } return 0L; }
osg::Node* ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const { osg::Node* node = 0L; ReadResult r = uri.getNode( dbOptions ); if ( r.succeeded() ) { node = r.releaseNode(); } else // failing that, fall back on the old encoding format.. { StringVector tok; StringTokenizer( *uri, tok, "()" ); if (tok.size() >= 2) return createNodeFromURI( URI(tok[1]), dbOptions ); } return node; }
WMSCapabilities* WMSCapabilitiesReader::read( const std::string &location, const osgDB::ReaderWriter::Options* options ) { WMSCapabilities *caps = NULL; if ( osgDB::containsServerAddress( location ) ) { ReadResult rr = URI(location).readString( options ); if ( rr.succeeded() ) { std::istringstream in( rr.getString() ); caps = read( in ); } } else { if ((osgDB::fileExists(location)) && (osgDB::fileType(location) == osgDB::REGULAR_FILE)) { std::ifstream in( location.c_str() ); caps = read( in ); } } return caps; }
osg::Node* ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const { osg::ref_ptr< osgDB::Options > options = dbOptions ? new osgDB::Options( *dbOptions ) : 0; // Explicitly cache images so that models that share images will only load one copy. options->setObjectCacheHint( osgDB::Options::CACHE_IMAGES ); osg::Node* node = 0L; ReadResult r = uri.readNode( options.get() ); if ( r.succeeded() ) { node = r.releaseNode(); OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")" << std::endl; osgUtil::Optimizer o; o.optimize( node, o.DEFAULT_OPTIMIZATIONS | o.INDEX_MESH | o.VERTEX_PRETRANSFORM | o.VERTEX_POSTTRANSFORM ); } else // failing that, fall back on the old encoding format.. { StringVector tok; StringTokenizer( *uri, tok, "()" ); if (tok.size() >= 2) { node = createNodeFromURI( URI(tok[1]), options.get() ); } } return node; }
Status initialize( const osgDB::Options* dbOptions ) { osg::ref_ptr<osgDB::Options> localOptions = Registry::instance()->cloneOrCreateOptions(dbOptions); CachePolicy::NO_CACHE.apply(localOptions.get()); if ( !getProfile() ) { return Status::Error( "An explicit profile definition is required by the OSG driver." ); } osg::ref_ptr<osg::Image> image; if ( !_options.url()->empty() ) { ReadResult r = _options.url()->readImage( localOptions.get() ); if ( r.succeeded() ) { image = r.getImage(); } } if ( !image.valid() ) { return Status::Error( Stringify() << "Faild to load data from \"" << _options.url()->full() << "\"" ); } // calculate and store the maximum LOD for which to return data if ( image.valid() ) { if ( _options.maxDataLevel().isSet() ) { _maxDataLevel = *_options.maxDataLevel(); } else { int minSpan = osg::minimum( image->s(), image->t() ); int tileSize = _options.tileSize().value(); _maxDataLevel = (int)LOG2((minSpan/tileSize)+1); //OE_NOTICE << "[osgEarth::OSG driver] minSpan=" << minSpan << ", _tileSize=" << tileSize << ", maxDataLevel = " << _maxDataLevel << std::endl; } getDataExtents().push_back( DataExtent(getProfile()->getExtent(), 0, _maxDataLevel) ); bool computeAlpha = (_options.convertLuminanceToRGBA() == true && image->getPixelFormat() == GL_LUMINANCE) || (_options.addAlpha() == true && !ImageUtils::hasAlphaChannel( image.get() ) ); if ( computeAlpha ) { image = makeRGBAandComputeAlpha( image.get() ); } else if ( ImageUtils::hasAlphaChannel( image.get() )) { image = ImageUtils::convertToRGBA8( image.get() ); } else { image = ImageUtils::convertToRGB8( image.get() ); } _image = GeoImage( image.get(), getProfile()->getExtent() ); } _extension = osgDB::getFileExtension( _options.url()->full() ); return STATUS_OK; }
CacheBin* TerrainLayer::getCacheBin(const Profile* profile) { if ( !_openCalled ) { OE_WARN << LC << "Illegal- called getCacheBin() before layer is open.. did you call open()?\n"; return 0L; } CacheSettings* cacheSettings = getCacheSettings(); if (!cacheSettings) return 0L; if (cacheSettings->cachePolicy()->isCacheDisabled()) return 0L; CacheBin* bin = cacheSettings->getCacheBin(); if (!bin) return 0L; // does the metadata need initializing? std::string metaKey = getMetadataKey(profile); Threading::ScopedMutexLock lock(_mutex); CacheBinMetadataMap::iterator i = _cacheBinMetadata.find(metaKey); if (i == _cacheBinMetadata.end()) { //std::string cacheId = _runtimeOptions->cacheId().get(); // read the metadata record from the cache bin: ReadResult rr = bin->readString(metaKey, _readOptions.get()); osg::ref_ptr<CacheBinMetadata> meta; bool metadataOK = false; if (rr.succeeded()) { // Try to parse the metadata record: Config conf; conf.fromJSON(rr.getString()); meta = new CacheBinMetadata(conf); if (meta->isOK()) { metadataOK = true; // verify that the cache if compatible with the open tile source: if ( getTileSource() && getProfile() ) { //todo: check the profile too if ( meta->_sourceDriver.get() != getTileSource()->getOptions().getDriver() ) { OE_WARN << LC << "Layer \"" << getName() << "\" is requesting a \"" << getTileSource()->getOptions().getDriver() << "\" cache, but a \"" << meta->_sourceDriver.get() << "\" cache exists at the specified location. " << "The cache will ignored for this layer.\n"; cacheSettings->cachePolicy() = CachePolicy::NO_CACHE; return 0L; } } // if not, see if we're in cache-only mode and still need a profile: else if (cacheSettings->cachePolicy()->isCacheOnly() && !_profile.valid()) { // in cacheonly mode, create a profile from the first cache bin accessed // (they SHOULD all be the same...) setProfile( Profile::create(meta->_sourceProfile.get()) ); _tileSize = meta->_sourceTileSize.get(); } bin->setMetadata(meta.get()); } else { OE_WARN << LC << "Metadata appears to be corrupt.\n"; } } if (!metadataOK) { // cache metadata does not exist, so try to create it. if ( getProfile() ) { meta = new CacheBinMetadata(); // no existing metadata; create some. meta->_cacheBinId = _runtimeCacheId; meta->_sourceName = this->getName(); meta->_sourceTileSize = getTileSize(); meta->_sourceProfile = getProfile()->toProfileOptions(); meta->_cacheProfile = profile->toProfileOptions(); meta->_cacheCreateTime = DateTime().asTimeStamp(); meta->_dataExtents = getDataExtents(); if (getTileSource()) { meta->_sourceDriver = getTileSource()->getOptions().getDriver(); } // store it in the cache bin. std::string data = meta->getConfig().toJSON(false); osg::ref_ptr<StringObject> temp = new StringObject(data); bin->write(metaKey, temp.get(), _readOptions.get()); bin->setMetadata(meta.get()); } else if ( cacheSettings->cachePolicy()->isCacheOnly() ) { disable(Stringify() << "Failed to open a cache for layer " "because cache_only policy is in effect and bin [" << _runtimeCacheId << "] " "could not be located."); return 0L; } else { OE_WARN << LC << "Failed to create cache bin [" << _runtimeCacheId << "] " "because there is no valid profile." << std::endl; cacheSettings->cachePolicy() = CachePolicy::NO_CACHE; return 0L; } } // If we loaded a profile from the cache metadata, apply the overrides: applyProfileOverrides(); if (meta.valid()) { _cacheBinMetadata[metaKey] = meta.get(); OE_DEBUG << LC << "Established metadata for cache bin [" << _runtimeCacheId << "]" << std::endl; } } return bin; }
osg::Node* ModelResource::createNodeFromURI( const URI& uri, const osgDB::Options* dbOptions ) const { if (_status.isError()) return 0L; osg::ref_ptr< osgDB::Options > options = dbOptions ? new osgDB::Options( *dbOptions ) : 0L; // Explicitly cache images so that models that share images will only load one copy. // If the options struture doesn't contain an object cache, OSG will use the global // object cache stored in the Registry. Without this, models that share textures or // use an atlas will duplicate memory usage. // // I don't like having this here - it seems like it belongs elsewhere and the caller // should be passing it in. But it needs to be set, so keep for now. if ( options.valid() ) { options->setObjectCacheHint( osgDB::Options::CACHE_IMAGES ); } osg::Node* node = 0L; ReadResult r = uri.readNode( options.get() ); if ( r.succeeded() ) { node = r.releaseNode(); OE_INFO << LC << "Loaded " << uri.base() << "(from " << (r.isFromCache()? "cache" : "source") << ")" << std::endl; osgUtil::Optimizer o; o.optimize( node, o.REMOVE_REDUNDANT_NODES | o.COMBINE_ADJACENT_LODS | o.MERGE_GEOMETRY | o.MAKE_FAST_GEOMETRY | o.CHECK_GEOMETRY | o.SHARE_DUPLICATE_STATE | o.INDEX_MESH | o.VERTEX_PRETRANSFORM | o.VERTEX_POSTTRANSFORM ); // Disable automatic texture unref since resources can be shared/paged. SetUnRefPolicyToFalse visitor; node->accept( visitor ); } else // failing that, fall back on the old encoding format.. { StringVector tok; StringTokenizer( *uri, tok, "()" ); if (tok.size() >= 2) { node = createNodeFromURI( URI(tok[1]), options.get() ); } } if (node == 0L && _status.isOK()) { Threading::ScopedMutexLock lock(_mutex); if (_status.isOK()) { _status = Status::Error(Status::ServiceUnavailable, "Failed to load resource file"); } } return node; }
GeoImage ImageLayer::createImageInKeyProfile(const TileKey& key, ProgressCallback* progress) { GeoImage result; // 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; } OE_DEBUG << LC << "create image for \"" << key.str() << "\", ext= " << key.getExtent().toString() << std::endl; // Check the layer L2 cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); ReadResult result = bin->readObject(key.str(), 0); if ( result.succeeded() ) return GeoImage(static_cast<osg::Image*>(result.releaseObject()), key.getExtent()); //_memCache->dumpStats(key.getProfile()->getFullSignature()); } // 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() ); } } // 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 ); // Normalize the image if necessary if ( result.valid() ) { ImageUtils::normalizeImage( result.getImage() ); } // memory cache first: if ( result.valid() && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); bin->write(key.str(), 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() && cacheBin && getCachePolicy().isCacheWriteable() ) { if ( key.getExtent() != result.getExtent() ) { OE_INFO << LC << "WARNING! mismatched extents." << std::endl; } cacheBin->write( key.str(), result.getImage() ); } 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::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; }
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; }
GeoHeightField ElevationLayer::createHeightField(const TileKey& key, ProgressCallback* progress ) { GeoHeightField result; osg::ref_ptr<osg::HeightField> hf; // If the layer is disabled, bail out. if ( getEnabled() == false ) { return GeoHeightField::INVALID; } // Check the memory cache first if ( _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); ReadResult cacheResult = bin->readObject(key.str() ); if ( cacheResult.succeeded() ) { result = GeoHeightField( static_cast<osg::HeightField*>(cacheResult.releaseObject()), key.getExtent()); } //_memCache->dumpStats(key.getProfile()->getFullSignature()); } if ( !result.valid() ) { // See if there's a persistent cache. 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; } // 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 && getCachePolicy().isCacheReadable() ) { ReadResult r = cacheBin->readObject( key.str() ); if ( r.succeeded() ) { bool expired = getCachePolicy().isExpired(r.lastModifiedTime()); cachedHF = r.get<osg::HeightField>(); if ( cachedHF && validateHeightField(cachedHF) ) { if (!expired) { hf = cachedHF; fromCache = true; } } } } // if we're cache-only, but didn't get data from the cache, fail silently. if ( !hf.valid() && isCacheOnly() ) { return GeoHeightField::INVALID; } if ( !hf.valid() ) { // bad tilesource? fail if ( !getTileSource() || !getTileSource()->isOK() ) return GeoHeightField::INVALID; if ( !isKeyInRange(key) ) return GeoHeightField::INVALID; // build a HF from the TileSource. hf = createHeightFieldFromTileSource( key, progress ); // 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. } // memory cache first: if ( hf && _memCache.valid() ) { CacheBin* bin = _memCache->getOrCreateBin( key.getProfile()->getFullSignature() ); bin->write(key.str(), hf.get()); } // cache if necessary if ( hf && cacheBin && !fromCache && getCachePolicy().isCacheWriteable() ) { cacheBin->write( key.str(), hf ); } // 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 so we don't have to worry about it later 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(), key.getExtent() ); } } // post-processing: if ( result.valid() ) { if ( _runtimeOptions.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( result.getHeightField(), result.getExtent(), NO_DATA_VALUE, geoid ); } } 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; }
bool SplatExtension::connect(MapNode* mapNode) { if ( !mapNode ) { OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl; return false; } OE_INFO << LC << "Connecting to MapNode.\n"; if ( !_options.catalogURI().isSet() ) { OE_WARN << LC << "Illegal: catalog URI is required" << std::endl; return false; } if ( !_options.legendURI().isSet() ) { OE_WARN << LC << "Illegal: legend URI is required" << std::endl; return false; } if ( !_options.coverageLayerName().isSet() ) { OE_WARN << LC << "Illegal: coverage layer name is required" << std::endl; return false; } // Locate the coverage layer in the map. const Map* map = mapNode->getMap(); const ImageLayer* coverageLayer = map->getImageLayerByName( _options.coverageLayerName().get() ); if ( !coverageLayer ) { OE_WARN << LC << "Coverage layer \"" << _options.coverageLayerName().get() << "\" not found in map." << std::endl; return false; } // Read in the catalog. osg::ref_ptr<SplatCatalog> catalog = new SplatCatalog(); { ReadResult result = _options.catalogURI()->readString( _dbOptions.get() ); if ( result.succeeded() ) { Config conf; conf.setReferrer(_options.catalogURI()->full()); std::string json = result.getString(); conf.fromJSON( json ); catalog->fromConfig( conf ); OE_INFO << LC << "Catalog: " << catalog->getClasses().size() << " classes\n"; } else { OE_WARN << LC << "Failed to read catalog from \"" << _options.catalogURI()->full() << "\"\n"; return false; } } // Read in the legend. osg::ref_ptr<SplatCoverageLegend> legend = new SplatCoverageLegend(); { ReadResult result = _options.legendURI()->readString( _dbOptions.get() ); if ( result.succeeded() ) { Config conf; conf.setReferrer(_options.legendURI()->full()); conf.fromJSON( result.getString() ); legend->fromConfig( conf ); OE_INFO << LC << "Legend: " << legend->getPredicates().size() << " mappings \n"; } else { OE_WARN << LC << "Failed to read legend from \"" << _options.legendURI()->full() << "\"\n"; return false; } } // Terrain effect that implements splatting. _effect = new SplatTerrainEffect( catalog, legend, _dbOptions.get() ); // set the coverage layer (mandatory) _effect->setCoverageLayer( coverageLayer ); // set the render order (optional) if ( _options.drawAfterImageLayers() == true ) _effect->setRenderOrder( 1.0f ); // set the various rendering options. if ( _options.coverageWarp().isSet() ) _effect->getCoverageWarpUniform()->set( _options.coverageWarp().get() ); if ( _options.coverageBlur().isSet() ) _effect->getCoverageBlurUniform()->set( _options.coverageBlur().get() ); if ( _options.scaleLevelOffset().isSet() ) _effect->getScaleLevelOffsetUniform()->set( (float)_options.scaleLevelOffset().get() ); // add it to the terrain. mapNode->getTerrainEngine()->addEffect( _effect.get() ); if ( ::getenv("OSGEARTH_SPLAT_MODELS") ) { // TEMPORARY. This is just a quick hack to test the tile-based model splatter. // It finds any occurance of a model in the catalog and just installs that in // a single splatter. Doesn't support multiple models, classifications, or // anything interesting quite yet. //const SplatClassVector& classes = catalog->getClasses(); //for(SplatClassVector::const_iterator i = classes.begin(); i != classes.end(); ++i ) //{ osg::ref_ptr<osg::Node> model; //if ( i->_modelURI.isSet() ) //{ model = URI("../data/red_flag.osg").getNode(_dbOptions.get()); //model = i->_modelURI->getNode(_dbOptions.get()); if ( model.valid() ) { if ( !_modelSplatter.valid() ) { _modelSplatter = new ModelSplatter(); } _modelSplatter->setModel( model.get() ); //if ( i->_modelCount.isSet() ) // _modelSplatter->setNumInstances( i->_modelCount.get() ); //if ( i->_modelLevel.isSet() ) // _modelSplatter->setMinLOD( i->_modelLevel.get() ); } else { OE_WARN << LC << "Can't load the tree!\n"; } // } } // Install the model splatter if we made one. if ( _modelSplatter.valid() ) { mapNode->getTerrainEngine()->addTileNodeCallback( _modelSplatter.get() ); } return true; }
bool SplatExtension::connect(MapNode* mapNode) { if ( !mapNode ) { OE_WARN << LC << "Illegal: MapNode cannot be null." << std::endl; return false; } OE_INFO << LC << "Connecting to MapNode.\n"; if ( !_options.catalogURI().isSet() ) { OE_WARN << LC << "Illegal: catalog URI is required" << std::endl; return false; } if ( !_options.legendURI().isSet() ) { OE_WARN << LC << "Illegal: legend URI is required" << std::endl; return false; } if ( !_options.coverageLayerName().isSet() ) { OE_WARN << LC << "Illegal: coverage layer name is required" << std::endl; return false; } // Locate the coverage layer in the map. const Map* map = mapNode->getMap(); const ImageLayer* coverageLayer = map->getImageLayerByName( _options.coverageLayerName().get() ); if ( !coverageLayer ) { OE_WARN << LC << "Coverage layer \"" << _options.coverageLayerName().get() << "\" not found in map." << std::endl; return false; } // Read in the catalog. osg::ref_ptr<SplatCatalog> catalog = new SplatCatalog(); { ReadResult result = _options.catalogURI()->readString( _dbOptions.get() ); if ( result.succeeded() ) { Config conf; conf.setReferrer(_options.catalogURI()->full()); std::string json = result.getString(); conf.fromJSON( json ); catalog->fromConfig( conf ); OE_INFO << LC << "Catalog: " << catalog->getClasses().size() << " classes\n"; } else { OE_WARN << LC << "Failed to read catalog from \"" << _options.catalogURI()->full() << "\"\n"; return false; } } // Read in the legend. osg::ref_ptr<SplatCoverageLegend> legend = new SplatCoverageLegend(); { ReadResult result = _options.legendURI()->readString( _dbOptions.get() ); if ( result.succeeded() ) { Config conf; conf.setReferrer(_options.legendURI()->full()); conf.fromJSON( result.getString() ); legend->fromConfig( conf ); OE_INFO << LC << "Legend: " << legend->getPredicates().size() << " mappings \n"; } else { OE_WARN << LC << "Failed to read legend from \"" << _options.legendURI()->full() << "\"\n"; return false; } } // Install the splatter on the terrain engine. _effect = new SplatTerrainEffect( catalog, legend, _dbOptions.get() ); // set the coverage layer (mandatory) _effect->setCoverageLayer( coverageLayer ); // set the render order (optional) if ( _options.drawAfterImageLayers() == true ) _effect->setRenderOrder( 1.0f ); mapNode->getTerrainEngine()->addEffect( _effect.get() ); return true; }
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; }