1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3  * Copyright 2019 Pelican Mapping
4  * http://osgearth.org
5  *
6  * osgEarth is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU Lesser General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>
18  */
19 #include "EarthFileSerializer"
20 #include <osgEarth/FileUtils>
21 #include <osgEarth/Extension>
22 #include <osgEarth/StringUtils>
23 #include <osgEarth/FileUtils>
24 #include <osgEarth/URI>
25 #include <osgDB/FileUtils>
26 #include <osgDB/FileNameUtils>
27 #include <stdio.h>
28 #include <ctype.h>
29 
30 using namespace osgEarth_osgearth;
31 using namespace osgEarth;
32 
33 #undef  LC
34 #define LC "[EarthSerializer2] "
35 
36 static const char * const PATH_SEPARATORS = "/\\";
37 static unsigned int PATH_SEPARATORS_LEN = 2;
38 
39 namespace
40 {
41 
42 	class PathIterator
43     {
44 		public:
45 			PathIterator(const std::string & v);
valid() const46 			bool valid() const { return start!=end; }
47 			PathIterator & operator++();
48 			std::string operator*();
49 
50 		protected:
51 			std::string::const_iterator end;     ///< End of path string
52 			std::string::const_iterator start;   ///< Points to the first char of an element, or ==end() if no more
53 			std::string::const_iterator stop;    ///< Points to the separator after 'start', or ==end()
54 
55 			/// Iterate until 'it' points to something different from a separator
56 			std::string::const_iterator skipSeparators(std::string::const_iterator it);
57 			std::string::const_iterator next(std::string::const_iterator it);
58     };
59 
PathIterator(const std::string & v)60 	PathIterator::PathIterator(const std::string & v) : end(v.end()), start(v.begin()), stop(v.begin()) { operator++(); }
61 
operator ++()62 	PathIterator & PathIterator::operator++()
63 	{
64 		if (!valid()) return *this;
65 			start = skipSeparators(stop);
66 		if (start != end) stop = next(start);
67 		return *this;
68 	}
operator *()69 	std::string PathIterator::operator*()
70 	{
71 		if (!valid()) return std::string();
72 		return std::string(start, stop);
73 	}
74 
skipSeparators(std::string::const_iterator it)75 	std::string::const_iterator PathIterator::skipSeparators(std::string::const_iterator it)
76 	{
77 		for (; it!=end && std::find_first_of(it, it+1, PATH_SEPARATORS, PATH_SEPARATORS+PATH_SEPARATORS_LEN) != it+1; ++it) {}
78 		return it;
79 	}
80 
next(std::string::const_iterator it)81 	std::string::const_iterator PathIterator::next(std::string::const_iterator it)
82 	{
83 		return std::find_first_of(it, end, PATH_SEPARATORS, PATH_SEPARATORS+PATH_SEPARATORS_LEN);
84 	}
85 
86     // Config tags that we handle specially (versus just letting the plugin mechanism
87     // take take of them)
isReservedWord(const std::string & k)88     bool isReservedWord(const std::string& k)
89     {
90         return
91             k == "options" ||
92             //k == "image" ||
93             k == "elevation" ||
94             k == "heightfield" ||
95             //k == "model" ||
96             //k == "mask" ||
97             k == "external" ||
98             k == "extensions" ||
99             k == "libraries";
100     }
101 
102     /**
103      * Looks at each key in a Config and tries to match that key to a shared library name;
104      * loads the shared library associated with the name. This will "pre-load" all the DLLs
105      * associated with extensions in the earth file even if they weren't linked.
106      *
107      * Will also pre-load any expressly indicated shared libraries in the "libraries" element.
108      */
preloadExtensionLibs(const Config & conf)109     void preloadExtensionLibs(const Config& conf)
110     {
111         for(ConfigSet::const_iterator i = conf.children().begin(); i != conf.children().end(); ++i)
112         {
113             const std::string& name = i->key();
114 
115             if ( isReservedWord(name) )
116                 continue;
117 
118             if ( !name.empty() )
119             {
120                 // Load the extension library if necessary.
121                 std::string libName = osgDB::Registry::instance()->createLibraryNameForExtension("osgearth_" + name);
122                 osgDB::Registry::LoadStatus status = osgDB::Registry::instance()->loadLibrary(libName);
123                 if ( status == osgDB::Registry::LOADED )
124                 {
125                     OE_INFO << LC << "Loaded extension lib \"" << libName << "\"\n";
126                 }
127                 else
128                 {
129                     // If it failed to load, try loading an extension from an osgEarth library with the same name.
130                     // Capitalize the name of the extension,.
131                     std::string capName = name;
132                     capName[0] = ::toupper(capName[0]);
133                     std::stringstream buf;
134                     buf << "osgEarth" << capName;
135                     libName = osgDB::Registry::instance()->createLibraryNameForNodeKit(buf.str());
136                     status = osgDB::Registry::instance()->loadLibrary(libName);
137                     if (status == osgDB::Registry::LOADED)
138                     {
139                         OE_INFO << LC << "Loaded extension lib \"" << libName << "\"\n";
140                     }
141                 }
142             }
143         }
144 
145         // Preload any libraries
146         Config libraries = conf.child("libraries");
147         if (!libraries.value().empty())
148         {
149             StringTokenizer izer( ";" );
150             StringVector libs;
151             izer.tokenize( libraries.value(), libs );
152             for (StringVector::iterator itr = libs.begin(); itr != libs.end(); ++itr)
153             {
154                 std::string lib = *itr;
155                 trim2(lib);
156                 std::string libName = osgDB::Registry::instance()->createLibraryNameForNodeKit(lib);
157                 osgDB::Registry::LoadStatus status = osgDB::Registry::instance()->loadLibrary(libName);
158                 if (status == osgDB::Registry::LOADED)
159                 {
160                     OE_INFO << LC << "Loaded library \"" << libName << "\"\n";
161                 }
162                 else
163                 {
164                     OE_WARN << LC << "Failed to load library \"" << libName << "\"\n";
165                 }
166             }
167         }
168     }
169 
170     /**
171      * Visits a Config hierarchy and rewrites relative pathnames to be relative to a new referrer.
172      */
173     struct RewritePaths
174     {
175         bool        _rewriteAbsolutePaths;
176         std::string _newReferrerAbsPath;
177         std::string _newReferrerFolder;
178 
RewritePaths__anon00987e4f0111::RewritePaths179         RewritePaths(const std::string& referrer)
180         {
181             _rewriteAbsolutePaths = false;
182             _newReferrerAbsPath = osgDB::convertFileNameToUnixStyle( osgDB::getRealPath(referrer) );
183             _newReferrerFolder  = osgDB::getFilePath( _newReferrerAbsPath );
184         }
185 
186         /** Whether to make absolute paths into relative paths if possible */
setRewriteAbsolutePaths__anon00987e4f0111::RewritePaths187         void setRewriteAbsolutePaths(bool value)
188         {
189             _rewriteAbsolutePaths = value;
190         }
191 
isLocation__anon00987e4f0111::RewritePaths192         bool isLocation(const Config& input) const
193         {
194             if ( input.value().empty() )
195                 return false;
196 
197             if ( input.referrer().empty() )
198                 return false;
199 
200             return
201                 input.key() == "url"      ||
202                 input.key() == "uri"      ||
203                 input.key() == "href"     ||
204                 input.key() == "filename" ||
205                 input.key() == "file"     ||
206                 input.key() == "pathname" ||
207                 input.key() == "path";
208         }
209 
apply__anon00987e4f0111::RewritePaths210         void apply(Config& input)
211         {
212             if ( isLocation(input) )
213             {
214                 // resolve the absolute path of the input:
215                 URI inputURI( input.value(), URIContext(input.referrer()) );
216 
217                 std::string newValue = resolve(inputURI);
218                 if ( newValue != input.value() )
219                 {
220                     input.setValue(newValue);
221                     input.setReferrer( _newReferrerAbsPath );
222                 }
223 
224                 if ( !input.externalRef().empty() )
225                 {
226                     URI xrefURI( input.externalRef(), URIContext(input.referrer()) );
227                     std::string newXRef = resolve(xrefURI);
228                     if ( newXRef != input.externalRef() )
229                     {
230                         input.setExternalRef( newXRef );
231                         input.setReferrer( _newReferrerAbsPath );
232                     }
233                 }
234             }
235 
236             for(ConfigSet::iterator i = input.children().begin(); i != input.children().end(); ++i)
237             {
238                 apply( *i );
239             }
240         }
241 
resolve__anon00987e4f0111::RewritePaths242         std::string resolve(const URI& inputURI )
243         {
244             std::string inputAbsPath = osgDB::convertFileNameToUnixStyle( inputURI.full() );
245 
246             // if the abs paths have different roots, no resolution; use the input.
247             if (osgDB::getPathRoot(inputAbsPath) != osgDB::getPathRoot(_newReferrerFolder))
248             {
249                 return inputURI.full();
250             }
251 
252             // see whether the file exists (this is how we verify that it's actually a path)
253             //if ( osgDB::fileExists(inputAbsPath) )
254             {
255                 if ( !osgDB::isAbsolutePath(inputURI.base()) || _rewriteAbsolutePaths )
256                 {
257                     std::string inputNewRelPath = getPathRelative( _newReferrerFolder, inputAbsPath );
258 
259                     //OE_DEBUG << LC << "\n"
260                     //    "   Rewriting \"" << input.value() << "\" as \"" << inputNewRelPath << "\"\n"
261                     //    "   Absolute = " << inputAbsPath << "\n"
262                     //    "   ReferrerFolder = " << _newReferrerFolder << "\n";
263 
264                     return inputNewRelPath;
265                 }
266             }
267 
268             return inputURI.base();
269         }
270 
getPathRelative__anon00987e4f0111::RewritePaths271 		std::string getPathRelative(const std::string& from, const std::string& to)
272 		{
273 			// This implementation is not 100% robust, and should be replaced with C++0x "std::path" as soon as possible.
274 
275 			// Definition: an "element" is a part between slashes. Ex: "/a/b" has two elements ("a" and "b").
276 			// Algorithm:
277 			// 1. If paths are neither both absolute nor both relative, then we cannot do anything (we need to make them absolute, but need additional info on how to make it). Return.
278 			// 2. If both paths are absolute and root isn't the same (for Windows only, as roots are of the type "C:", "D:"), then the operation is impossible. Return.
279 			// 3. Iterate over two paths elements until elements are equal
280 			// 4. For each remaining element in "from", add ".." to result
281 			// 5. For each remaining element in "to", add this element to result
282 
283 			// 1 & 2
284 			const std::string root = osgDB::getPathRoot(from);
285 			if (root != osgDB::getPathRoot(to)) {
286 				OSG_INFO << "Cannot relativise paths. From=" << from << ", To=" << to << ". Returning 'to' unchanged." << std::endl;
287 				//return to;
288 				return osgDB::getSimpleFileName(to);
289 			}
290 
291 			// 3
292 			PathIterator itFrom(from), itTo(to);
293 			// Iterators may point to Windows roots. As we tested they are equal, there is no need to ++itFrom and ++itTo.
294 			// However, if we got an Unix root, we must add it to the result.
295 			// std::string res(root == "/" ? "/" : "");
296 			// Since result is a relative path, even in unix, no need to add / to the result first.
297 			std::string res = "";
298 			for(; itFrom.valid() && itTo.valid() && *itFrom==*itTo; ++itFrom, ++itTo) {}
299 
300 			// 4
301 			for(; itFrom.valid(); ++itFrom) res += "../";
302 
303 			// 5
304 			for(; itTo.valid(); ++itTo) res += *itTo + "/";
305 
306 			// Remove trailing slash before returning
307 			if (!res.empty() && std::find_first_of(res.rbegin(), res.rbegin()+1, PATH_SEPARATORS, PATH_SEPARATORS+PATH_SEPARATORS_LEN) != res.rbegin()+1)
308 			{
309 				return res.substr(0, res.length()-1);
310 			}
311 			return res;
312 			}
313     };
314 }
315 
316 //............................................................................
317 
318 namespace
319 {
320     // support for "special" extension names (convenience and backwards compat)
createSpecialExtension(const Config & conf)321     Extension* createSpecialExtension(const Config& conf)
322     {
323         // special support for the default sky extension:
324         if (conf.key() == "sky" && !conf.hasValue("driver"))
325             return Extension::create("sky_simple", conf);
326 
327         if (conf.key() == "ocean" && !conf.hasValue("driver"))
328             return Extension::create("ocean_simple", conf);
329 
330         return 0L;
331     }
332 
addLayer(const Config & conf,Map * map)333     bool addLayer(const Config& conf, Map* map)
334     {
335         Layer* layer = Layer::create(conf);
336         if (layer)
337         {
338             map->addLayer(layer);
339         }
340         return layer != 0L;
341     }
342 
loadExtension(const Config & conf)343     Extension* loadExtension(const Config& conf)
344     {
345         std::string name = conf.key();
346         Extension* extension = Extension::create( conf.key(), conf );
347         if ( !extension )
348         {
349             name = conf.key() + "_" + conf.value("driver");
350             extension = Extension::create(name, conf);
351 
352             if (!extension)
353                 extension = createSpecialExtension(conf);
354         }
355 
356         if (!extension)
357         {
358             OE_INFO << LC << "Failed to find an extension for \"" << name << "\"\n";
359         }
360 
361         return extension;
362     }
363 
reportErrors(const Map * map)364     void reportErrors(const Map* map)
365     {
366         for (unsigned i = 0; i < map->getNumLayers(); ++i)
367         {
368             const Layer* layer = map->getLayerAt(i);
369             if (layer->getStatus().isError())
370             {
371                 OE_WARN << LC << layer->getTypeName() << " \"" << layer->getName() << "\" : " << layer->getStatus().toString() << std::endl;
372             }
373         }
374     }
375 }
376 
EarthFileSerializer2()377 EarthFileSerializer2::EarthFileSerializer2() :
378 _rewritePaths        ( true ),
379 _rewriteAbsolutePaths( false )
380 {
381     // nop
382 }
383 
384 
385 osg::Node*
deserialize(const Config & conf,const std::string & referrer) const386 EarthFileSerializer2::deserialize( const Config& conf, const std::string& referrer ) const
387 {
388     // First, pre-load any extension DLLs.
389     preloadExtensionLibs(conf);
390     preloadExtensionLibs(conf.child("extensions"));
391     preloadExtensionLibs(conf.child("external"));
392 
393     MapOptions mapOptions( conf.child( "options" ) );
394 
395     // legacy: check for name/type in top-level attrs:
396     if ( conf.hasValue( "name" ) || conf.hasValue( "type" ) )
397     {
398         Config legacy;
399         if ( conf.hasValue("name") ) legacy.add( "name", conf.value("name") );
400         if ( conf.hasValue("type") ) legacy.add( "type", conf.value("type") );
401         mapOptions.mergeConfig( legacy );
402     }
403 
404     osg::ref_ptr< Map > map = new Map( mapOptions );
405 
406     // Start a batch update of the map:
407     map->beginUpdate();
408 
409     // Read all the elevation layers in FIRST so other layers can access them for things like clamping.
410     // TODO: revisit this since we should really be listening for elevation data changes and
411     // re-clamping based on that..
412     for(ConfigSet::const_iterator i = conf.children().begin(); i != conf.children().end(); ++i)
413     {
414         // for backwards compatibility:
415         if (i->key() == "heightfield")
416         {
417             Config temp = *i;
418             temp.key() = "elevation";
419             addLayer(temp, map.get());
420         }
421 
422         else if ( i->key() == "elevation" ) // || i->key() == "heightfield" )
423         {
424             addLayer(*i, map.get());
425         }
426     }
427 
428     Config externalConfig;
429     std::vector<osg::ref_ptr<Extension> > extensions;
430 
431     // Read the layers in LAST (otherwise they will not benefit from the cache/profile configuration)
432     for(ConfigSet::const_iterator i = conf.children().begin(); i != conf.children().end(); ++i)
433     {
434         if (i->key() == "options" || i->key() == "name" || i->key() == "type" || i->key() == "version")
435         {
436             // nop - handled earlier
437         }
438 
439         else if ( i->key() == "external" || i->key() == "extensions" )
440         {
441             externalConfig = *i;
442 
443             for(ConfigSet::const_iterator e = i->children().begin(); e != i->children().end(); ++e)
444             {
445                 Extension* extension = loadExtension(*e);
446                 if (extension)
447                     extensions.push_back(extension);
448             }
449         }
450 
451         else if ( !isReservedWord(i->key()) ) // plugins/extensions.
452         {
453             // try to add as a plugin Layer first:
454             bool addedLayer = addLayer(*i, map.get());
455 
456             // failing that, try to load as an extension:
457             if ( !addedLayer )
458             {
459                 Extension* extension = loadExtension(*i);
460                 if (extension)
461                     extensions.push_back(extension);
462             }
463         }
464     }
465 
466     // Complete the batch update of the map
467     map->endUpdate();
468 
469     // If any errors occurred, report them now.
470     reportErrors(map.get());
471 
472     // Yes, MapOptions and MapNodeOptions share the same Config node. Weird but true.
473     MapNodeOptions mapNodeOptions( conf.child("options") );
474 
475     // Create a map node.
476     osg::ref_ptr<MapNode> mapNode = new MapNode( map.get(), mapNodeOptions );
477 
478     // Apply the external conf if there is one.
479     if (!externalConfig.empty())
480     {
481         mapNode->externalConfig() = externalConfig;
482     }
483 
484     // Install the extensions
485     for (unsigned i = 0; i < extensions.size(); ++i)
486     {
487         mapNode->addExtension(extensions[i].get());
488     }
489 
490     // return the topmost parent of the mapnode. It's possible that
491     // an extension added parents!
492     osg::Node* top = mapNode.release();
493 
494     while( top->getNumParents() > 0 )
495         top = top->getParent(0);
496 
497     return top;
498 }
499 
500 
501 Config
serialize(const MapNode * input,const std::string & referrer) const502 EarthFileSerializer2::serialize(const MapNode* input, const std::string& referrer) const
503 {
504     Config mapConf("map");
505 
506     if (input && input->getMap())
507     {
508         mapConf = input->getConfig();
509 
510         // Re-write pathnames in the Config so they are relative to the new referrer.
511         if ( _rewritePaths && !referrer.empty() )
512         {
513             RewritePaths rewritePaths( referrer );
514             rewritePaths.setRewriteAbsolutePaths( _rewriteAbsolutePaths );
515             rewritePaths.apply( mapConf );
516         }
517     }
518 
519     return mapConf;
520 }
521