522 lines
18 KiB
Plaintext
522 lines
18 KiB
Plaintext
|
/* -*-c++-*- */
|
||
|
/* osgEarth - Geospatial SDK for OpenSceneGraph
|
||
|
* Copyright 2020 Pelican Mapping
|
||
|
* http://osgearth.org
|
||
|
*
|
||
|
* osgEarth is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU Lesser General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||
|
*/
|
||
|
#ifndef OSGEARTH_URI
|
||
|
#define OSGEARTH_URI 1
|
||
|
|
||
|
#include <osgEarth/Common>
|
||
|
#include <osgEarth/Containers>
|
||
|
#include <osgEarth/IOTypes>
|
||
|
#include <osg/Image>
|
||
|
#include <osg/Node>
|
||
|
#include <osgDB/Options>
|
||
|
#include <osgDB/ReaderWriter>
|
||
|
|
||
|
namespace osgUtil
|
||
|
{
|
||
|
class IncrementalCompileOperation;
|
||
|
}
|
||
|
|
||
|
namespace osgEarth
|
||
|
{
|
||
|
class URI;
|
||
|
class ProgressCallback;
|
||
|
|
||
|
using namespace Threading;
|
||
|
|
||
|
/**
|
||
|
* Context for resolving relative URIs.
|
||
|
*
|
||
|
* This object provides "context" for a relative URI. In other words, it provides
|
||
|
* all of the information the system needs to resolve it to an absolute location from
|
||
|
* which OSG can load data.
|
||
|
*
|
||
|
* The "referrer" is the location of an object that "points" to the object in the
|
||
|
* corresponding URI. The location conveyed by the URI will be relative to the location of
|
||
|
* its referrer. For example, a referrer of "http://server/folder/hello.xml" applied
|
||
|
* to the URI "there.jpg" will resolve to "http://server/folder/there.jpg". NOTE that referrer
|
||
|
* it not itself a location (like a folder); rather it's the object that referred to the URI
|
||
|
* being contextualized.
|
||
|
*/
|
||
|
class OSGEARTH_EXPORT URIContext
|
||
|
{
|
||
|
public:
|
||
|
/** Creates an empty context. */
|
||
|
URIContext();
|
||
|
|
||
|
/** Creates a context that specifies a referring object. */
|
||
|
URIContext(const std::string& referrer);
|
||
|
|
||
|
/** Copy constructor. */
|
||
|
URIContext(const URIContext& rhs);
|
||
|
|
||
|
/** dtor */
|
||
|
virtual ~URIContext() { }
|
||
|
|
||
|
/** Serializes this context to an Options structure. This is useful when passing context information
|
||
|
to an osgDB::ReaderWriter that takes a stream as input -- the stream is anonymous, therefore this
|
||
|
is the preferred way to communicate the context information. */
|
||
|
void store( osgDB::Options* options );
|
||
|
|
||
|
/** Creates a context from the serialized version in an Options structure (see above) */
|
||
|
URIContext( const osgDB::Options* options );
|
||
|
|
||
|
/** Returns the referring object. */
|
||
|
const std::string& referrer() const { return _referrer; }
|
||
|
|
||
|
/** True if the context is empty */
|
||
|
bool empty() const { return _referrer.empty(); }
|
||
|
|
||
|
/** Parents the input context with the current object, placing the subContext's information
|
||
|
under it. Used to re-parent relative locations with a higher-level referrer object. */
|
||
|
URIContext add( const URIContext& subContext ) const;
|
||
|
|
||
|
/** Returns a new context with the sub path appended to the current referrer path. */
|
||
|
URIContext add( const std::string& subPath ) const;
|
||
|
|
||
|
/** creates a string suitable for passing to an osgDB::ReaderWriter implementation */
|
||
|
std::string getOSGPath( const std::string& target ) const;
|
||
|
|
||
|
//! Add a header name/value pair to use when requesting a remote URL
|
||
|
void addHeader(const std::string& name, const std::string& value);
|
||
|
|
||
|
//! Headers in this URIContext
|
||
|
const Headers& getHeaders() const;
|
||
|
|
||
|
//! Headers in this URIContext
|
||
|
Headers& getHeaders();
|
||
|
|
||
|
|
||
|
private:
|
||
|
friend class URI;
|
||
|
std::string _referrer;
|
||
|
Headers _headers;
|
||
|
};
|
||
|
|
||
|
//--------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Stream container for reading a URI directly from a stream
|
||
|
*/
|
||
|
class OSGEARTH_EXPORT URIStream
|
||
|
{
|
||
|
public:
|
||
|
URIStream(const URI& uri, std::ios_base::openmode mode =std::ios_base::in);
|
||
|
|
||
|
virtual ~URIStream();
|
||
|
|
||
|
public:
|
||
|
// auto-cast to istream
|
||
|
operator std::istream& ();
|
||
|
|
||
|
protected:
|
||
|
friend class URI;
|
||
|
std::istream* _instream;
|
||
|
};
|
||
|
|
||
|
//--------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Represents the location of a resource, providing the raw (original, possibly
|
||
|
* relative) and absolute forms.
|
||
|
*
|
||
|
* URI is serializable and may be used in an earth file, like in the following
|
||
|
* example. Note that in earth files, the URI is actually called "url"; this is
|
||
|
* simply because of an old convention and we wish to avoid breaking backwards
|
||
|
* compatibility.
|
||
|
*
|
||
|
* <url>../path/relative/to/earth/file</url>
|
||
|
*
|
||
|
* Note also that a relative URI will be relative to the location of the
|
||
|
* parent resource (usually the earth file itself).
|
||
|
*
|
||
|
* You can also specify osgDB plugin options; for example:
|
||
|
*
|
||
|
* <url options_string="JPEG_QUALITY 60">../path/to/image.jpg</url>
|
||
|
*
|
||
|
* Of course, options are particular to OSG plugins, so please consult the
|
||
|
* code for your plugin for more information.
|
||
|
*/
|
||
|
class OSGEARTH_EXPORT URI
|
||
|
{
|
||
|
public:
|
||
|
/**
|
||
|
* Constructs an empty (and invalid) URI.
|
||
|
*/
|
||
|
URI();
|
||
|
|
||
|
/**
|
||
|
* Constructs a new URI from a location (typically an absolute url)
|
||
|
*/
|
||
|
URI( const std::string& location );
|
||
|
|
||
|
/**
|
||
|
* Constructs a new URI from a location and an existing context.
|
||
|
*/
|
||
|
URI( const std::string& location, const URIContext& context );
|
||
|
|
||
|
/**
|
||
|
* Constructs a new URI from a string.
|
||
|
*/
|
||
|
URI( const char* location );
|
||
|
|
||
|
/** dtor */
|
||
|
virtual ~URI() { }
|
||
|
|
||
|
public:
|
||
|
|
||
|
/** The base (possibly relative) location string. */
|
||
|
const std::string& base() const { return _baseURI; }
|
||
|
|
||
|
/** The fully qualified location string. */
|
||
|
const std::string& full() const { return _fullURI; }
|
||
|
|
||
|
/** The dereference operator also returns the fully qualified URI, since it's a common operation. */
|
||
|
const std::string& operator * () const { return _fullURI; }
|
||
|
|
||
|
/** Context with which this URI was created. */
|
||
|
const URIContext& context() const { return _context; }
|
||
|
|
||
|
/** Whether the URI is empty */
|
||
|
bool empty() const { return _baseURI.empty(); }
|
||
|
|
||
|
/** Whether the object of the URI is cacheable. */
|
||
|
bool isRemote() const;
|
||
|
|
||
|
/** Returns a copy of this URI with the suffix appended */
|
||
|
URI append( const std::string& suffix ) const;
|
||
|
|
||
|
/** String used for keying the cache */
|
||
|
const std::string& cacheKey() const { return !_cacheKey.empty() ? _cacheKey : _fullURI; }
|
||
|
|
||
|
/** osgDB::Options option string (plugin options) */
|
||
|
optional<std::string>& optionString() { return _optionString; }
|
||
|
const optional<std::string>& optionString() const { return _optionString; }
|
||
|
|
||
|
public:
|
||
|
|
||
|
/** Sets a cache key. By default the cache key is the full URI, but you can override that. */
|
||
|
void setCacheKey( const std::string& key ) { _cacheKey = key; }
|
||
|
|
||
|
public: // read methods return a ReadResult object
|
||
|
|
||
|
ReadResult readObject(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const;
|
||
|
|
||
|
ReadResult readImage(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const;
|
||
|
|
||
|
ReadResult readNode(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const;
|
||
|
|
||
|
ReadResult readString(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const;
|
||
|
|
||
|
ReadResult readConfig(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const;
|
||
|
|
||
|
public: // get methods call the read* methods, then just return the raw data.
|
||
|
|
||
|
osg::Object* getObject(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const { return readObject(dbOptions, progress).releaseObject(); }
|
||
|
|
||
|
osg::Image* getImage(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const { return readImage(dbOptions, progress).releaseImage(); }
|
||
|
|
||
|
osg::Node* getNode(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const { return readNode(dbOptions, progress).releaseNode(); }
|
||
|
|
||
|
std::string getString(
|
||
|
const osgDB::Options* dbOptions =0L,
|
||
|
ProgressCallback* progress =0L ) const { return readString(dbOptions, progress).getString(); }
|
||
|
|
||
|
public:
|
||
|
|
||
|
bool operator < ( const URI& rhs ) const { return _fullURI < rhs._fullURI; }
|
||
|
|
||
|
bool operator == ( const URI& rhs ) const { return _fullURI.compare(rhs._fullURI) == 0; }
|
||
|
|
||
|
bool operator != ( const URI& rhs ) const { return _fullURI.compare(rhs._fullURI) != 0; }
|
||
|
|
||
|
public:
|
||
|
/** Copier */
|
||
|
URI( const URI& rhs );
|
||
|
|
||
|
|
||
|
public: // config methods
|
||
|
|
||
|
Config getConfig() const;
|
||
|
|
||
|
void mergeConfig(const Config& conf);
|
||
|
|
||
|
public: // Static convenience methods
|
||
|
|
||
|
/** Encodes text to URL safe test. Escapes special charaters */
|
||
|
inline static std::string urlEncode(const std::string &value);
|
||
|
|
||
|
protected:
|
||
|
std::string _baseURI;
|
||
|
std::string _fullURI;
|
||
|
std::string _cacheKey;
|
||
|
URIContext _context;
|
||
|
optional<std::string> _optionString;
|
||
|
|
||
|
void ctorCacheKey();
|
||
|
};
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
namespace std {
|
||
|
// std::hash specialization for URI
|
||
|
template<> struct hash<osgEarth::URI> {
|
||
|
inline size_t operator()(const osgEarth::URI& value) const {
|
||
|
return hash<string>()(value.full());
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
namespace osgEarth
|
||
|
{
|
||
|
/**
|
||
|
* A lookup table that maps URI references to other URI references. This
|
||
|
* is used as an optional resource mapping table. (See KML's ResourceMap
|
||
|
* for usage example)
|
||
|
*
|
||
|
* WARNING: osgDB::Options will only store a raw pointer to the class, so
|
||
|
* make sure the scope of the osgDB::Options does not exceed the scope of
|
||
|
* the embedded alias map!
|
||
|
*/
|
||
|
class OSGEARTH_EXPORT URIAliasMap
|
||
|
{
|
||
|
public:
|
||
|
/**
|
||
|
* Inserts a key-value pair into the map.
|
||
|
*/
|
||
|
void insert( const std::string& key, const std::string& value );
|
||
|
|
||
|
/**
|
||
|
* Resolves the input address into a URI string.
|
||
|
*/
|
||
|
std::string resolve(const std::string& input, const URIContext& context) const;
|
||
|
|
||
|
/**
|
||
|
* True if there are no mappings
|
||
|
*/
|
||
|
bool empty() const { return _map.empty(); }
|
||
|
|
||
|
/**
|
||
|
* Clears out the map.
|
||
|
*/
|
||
|
void clear() { _map.clear(); }
|
||
|
|
||
|
/**
|
||
|
* Loads an alias map from an Options.
|
||
|
*/
|
||
|
static URIAliasMap* from( const osgDB::Options* options ) {
|
||
|
return options ? const_cast<URIAliasMap*>(static_cast<const URIAliasMap*>(options->getPluginData("osgEarth::URIAliasMap"))) : 0L;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stores an alias map in an Options
|
||
|
*/
|
||
|
void apply( osgDB::Options* options ) {
|
||
|
if ( options ) options->setPluginData("osgEarth::URIAliasMap", this);
|
||
|
}
|
||
|
|
||
|
protected:
|
||
|
std::map<std::string,std::string> _map;
|
||
|
friend class Config;
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
* A custom read callback (that you can set in an osgDB::Options) that will
|
||
|
* attempt to resolve pathnames using a URI alias map.
|
||
|
*/
|
||
|
class OSGEARTH_EXPORT URIAliasMapReadCallback : public osgDB::ReadFileCallback
|
||
|
{
|
||
|
public:
|
||
|
URIAliasMapReadCallback( const URIAliasMap& aliasMap, const URIContext& context );
|
||
|
|
||
|
virtual osgDB::ReaderWriter::ReadResult openArchive(const std::string& filename, osgDB::ReaderWriter::ArchiveStatus status, unsigned int indexBlockSizeHint, const osgDB::Options* useObjectCache);
|
||
|
virtual osgDB::ReaderWriter::ReadResult readObject(const std::string& filename, const osgDB::Options* options);
|
||
|
virtual osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options);
|
||
|
virtual osgDB::ReaderWriter::ReadResult readHeightField(const std::string& filename, const osgDB::Options* options);
|
||
|
virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& filename, const osgDB::Options* options);
|
||
|
virtual osgDB::ReaderWriter::ReadResult readShader(const std::string& filename, const osgDB::Options* options);
|
||
|
|
||
|
protected:
|
||
|
const URIAliasMap& _aliasMap;
|
||
|
URIContext _context;
|
||
|
};
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* A URI result cache that you can embed in an osgDB::Options, and if found,
|
||
|
* URI will attempt to use it.
|
||
|
*
|
||
|
* WARNING: osgDB::Options will only store a raw pointer to the class, so
|
||
|
* make sure the scope of the osgDB::Options does not exceed the scope of
|
||
|
* the embedded cache!
|
||
|
*/
|
||
|
struct /*header-only*/ URIResultCache : public LRUCache<URI, ReadResult>
|
||
|
{
|
||
|
URIResultCache( bool threadsafe =true )
|
||
|
: LRUCache<URI,ReadResult>( threadsafe ) { }
|
||
|
|
||
|
static URIResultCache* from(const osgDB::Options* options) {
|
||
|
return options ? const_cast<URIResultCache*>(static_cast<const URIResultCache*>(options->getPluginData("osgEarth::URIResultCache"))) : 0L;
|
||
|
}
|
||
|
|
||
|
void apply( osgDB::Options* options ) {
|
||
|
if ( options ) options->setPluginData("osgEarth::URIResultCache", this);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* You can install a post-read callback in a osgDB::Options and the URI
|
||
|
* class till invoke it on a ReadResult before returning.
|
||
|
*/
|
||
|
class /*header-only*/ URIPostReadCallback : public osg::Referenced
|
||
|
{
|
||
|
public:
|
||
|
URIPostReadCallback() { }
|
||
|
virtual ~URIPostReadCallback() { }
|
||
|
|
||
|
virtual void operator()( ReadResult& result ) =0;
|
||
|
|
||
|
public:
|
||
|
void apply(osgDB::Options* options) {
|
||
|
if ( options ) options->setPluginData("osgEarth::URIPostReadCallback", this);
|
||
|
}
|
||
|
|
||
|
static URIPostReadCallback* from(const osgDB::Options* options) {
|
||
|
return options ? const_cast<URIPostReadCallback*>(static_cast<const URIPostReadCallback*>(options->getPluginData("osgEarth::URIPostReadCallback"))) : 0L;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
// Config specialization for URI:
|
||
|
|
||
|
template<> inline
|
||
|
void Config::set<URI>( const std::string& key, const optional<URI>& opt ) {
|
||
|
if ( opt.isSet() ) {
|
||
|
remove(key);
|
||
|
set( key, opt->getConfig() );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template<> inline
|
||
|
bool Config::get<URI>( const std::string& key, optional<URI>& output ) const {
|
||
|
if ( hasChild(key) ) {
|
||
|
const Config& uriconf = child(key);
|
||
|
if (!uriconf.value().empty()) {
|
||
|
URI uri(uriconf.value(), uriconf.referrer());
|
||
|
uri.mergeConfig(uriconf);
|
||
|
output = uri;
|
||
|
return true;
|
||
|
}
|
||
|
else return false;
|
||
|
}
|
||
|
else return false;
|
||
|
}
|
||
|
|
||
|
// Config specialization for URIAliasMap
|
||
|
|
||
|
template <> inline
|
||
|
void Config::set<URIAliasMap>( const std::string& key, const optional<URIAliasMap>& map ) {
|
||
|
remove( key );
|
||
|
if ( map.isSet() ) {
|
||
|
Config conf( key );
|
||
|
for( std::map<std::string,std::string>::const_iterator i = map->_map.begin(); i != map->_map.end(); ++i ) {
|
||
|
Config alias( "alias" );
|
||
|
alias.add( "source", i->first );
|
||
|
alias.add( "target", i->second );
|
||
|
conf.add( alias );
|
||
|
}
|
||
|
set(conf);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
template <> inline
|
||
|
bool Config::get<URIAliasMap>( const std::string& key, optional<URIAliasMap>& output ) const {
|
||
|
Config alias = child(key);
|
||
|
if ( !alias.empty() ) {
|
||
|
for( ConfigSet::const_iterator i = alias.children().begin(); i != alias.children().end(); ++i ) {
|
||
|
std::string source = i->value("source");
|
||
|
std::string target = i->value("target");
|
||
|
if ( !source.empty() && !target.empty() )
|
||
|
output.mutable_value().insert( source, target );
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
// Ref.: https://stackoverflow.com/questions/154536/encode-decode-urls-in-c
|
||
|
std::string URI::urlEncode(const std::string &value)
|
||
|
{
|
||
|
std::ostringstream escaped;
|
||
|
escaped.fill('0');
|
||
|
escaped << std::hex;
|
||
|
|
||
|
for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) {
|
||
|
std::string::value_type c = (*i);
|
||
|
|
||
|
// Keep alphanumeric and other accepted characters intact
|
||
|
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
|
||
|
escaped << c;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Any other characters are percent-encoded
|
||
|
escaped << std::uppercase;
|
||
|
escaped << '%' << std::setw(2) << int((unsigned char)c);
|
||
|
escaped << std::nouppercase;
|
||
|
}
|
||
|
|
||
|
return escaped.str();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif // OSGEARTH_URI
|