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