1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
3 // Copyright (C) 2001-2019 German Aerospace Center (DLR) and others.
4 // This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v2.0
6 // which accompanies this distribution, and is available at
7 // http://www.eclipse.org/legal/epl-v20.html
8 // SPDX-License-Identifier: EPL-2.0
9 /****************************************************************************/
10 /// @file    NIImporter_OpenStreetMap.cpp
11 /// @author  Daniel Krajzewicz
12 /// @author  Jakob Erdmann
13 /// @author  Michael Behrisch
14 /// @author  Walter Bamberger
15 /// @author  Gregor Laemmel
16 /// @date    Mon, 14.04.2008
17 /// @version $Id$
18 ///
19 // Importer for networks stored in OpenStreetMap format
20 /****************************************************************************/
21 
22 
23 // ===========================================================================
24 // included modules
25 // ===========================================================================
26 #include <config.h>
27 #include <algorithm>
28 #include <set>
29 #include <functional>
30 #include <sstream>
31 #include <limits>
32 #include <utils/xml/SUMOSAXHandler.h>
33 #include <utils/common/UtilExceptions.h>
34 #include <utils/common/StringUtils.h>
35 #include <utils/common/ToString.h>
36 #include <utils/common/MsgHandler.h>
37 #include <utils/common/StringUtils.h>
38 #include <utils/common/StringTokenizer.h>
39 #include <netbuild/NBEdge.h>
40 #include <netbuild/NBEdgeCont.h>
41 #include <netbuild/NBNode.h>
42 #include <netbuild/NBNodeCont.h>
43 #include <netbuild/NBNetBuilder.h>
44 #include <netbuild/NBOwnTLDef.h>
45 #include <utils/xml/SUMOXMLDefinitions.h>
46 #include <utils/geom/GeoConvHelper.h>
47 #include <utils/geom/GeomConvHelper.h>
48 #include <utils/options/OptionsCont.h>
49 #include <utils/common/FileHelpers.h>
50 #include <utils/xml/XMLSubSys.h>
51 #include <netbuild/NBPTLine.h>
52 #include <netbuild/NBPTLineCont.h>
53 #include "NILoader.h"
54 #include "NIImporter_OpenStreetMap.h"
55 
56 //#define DEBUG_LAYER_ELEVATION
57 
58 // ---------------------------------------------------------------------------
59 // static members
60 // ---------------------------------------------------------------------------
61 const double NIImporter_OpenStreetMap::MAXSPEED_UNGIVEN = -1;
62 
63 const long long int NIImporter_OpenStreetMap::INVALID_ID = std::numeric_limits<long long int>::max();
64 
65 // ===========================================================================
66 // Private classes
67 // ===========================================================================
68 
69 /** @brief Functor which compares two Edges
70  */
71 class NIImporter_OpenStreetMap::CompareEdges {
72 public:
operator ()(const Edge * e1,const Edge * e2) const73     bool operator()(const Edge* e1, const Edge* e2) const {
74         if (e1->myHighWayType != e2->myHighWayType) {
75             return e1->myHighWayType > e2->myHighWayType;
76         }
77         if (e1->myNoLanes != e2->myNoLanes) {
78             return e1->myNoLanes > e2->myNoLanes;
79         }
80         if (e1->myNoLanesForward != e2->myNoLanesForward) {
81             return e1->myNoLanesForward > e2->myNoLanesForward;
82         }
83         if (e1->myMaxSpeed != e2->myMaxSpeed) {
84             return e1->myMaxSpeed > e2->myMaxSpeed;
85         }
86         if (e1->myIsOneWay != e2->myIsOneWay) {
87             return e1->myIsOneWay > e2->myIsOneWay;
88         }
89         return e1->myCurrentNodes > e2->myCurrentNodes;
90     }
91 };
92 
93 // ===========================================================================
94 // method definitions
95 // ===========================================================================
96 // ---------------------------------------------------------------------------
97 // static methods
98 // ---------------------------------------------------------------------------
99 const std::string NIImporter_OpenStreetMap::compoundTypeSeparator("|"); //clang-tidy says: "compundTypeSeparator with
100 // static storage duration my throw an exception that cannot be caught
101 
102 void
loadNetwork(const OptionsCont & oc,NBNetBuilder & nb)103 NIImporter_OpenStreetMap::loadNetwork(const OptionsCont& oc, NBNetBuilder& nb) {
104     NIImporter_OpenStreetMap importer;
105     importer.load(oc, nb);
106 }
107 
108 NIImporter_OpenStreetMap::NIImporter_OpenStreetMap() = default;
109 
~NIImporter_OpenStreetMap()110 NIImporter_OpenStreetMap::~NIImporter_OpenStreetMap() {
111     // delete nodes
112     for (auto myUniqueNode : myUniqueNodes) {
113         delete myUniqueNode;
114     }
115     // delete edges
116     for (auto& myEdge : myEdges) {
117         delete myEdge.second;
118     }
119     // delete platform shapes
120     for (auto& myPlatformShape : myPlatformShapes) {
121         delete myPlatformShape.second;
122     }
123 }
124 
125 void
load(const OptionsCont & oc,NBNetBuilder & nb)126 NIImporter_OpenStreetMap::load(const OptionsCont& oc, NBNetBuilder& nb) {
127     // check whether the option is set (properly)
128     if (!oc.isSet("osm-files")) {
129         return;
130     }
131     /* Parse file(s)
132      * Each file is parsed twice: first for nodes, second for edges. */
133     std::vector<std::string> files = oc.getStringVector("osm-files");
134     // load nodes, first
135     NodesHandler nodesHandler(myOSMNodes, myUniqueNodes, oc);
136     for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
137         // nodes
138         if (!FileHelpers::isReadable(*file)) {
139             WRITE_ERROR("Could not open osm-file '" + *file + "'.");
140             return;
141         }
142         nodesHandler.setFileName(*file);
143         PROGRESS_BEGIN_MESSAGE("Parsing nodes from osm-file '" + *file + "'");
144         if (!XMLSubSys::runParser(nodesHandler, *file)) {
145             return;
146         }
147         PROGRESS_DONE_MESSAGE();
148     }
149     // load edges, then
150     EdgesHandler edgesHandler(myOSMNodes, myEdges, myPlatformShapes);
151     for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
152         // edges
153         edgesHandler.setFileName(*file);
154         PROGRESS_BEGIN_MESSAGE("Parsing edges from osm-file '" + *file + "'");
155         XMLSubSys::runParser(edgesHandler, *file);
156         PROGRESS_DONE_MESSAGE();
157     }
158 
159     /* Remove duplicate edges with the same shape and attributes */
160     if (!oc.getBool("osm.skip-duplicates-check")) {
161         PROGRESS_BEGIN_MESSAGE("Removing duplicate edges");
162         if (myEdges.size() > 1) {
163             std::set<const Edge*, CompareEdges> dupsFinder;
164             for (auto it = myEdges.begin(); it != myEdges.end();) {
165                 if (dupsFinder.count(it->second) > 0) {
166                     WRITE_MESSAGE("Found duplicate edges. Removing " + toString(it->first));
167                     delete it->second;
168                     myEdges.erase(it++);
169                 } else {
170                     dupsFinder.insert(it->second);
171                     it++;
172                 }
173             }
174         }
175         PROGRESS_DONE_MESSAGE();
176     }
177 
178     /* Mark which nodes are used (by edges or traffic lights).
179      * This is necessary to detect which OpenStreetMap nodes are for
180      * geometry only */
181     std::map<long long int, int> nodeUsage;
182     // Mark which nodes are used by edges (begin and end)
183     for (std::map<long long int, Edge*>::const_iterator i = myEdges.begin(); i != myEdges.end(); ++i) {
184         Edge* e = (*i).second;
185         assert(e->myCurrentIsRoad);
186         for (std::vector<long long int>::const_iterator j = e->myCurrentNodes.begin();
187                 j != e->myCurrentNodes.end();
188                 ++j) {
189             if (nodeUsage.find(*j) == nodeUsage.end()) {
190                 nodeUsage[*j] = 0;
191             }
192             nodeUsage[*j] = nodeUsage[*j] + 1;
193         }
194     }
195     // Mark which nodes are used by traffic lights
196     for (std::map<long long int, NIOSMNode*>::const_iterator nodesIt = myOSMNodes.begin();
197             nodesIt != myOSMNodes.end();
198             ++nodesIt) {
199         if (nodesIt->second->tlsControlled || nodesIt->second->railwaySignal /* || nodesIt->second->railwayCrossing*/) {
200             // If the key is not found in the map, the value is automatically
201             // initialized with 0.
202             nodeUsage[nodesIt->first] += 1;
203         }
204     }
205 
206     /* Instantiate edges
207      * Only those nodes in the middle of an edge which are used by more than
208      * one edge are instantiated. Other nodes are considered as geometry nodes. */
209     NBNodeCont& nc = nb.getNodeCont();
210     NBTrafficLightLogicCont& tlsc = nb.getTLLogicCont();
211     for (auto& myEdge : myEdges) {
212         Edge* e = myEdge.second;
213         assert(e->myCurrentIsRoad);
214         if (e->myCurrentNodes.size() < 2) {
215             WRITE_WARNING("Discarding way '" + toString(e->id) + "' because it has only " +
216                           toString(e->myCurrentNodes.size()) + " node(s)");
217             continue;
218         }
219         // build nodes;
220         //  - the from- and to-nodes must be built in any case
221         //  - the in-between nodes are only built if more than one edge references them
222         NBNode* currentFrom = insertNodeChecking(*e->myCurrentNodes.begin(), nc, tlsc);
223         NBNode* last = insertNodeChecking(*(e->myCurrentNodes.end() - 1), nc, tlsc);
224         int running = 0;
225         std::vector<long long int> passed;
226         for (auto j = e->myCurrentNodes.begin(); j != e->myCurrentNodes.end(); ++j) {
227             passed.push_back(*j);
228             if (nodeUsage[*j] > 1 && j != e->myCurrentNodes.end() - 1 && j != e->myCurrentNodes.begin()) {
229                 NBNode* currentTo = insertNodeChecking(*j, nc, tlsc);
230                 running = insertEdge(e, running, currentFrom, currentTo, passed, nb);
231                 currentFrom = currentTo;
232                 passed.clear();
233                 passed.push_back(*j);
234             }
235         }
236         if (running == 0) {
237             running = -1;
238         }
239         insertEdge(e, running, currentFrom, last, passed, nb);
240     }
241 
242     const double layerElevation = oc.getFloat("osm.layer-elevation");
243     if (layerElevation > 0) {
244         reconstructLayerElevation(layerElevation, nb);
245     }
246 
247     //revise pt stops; remove stops on deleted edges
248     if (OptionsCont::getOptions().isSet("ptstop-output")) {
249         nb.getPTStopCont().cleanupDeleted(nb.getEdgeCont());
250     }
251 
252     // load relations (after edges are built since we want to apply
253     // turn-restrictions directly to NBEdges)
254     RelationHandler relationHandler(myOSMNodes, myEdges, &(nb.getPTStopCont()), myPlatformShapes,
255                                     &(nb.getPTLineCont()), oc);
256     for (std::vector<std::string>::const_iterator file = files.begin(); file != files.end(); ++file) {
257         // relations
258         relationHandler.setFileName(*file);
259         PROGRESS_BEGIN_MESSAGE("Parsing relations from osm-file '" + *file + "'");
260         XMLSubSys::runParser(relationHandler, *file);
261         PROGRESS_DONE_MESSAGE();
262     }
263 }
264 
265 NBNode*
insertNodeChecking(long long int id,NBNodeCont & nc,NBTrafficLightLogicCont & tlsc)266 NIImporter_OpenStreetMap::insertNodeChecking(long long int id, NBNodeCont& nc, NBTrafficLightLogicCont& tlsc) {
267     NBNode* node = nc.retrieve(toString(id));
268     if (node == nullptr) {
269         NIOSMNode* n = myOSMNodes.find(id)->second;
270         Position pos(n->lon, n->lat, n->ele);
271         if (!NBNetBuilder::transformCoordinate(pos, true)) {
272             WRITE_ERROR("Unable to project coordinates for junction '" + toString(id) + "'.");
273             return nullptr;
274         }
275         node = new NBNode(toString(id), pos);
276         if (!nc.insert(node)) {
277             WRITE_ERROR("Could not insert junction '" + toString(id) + "'.");
278             delete node;
279             return nullptr;
280         }
281         n->node = node;
282         if (n->railwayCrossing) {
283             node->reinit(pos, NODETYPE_RAIL_CROSSING);
284         } else if (n->railwaySignal) {
285             node->reinit(pos, NODETYPE_RAIL_SIGNAL);
286         } else if (n->tlsControlled) {
287             // ok, this node is a traffic light node where no other nodes
288             //  participate
289             // @note: The OSM-community has not settled on a schema for differentiating between fixed and actuated lights
290             TrafficLightType type = SUMOXMLDefinitions::TrafficLightTypes.get(
291                                         OptionsCont::getOptions().getString("tls.default-type"));
292             NBOwnTLDef* tlDef = new NBOwnTLDef(toString(id), node, 0, type);
293             if (!tlsc.insert(tlDef)) {
294                 // actually, nothing should fail here
295                 delete tlDef;
296                 throw ProcessError("Could not allocate tls '" + toString(id) + "'.");
297             }
298         }
299         if (n->railwayBufferStop) {
300             node->setParameter("buffer_stop", "true");
301         }
302     }
303     return node;
304 }
305 
306 int
insertEdge(Edge * e,int index,NBNode * from,NBNode * to,const std::vector<long long int> & passed,NBNetBuilder & nb)307 NIImporter_OpenStreetMap::insertEdge(Edge* e, int index, NBNode* from, NBNode* to,
308                                      const std::vector<long long int>& passed, NBNetBuilder& nb) {
309     NBNodeCont& nc = nb.getNodeCont();
310     NBEdgeCont& ec = nb.getEdgeCont();
311     NBTypeCont& tc = nb.getTypeCont();
312     NBPTStopCont& sc = nb.getPTStopCont();
313 
314     NBTrafficLightLogicCont& tlsc = nb.getTLLogicCont();
315     // patch the id
316     std::string id = toString(e->id);
317     if (from == nullptr || to == nullptr) {
318         WRITE_ERROR("Discarding edge '" + id + "' because the nodes could not be built.");
319         return index;
320     }
321     if (index >= 0) {
322         id = id + "#" + toString(index);
323     } else {
324         index = 0;
325     }
326     if (from == to) {
327         assert(passed.size() >= 2);
328         if (passed.size() == 2) {
329             WRITE_WARNING("Discarding edge '" + id + "' which connects two identical nodes without geometry.");
330             return index;
331         }
332         // in the special case of a looped way split again using passed
333         int intermediateIndex = (int) passed.size() / 2;
334         NBNode* intermediate = insertNodeChecking(passed[intermediateIndex], nc, tlsc);
335         std::vector<long long int> part1(passed.begin(), passed.begin() + intermediateIndex + 1);
336         std::vector<long long int> part2(passed.begin() + intermediateIndex, passed.end());
337         index = insertEdge(e, index, from, intermediate, part1, nb);
338         return insertEdge(e, index, intermediate, to, part2, nb);
339     }
340     const int newIndex = index + 1;
341 
342     // convert the shape
343     PositionVector shape;
344     for (long long i : passed) {
345         NIOSMNode* n = myOSMNodes.find(i)->second;
346 
347         if (n->ptStopPosition) {
348             NBPTStop* existingPtStop = sc.get(toString(n->id));
349             if (existingPtStop != nullptr) {
350                 existingPtStop->registerAdditionalEdge(toString(e->id), id);
351             } else {
352                 Position ptPos(n->lon, n->lat, n->ele);
353                 if (!NBNetBuilder::transformCoordinate(ptPos)) {
354                     WRITE_ERROR("Unable to project coordinates for node '" + toString(n->id) + "'.");
355                 }
356                 NBPTStop* ptStop = new NBPTStop(toString(n->id), ptPos, id, toString(e->id), n->ptStopLength, n->name,
357                                                 n->permissions);
358 
359                 sc.insert(ptStop);
360             }
361         }
362         Position pos(n->lon, n->lat, n->ele);
363         shape.push_back(pos);
364     }
365     if (!NBNetBuilder::transformCoordinates(shape)) {
366         WRITE_ERROR("Unable to project coordinates for edge '" + id + "'.");
367     }
368 //    shape.in
369 
370     std::string type = e->myHighWayType;
371     if (!tc.knows(type)) {
372         if (myUnusableTypes.count(type) > 0) {
373             return newIndex;
374         }
375         if (myKnownCompoundTypes.count(type) > 0) {
376             type = myKnownCompoundTypes[type];
377         } else {
378             // this edge has a type which does not yet exist in the TypeContainer
379             StringTokenizer tok = StringTokenizer(type, compoundTypeSeparator);
380             std::vector<std::string> types;
381             while (tok.hasNext()) {
382                 std::string t = tok.next();
383                 if (tc.knows(t)) {
384                     if (std::find(types.begin(), types.end(), t) == types.end()) {
385                         types.push_back(t);
386                     }
387                 } else if (tok.size() > 1) {
388                     WRITE_WARNING(
389                         "Discarding unknown compound '" + t + "' in type '" + type + "' (first occurence for edge '"
390                         + id
391                         + "').");
392                 }
393             }
394             if (types.empty()) {
395                 WRITE_WARNING("Discarding unusable type '" + type + "' (first occurence for edge '" + id + "').");
396                 myUnusableTypes.insert(type);
397                 return newIndex;
398             }
399 
400             const std::string newType = joinToString(types, "|");
401             if (tc.knows(newType)) {
402                 myKnownCompoundTypes[type] = newType;
403                 type = newType;
404             } else if (myKnownCompoundTypes.count(newType) > 0) {
405                 type = myKnownCompoundTypes[newType];
406             } else {
407                 // build a new type by merging all values
408                 int numLanes = 0;
409                 double maxSpeed = 0;
410                 int prio = 0;
411                 double width = NBEdge::UNSPECIFIED_WIDTH;
412                 double sidewalkWidth = NBEdge::UNSPECIFIED_WIDTH;
413                 double bikelaneWidth = NBEdge::UNSPECIFIED_WIDTH;
414                 bool defaultIsOneWay = false;
415                 SVCPermissions permissions = 0;
416                 bool discard = true;
417                 for (auto& type2 : types) {
418                     if (!tc.getShallBeDiscarded(type2)) {
419                         numLanes = MAX2(numLanes, tc.getNumLanes(type2));
420                         maxSpeed = MAX2(maxSpeed, tc.getSpeed(type2));
421                         prio = MAX2(prio, tc.getPriority(type2));
422                         defaultIsOneWay &= tc.getIsOneWay(type2);
423                         //std::cout << "merging component " << type2 << " into type " << newType << " allows=" << getVehicleClassNames(tc.getPermissions(type2)) << "\n";
424                         permissions |= tc.getPermissions(type2);
425                         width = MAX2(width, tc.getWidth(type2));
426                         sidewalkWidth = MAX2(sidewalkWidth, tc.getSidewalkWidth(type2));
427                         bikelaneWidth = MAX2(bikelaneWidth, tc.getBikeLaneWidth(type2));
428                         discard = false;
429                     }
430                 }
431                 if (width != NBEdge::UNSPECIFIED_WIDTH) {
432                     width = MAX2(width, SUMO_const_laneWidth);
433                 }
434                 // ensure pedestrians don't run into trains
435                 if (sidewalkWidth == NBEdge::UNSPECIFIED_WIDTH
436                         && (permissions & SVC_PEDESTRIAN) != 0
437                         && (permissions & SVC_RAIL_CLASSES) != 0) {
438                     //std::cout << "patching sidewalk for type '" << newType << "' which allows=" << getVehicleClassNames(permissions) << "\n";
439                     sidewalkWidth = OptionsCont::getOptions().getFloat("default.sidewalk-width");
440                 }
441 
442                 if (discard) {
443                     WRITE_WARNING(
444                         "Discarding compound type '" + newType + "' (first occurence for edge '" + id + "').");
445                     myUnusableTypes.insert(newType);
446                     return newIndex;
447                 }
448 
449                 WRITE_MESSAGE("Adding new type '" + type + "' (first occurence for edge '" + id + "').");
450                 tc.insert(newType, numLanes, maxSpeed, prio, permissions, width, defaultIsOneWay, sidewalkWidth,
451                           bikelaneWidth);
452                 for (auto& type3 : types) {
453                     if (!tc.getShallBeDiscarded(type3)) {
454                         tc.copyRestrictionsAndAttrs(type3, newType);
455                     }
456                 }
457                 myKnownCompoundTypes[type] = newType;
458                 type = newType;
459 
460             }
461 
462         }
463     }
464 
465     // otherwise it is not an edge and will be ignored
466     bool ok = true;
467     int numLanesForward = tc.getNumLanes(type);
468     int numLanesBackward = tc.getNumLanes(type);
469     double speed = tc.getSpeed(type);
470     bool defaultsToOneWay = tc.getIsOneWay(type);
471     SVCPermissions forwardPermissions = tc.getPermissions(type);
472     SVCPermissions backwardPermissions = tc.getPermissions(type);
473     const std::string streetName = isRailway(forwardPermissions) && e->ref != "" ? e->ref : e->streetName;
474     double forwardWidth = tc.getWidth(type);
475     double backwardWidth = tc.getWidth(type);
476     const bool addSidewalk = (tc.getSidewalkWidth(type) != NBEdge::UNSPECIFIED_WIDTH);
477     const bool addBikeLane = (tc.getBikeLaneWidth(type) != NBEdge::UNSPECIFIED_WIDTH);
478     // check directions
479     bool addForward = true;
480     bool addBackward = true;
481     if (e->myIsOneWay == "true" || e->myIsOneWay == "yes" || e->myIsOneWay == "1"
482             || (defaultsToOneWay && e->myIsOneWay != "no" && e->myIsOneWay != "false" && e->myIsOneWay != "0" &&
483                 e->getParameter("railway:preferred_direction", "") != "both")) {
484         addBackward = false;
485     }
486     if (e->myIsOneWay == "-1" || e->myIsOneWay == "reverse") {
487         // one-way in reversed direction of way
488         addForward = false;
489         addBackward = true;
490     }
491     if (!e->myIsOneWay.empty() && e->myIsOneWay != "false" && e->myIsOneWay != "no" && e->myIsOneWay != "true"
492             && e->myIsOneWay != "yes" && e->myIsOneWay != "-1" && e->myIsOneWay != "1" && e->myIsOneWay != "reverse") {
493         WRITE_WARNING("New value for oneway found: " + e->myIsOneWay);
494     }
495     // if we had been able to extract the number of lanes, override the highway type default
496     if (e->myNoLanes > 0) {
497         if (addForward && !addBackward) {
498             numLanesForward = e->myNoLanes;
499         } else if (!addForward && addBackward) {
500             numLanesBackward = e->myNoLanes;
501         } else {
502             if (e->myNoLanesForward > 0) {
503                 numLanesForward = e->myNoLanesForward;
504             } else if (e->myNoLanesForward < 0) {
505                 numLanesForward = e->myNoLanes + e->myNoLanesForward;
506             } else {
507                 numLanesForward = (int) std::ceil(e->myNoLanes / 2.0);
508             }
509             numLanesBackward = e->myNoLanes - numLanesForward;
510             // sometimes ways are tagged according to their physical width of a single
511             // lane but they are intended for traffic in both directions
512             numLanesForward = MAX2(1, numLanesForward);
513             numLanesBackward = MAX2(1, numLanesBackward);
514         }
515     } else if (e->myNoLanes == 0) {
516         WRITE_WARNING("Skipping edge '" + id + "' because it has zero lanes.");
517         ok = false;
518     }
519     // if we had been able to extract the maximum speed, override the type's default
520     if (e->myMaxSpeed != MAXSPEED_UNGIVEN) {
521         speed = (double)(e->myMaxSpeed / 3.6);
522     }
523     if (speed <= 0) {
524         WRITE_WARNING("Skipping edge '" + id + "' because it has speed " + toString(speed));
525         ok = false;
526     }
527     // deal with cycleways that run in the opposite direction of a one-way street
528     WayType cyclewayType = e->myCyclewayType; // make a copy because we do some temporary modifications
529     if (addBikeLane) {
530         if (!addForward && (cyclewayType & WAY_FORWARD) != 0) {
531             addForward = true;
532             forwardPermissions = SVC_BICYCLE;
533             forwardWidth = tc.getBikeLaneWidth(type);
534             numLanesForward = 1;
535             // do not add an additional cycle lane
536             cyclewayType = (WayType)(cyclewayType & ~WAY_FORWARD);  //clang tidy thinks "!WAY_FORWARD" is always false
537         }
538         if (!addBackward && (cyclewayType & WAY_BACKWARD) != 0) {
539             addBackward = true;
540             backwardPermissions = SVC_BICYCLE;
541             backwardWidth = tc.getBikeLaneWidth(type);
542             numLanesBackward = 1;
543             // do not add an additional cycle lane
544             cyclewayType = (WayType)(cyclewayType & ~WAY_BACKWARD); //clang tidy thinks "!WAY_BACKWARD" is always false
545         }
546     }
547     // deal with sidewalks that run in the opposite direction of a one-way street
548     WayType sidewalkType = e->mySidewalkType; // make a copy because we do some temporary modifications
549     if (addSidewalk) {
550         if (!addForward && (sidewalkType & WAY_FORWARD) != 0) {
551             addForward = true;
552             forwardPermissions = SVC_PEDESTRIAN;
553             forwardWidth = tc.getSidewalkWidth(type);
554             numLanesForward = 1;
555             // do not add an additional sidewalk
556             sidewalkType = (WayType)(sidewalkType & ~WAY_FORWARD);  //clang tidy thinks "!WAY_FORWARD" is always false
557         }
558         if (!addBackward && (sidewalkType & WAY_BACKWARD) != 0) {
559             addBackward = true;
560             backwardPermissions = SVC_PEDESTRIAN;
561             backwardWidth = tc.getSidewalkWidth(type);
562             numLanesBackward = 1;
563             // do not add an additional cycle lane
564             sidewalkType = (WayType)(sidewalkType & ~WAY_BACKWARD); //clang tidy thinks "!WAY_BACKWARD" is always false
565         }
566     }
567     // deal with busways that run in the opposite direction of a one-way street
568     if (!addForward && (e->myBuswayType & WAY_FORWARD) != 0) {
569         addForward = true;
570         forwardPermissions = SVC_BUS;
571         numLanesForward = 1;
572     }
573     if (!addBackward && (e->myBuswayType & WAY_BACKWARD) != 0) {
574         addBackward = true;
575         backwardPermissions = SVC_BUS;
576         numLanesBackward = 1;
577     }
578 
579     const std::string origID = OptionsCont::getOptions().getBool("output.original-names") ? toString(e->id) : "";
580     if (ok) {
581         LaneSpreadFunction lsf = (addBackward || OptionsCont::getOptions().getBool("osm.oneway-spread-right")) &&
582                                  e->getParameter("railway:preferred_direction", "") != "both" ? LANESPREAD_RIGHT : LANESPREAD_CENTER;
583 
584         id = StringUtils::escapeXML(id);
585         const std::string reverseID = "-" + id;
586 
587         if (addForward) {
588             assert(numLanesForward > 0);
589             NBEdge* nbe = new NBEdge(id, from, to, type, speed, numLanesForward, tc.getPriority(type),
590                                      forwardWidth, NBEdge::UNSPECIFIED_OFFSET, shape,
591                                      StringUtils::escapeXML(streetName), origID, lsf, true);
592             nbe->setPermissions(forwardPermissions);
593             if ((e->myBuswayType & WAY_FORWARD) != 0) {
594                 nbe->setPermissions(SVC_BUS, 0);
595             }
596             if (addBikeLane && (cyclewayType == WAY_UNKNOWN || (cyclewayType & WAY_FORWARD) != 0)) {
597                 nbe->addBikeLane(tc.getBikeLaneWidth(type));
598             } else if (nbe->getPermissions(0) == SVC_BUS) {
599                 // bikes drive on buslanes if no separate cycle lane is available
600                 nbe->setPermissions(SVC_BUS | SVC_BICYCLE, 0);
601             }
602             if (addSidewalk && (sidewalkType == WAY_UNKNOWN || (sidewalkType & WAY_FORWARD) != 0)) {
603                 nbe->addSidewalk(tc.getSidewalkWidth(type));
604             }
605             nbe->updateParameter(e->getParametersMap());
606             if (!ec.insert(nbe)) {
607                 delete nbe;
608                 throw ProcessError("Could not add edge '" + id + "'.");
609             }
610         }
611         if (addBackward) {
612             assert(numLanesBackward > 0);
613             NBEdge* nbe = new NBEdge(reverseID, to, from, type, speed, numLanesBackward, tc.getPriority(type),
614                                      backwardWidth, NBEdge::UNSPECIFIED_OFFSET, shape.reverse(),
615                                      StringUtils::escapeXML(streetName), origID, lsf, true);
616             nbe->setPermissions(backwardPermissions);
617             if ((e->myBuswayType & WAY_BACKWARD) != 0) {
618                 nbe->setPermissions(SVC_BUS, 0);
619             }
620             if (addBikeLane && (cyclewayType == WAY_UNKNOWN || (cyclewayType & WAY_BACKWARD) != 0)) {
621                 nbe->addBikeLane(tc.getBikeLaneWidth(type));
622             } else if (nbe->getPermissions(0) == SVC_BUS) {
623                 // bikes drive on buslanes if no separate cycle lane is available
624                 nbe->setPermissions(SVC_BUS | SVC_BICYCLE, 0);
625             }
626             if (addSidewalk && (sidewalkType == WAY_UNKNOWN || (sidewalkType & WAY_BACKWARD) != 0)) {
627                 nbe->addSidewalk(tc.getSidewalkWidth(type));
628             }
629             nbe->updateParameter(e->getParametersMap());
630             if (!ec.insert(nbe)) {
631                 delete nbe;
632                 throw ProcessError("Could not add edge '-" + id + "'.");
633             }
634         }
635         if ((e->myParkingType & PARKING_BOTH) != 0 && OptionsCont::getOptions().isSet("parking-output")) {
636             if ((e->myParkingType & PARKING_RIGHT) != 0) {
637                 if (addForward) {
638                     nb.getParkingCont().push_back(NBParking(id, id));
639                 } else {
640                     /// XXX parking area should be added on the left side of a reverse one-way street
641                     if ((e->myParkingType & PARKING_LEFT) == 0 && !addBackward) {
642                         /// put it on the wrong side (better than nothing)
643                         nb.getParkingCont().push_back(NBParking(reverseID, reverseID));
644                     }
645                 }
646             }
647             if ((e->myParkingType & PARKING_LEFT) != 0) {
648                 if (addBackward) {
649                     nb.getParkingCont().push_back(NBParking(reverseID, reverseID));
650                 } else {
651                     /// XXX parking area should be added on the left side of an one-way street
652                     if ((e->myParkingType & PARKING_RIGHT) == 0 && !addForward) {
653                         /// put it on the wrong side (better than nothing)
654                         nb.getParkingCont().push_back(NBParking(id, id));
655                     }
656                 }
657             }
658         }
659     }
660     return newIndex;
661 }
662 
663 // ---------------------------------------------------------------------------
664 // definitions of NIImporter_OpenStreetMap::NodesHandler-methods
665 // ---------------------------------------------------------------------------
NodesHandler(std::map<long long int,NIOSMNode * > & toFill,std::set<NIOSMNode *,CompareNodes> & uniqueNodes,const OptionsCont & oc)666 NIImporter_OpenStreetMap::NodesHandler::NodesHandler(std::map<long long int, NIOSMNode*>& toFill,
667         std::set<NIOSMNode*, CompareNodes>& uniqueNodes,
668         const OptionsCont& oc)
669 
670     :
671     SUMOSAXHandler("osm - file"),
672     myToFill(toFill),
673     myLastNodeID(-1),
674     myIsInValidNodeTag(false),
675     myHierarchyLevel(0),
676     myUniqueNodes(uniqueNodes),
677     myImportElevation(oc.getBool("osm.elevation")),
678     myOptionsCont(oc) {
679 }
680 
681 NIImporter_OpenStreetMap::NodesHandler::~NodesHandler() = default;
682 
683 void
myStartElement(int element,const SUMOSAXAttributes & attrs)684 NIImporter_OpenStreetMap::NodesHandler::myStartElement(int element, const SUMOSAXAttributes& attrs) {
685     ++myHierarchyLevel;
686     if (element == SUMO_TAG_NODE) {
687         bool ok = true;
688         if (myHierarchyLevel != 2) {
689             WRITE_ERROR("Node element on wrong XML hierarchy level (id='" + toString(attrs.get<long
690                         long
691                         int>(SUMO_ATTR_ID,
692                              nullptr, ok))
693                         + "', level='" + toString(myHierarchyLevel) + "').");
694             return;
695         }
696         const long long int id = attrs.get<long long int>(SUMO_ATTR_ID, nullptr, ok);
697         const std::string action = attrs.hasAttribute("action") ? attrs.getStringSecure("action", "") : "";
698         if (action == "delete" || !ok) {
699             return;
700         }
701         myLastNodeID = -1;
702         if (myToFill.find(id) == myToFill.end()) {
703             myLastNodeID = id;
704             // assume we are loading multiple files...
705             //  ... so we won't report duplicate nodes
706             bool ok2 = true;
707             double tlat, tlon;
708             std::istringstream lon(attrs.get<std::string>(SUMO_ATTR_LON, toString(id).c_str(), ok2));
709             if (!ok2) {
710                 return;
711             }
712             lon >> tlon;
713             if (lon.fail()) {
714                 WRITE_ERROR("Node's '" + toString(id) + "' lon information is not numeric.");
715                 return;
716             }
717             std::istringstream lat(attrs.get<std::string>(SUMO_ATTR_LAT, toString(id).c_str(), ok2));
718             if (!ok2) {
719                 return;
720             }
721             lat >> tlat;
722             if (lat.fail()) {
723                 WRITE_ERROR("Node's '" + toString(id) + "' lat information is not numeric.");
724                 return;
725             }
726             auto* toAdd = new NIOSMNode(id, tlon, tlat);
727             myIsInValidNodeTag = true;
728 
729             auto similarNode = myUniqueNodes.find(toAdd);
730             if (similarNode == myUniqueNodes.end()) {
731                 myUniqueNodes.insert(toAdd);
732             } else {
733                 delete toAdd;
734                 toAdd = *similarNode;
735                 WRITE_MESSAGE("Found duplicate nodes. Substituting " + toString(id) + " with " + toString(toAdd->id));
736             }
737             myToFill[id] = toAdd;
738         }
739     }
740     if (element == SUMO_TAG_TAG && myIsInValidNodeTag) {
741         if (myHierarchyLevel != 3) {
742             WRITE_ERROR("Tag element on wrong XML hierarchy level.");
743             return;
744         }
745         bool ok = true;
746         std::string key = attrs.get<std::string>(SUMO_ATTR_K, toString(myLastNodeID).c_str(), ok, false);
747         // we check whether the key is relevant (and we really need to transcode the value) to avoid hitting #1636
748         if (key == "highway" || key == "ele" || key == "crossing" || key == "railway" || key == "public_transport"
749                 || key == "name" || key == "train" || key == "bus" || key == "tram" || key == "light_rail" || key == "subway" || key == "station" || key == "noexit"
750                 || StringUtils::startsWith(key, "railway:signal")) {
751             std::string value = attrs.get<std::string>(SUMO_ATTR_V, toString(myLastNodeID).c_str(), ok, false);
752             if (key == "highway" && value.find("traffic_signal") != std::string::npos) {
753                 myToFill[myLastNodeID]->tlsControlled = true;
754             } else if (key == "crossing" && value.find("traffic_signals") != std::string::npos) {
755                 myToFill[myLastNodeID]->tlsControlled = true;
756             } else if ((key == "noexit" && value == "yes")
757                        || (key == "railway" && value == "buffer_stop")) {
758                 myToFill[myLastNodeID]->railwayBufferStop = true;
759             } else if (key == "railway" && value.find("crossing") != std::string::npos) {
760                 myToFill[myLastNodeID]->railwayCrossing = true;
761             } else if (StringUtils::startsWith(key, "railway:signal") && (
762                            value == "block" || value == "entry"  || value == "exit" || value == "intermediate")) {
763                 myToFill[myLastNodeID]->railwaySignal = true;
764             } else if ((key == "public_transport" && value == "stop_position") ||
765                        (key == "highway" && value == "bus_stop")) {
766                 myToFill[myLastNodeID]->ptStopPosition = true;
767                 if (myToFill[myLastNodeID]->ptStopLength == 0) {
768                     // default length
769                     myToFill[myLastNodeID]->ptStopLength = myOptionsCont.getFloat("osm.stop-output.length");
770                 }
771             } else if (key == "name") {
772                 myToFill[myLastNodeID]->name = value;
773             } else if (key == "train") {
774                 myToFill[myLastNodeID]->permissions = SVC_RAIL;
775                 myToFill[myLastNodeID]->ptStopLength = myOptionsCont.getFloat("osm.stop-output.length.train");
776             } else if (key == "subway" || key == "light_rail"
777                        || (key == "station" && (value == "subway" || value == "light_rail"))) {
778                 myToFill[myLastNodeID]->permissions = SVC_RAIL_URBAN;
779                 myToFill[myLastNodeID]->ptStopLength = myOptionsCont.getFloat("osm.stop-output.length.train");
780             } else if (key == "bus") {
781                 myToFill[myLastNodeID]->permissions = SVC_BUS;
782                 myToFill[myLastNodeID]->ptStopLength = myOptionsCont.getFloat("osm.stop-output.length.bus");
783             } else if (key == "tram") {
784                 myToFill[myLastNodeID]->permissions = SVC_TRAM;
785                 myToFill[myLastNodeID]->ptStopLength = myOptionsCont.getFloat("osm.stop-output.length.tram");
786             } else if (myImportElevation && key == "ele") {
787                 try {
788                     myToFill[myLastNodeID]->ele = StringUtils::toDouble(value);
789                 } catch (...) {
790                     WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in node '" +
791                                   toString(myLastNodeID) + "'.");
792                 }
793             }
794         }
795     }
796 }
797 
798 void
myEndElement(int element)799 NIImporter_OpenStreetMap::NodesHandler::myEndElement(int element) {
800     if (element == SUMO_TAG_NODE && myHierarchyLevel == 2) {
801         myLastNodeID = -1;
802         myIsInValidNodeTag = false;
803     }
804     --myHierarchyLevel;
805 }
806 
807 // ---------------------------------------------------------------------------
808 // definitions of NIImporter_OpenStreetMap::EdgesHandler-methods
809 // ---------------------------------------------------------------------------
EdgesHandler(const std::map<long long int,NIOSMNode * > & osmNodes,std::map<long long int,Edge * > & toFill,std::map<long long int,Edge * > & platformShapes)810 NIImporter_OpenStreetMap::EdgesHandler::EdgesHandler(
811     const std::map<long long int, NIOSMNode*>& osmNodes,
812     std::map<long long int, Edge*>& toFill, std::map<long long int, Edge*>& platformShapes):
813     SUMOSAXHandler("osm - file"),
814     myOSMNodes(osmNodes),
815     myEdgeMap(toFill),
816     myPlatformShapesMap(platformShapes) {
817     mySpeedMap["signals"] = MAXSPEED_UNGIVEN;
818     mySpeedMap["none"] = 142.; // Auswirkungen eines allgemeinen Tempolimits auf Autobahnen im Land Brandeburg (2007)
819     mySpeedMap["no"] = 142.;
820     mySpeedMap["walk"] = 5.;
821     mySpeedMap["DE:rural"] = 100.;
822     mySpeedMap["DE:urban"] = 50.;
823     mySpeedMap["DE:living_street"] = 10.;
824     myAllAttributes = OptionsCont::getOptions().getBool("osm.all-attributes");
825 
826 }
827 
828 NIImporter_OpenStreetMap::EdgesHandler::~EdgesHandler() = default;
829 
830 void
myStartElement(int element,const SUMOSAXAttributes & attrs)831 NIImporter_OpenStreetMap::EdgesHandler::myStartElement(int element,
832         const SUMOSAXAttributes& attrs) {
833     myParentElements.push_back(element);
834     // parse "way" elements
835     if (element == SUMO_TAG_WAY) {
836         bool ok = true;
837         const long long int id = attrs.get<long long int>(SUMO_ATTR_ID, nullptr, ok);
838         std::string action = attrs.hasAttribute("action") ? attrs.getStringSecure("action", "") : "";
839         if (action == "delete" || !ok) {
840             myCurrentEdge = nullptr;
841             return;
842         }
843         myCurrentEdge = new Edge(id);
844     }
845     // parse "nd" (node) elements
846     if (element == SUMO_TAG_ND && myCurrentEdge != nullptr) {
847         bool ok = true;
848         long long int ref = attrs.get<long long int>(SUMO_ATTR_REF, nullptr, ok);
849         if (ok) {
850             auto node = myOSMNodes.find(ref);
851             if (node == myOSMNodes.end()) {
852                 WRITE_WARNING("The referenced geometry information (ref='" + toString(ref) + "') is not known");
853                 return;
854             }
855 
856             ref = node->second->id; // node may have been substituted
857             if (myCurrentEdge->myCurrentNodes.empty() ||
858                     myCurrentEdge->myCurrentNodes.back() != ref) { // avoid consecutive duplicates
859                 myCurrentEdge->myCurrentNodes.push_back(ref);
860             }
861 
862         }
863     }
864     // parse values
865     if (element == SUMO_TAG_TAG && myParentElements.size() > 2
866             && myParentElements[myParentElements.size() - 2] == SUMO_TAG_WAY) {
867         if (myCurrentEdge == nullptr) {
868             return;
869         }
870         bool ok = true;
871         std::string key = attrs.get<std::string>(SUMO_ATTR_K, toString(myCurrentEdge->id).c_str(), ok, false);
872         if (key.size() > 8 && StringUtils::startsWith(key, "cycleway:")) {
873             // handle special cycleway keys
874             const std::string cyclewaySpec = key.substr(9);
875             key = "cycleway";
876             if (cyclewaySpec == "right") {
877                 myCurrentEdge->myCyclewayType = (WayType)(myCurrentEdge->myCyclewayType | WAY_FORWARD);
878             } else if (cyclewaySpec == "left") {
879                 myCurrentEdge->myCyclewayType = (WayType)(myCurrentEdge->myCyclewayType | WAY_BACKWARD);
880             } else if (cyclewaySpec == "both") {
881                 myCurrentEdge->myCyclewayType = (WayType)(myCurrentEdge->myCyclewayType | WAY_BOTH);
882             } else {
883                 key = "ignore";
884             }
885             if ((myCurrentEdge->myCyclewayType & WAY_BOTH) != 0) {
886                 // now we have some info on directionality
887                 myCurrentEdge->myCyclewayType = (WayType)(myCurrentEdge->myCyclewayType & ~WAY_UNKNOWN);
888             }
889         } else if (key.size() > 6 && StringUtils::startsWith(key, "busway:")) {
890             // handle special busway keys
891             const std::string buswaySpec = key.substr(7);
892             key = "busway";
893             if (buswaySpec == "right") {
894                 myCurrentEdge->myBuswayType = (WayType)(myCurrentEdge->myBuswayType | WAY_FORWARD);
895             } else if (buswaySpec == "left") {
896                 myCurrentEdge->myBuswayType = (WayType)(myCurrentEdge->myBuswayType | WAY_BACKWARD);
897             } else if (buswaySpec == "both") {
898                 myCurrentEdge->myBuswayType = (WayType)(myCurrentEdge->myBuswayType | WAY_BOTH);
899             } else {
900                 key = "ignore";
901             }
902         }
903         if (myAllAttributes && (key == "bridge" || key == "tunnel")) {
904             myCurrentEdge->setParameter(key, "true"); // could be differentiated further if necessary
905         }
906         // we check whether the key is relevant (and we really need to transcode the value) to avoid hitting #1636
907         if (!StringUtils::endsWith(key, "way") && !StringUtils::startsWith(key, "lanes")
908                 && key != "maxspeed" && key != "junction" && key != "name" && key != "tracks" && key != "layer"
909                 && key != "route"
910                 && key != "sidewalk"
911                 && key != "ref"
912                 && !StringUtils::startsWith(key, "parking")
913                 && key != "postal_code" && key != "railway:preferred_direction" && key != "public_transport") {
914             return;
915         }
916         std::string value = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentEdge->id).c_str(), ok, false);
917 
918         if ((key == "highway" && value != "platform") || key == "railway" || key == "waterway" || key == "cycleway"
919                 || key == "busway" || key == "route" || key == "sidewalk") {
920             myCurrentEdge->myCurrentIsRoad = true;
921             // special cycleway stuff
922             if (key == "cycleway") {
923                 if (value == "no") {
924                     return;
925                 }
926                 if (value == "opposite_track") {
927                     myCurrentEdge->myCyclewayType = WAY_BACKWARD;
928                 } else if (value == "opposite_lane") {
929                     myCurrentEdge->myCyclewayType = WAY_BACKWARD;
930                 }
931             }
932             // special sidewalk stuff
933             if (key == "sidewalk") {
934                 if (value == "no" || value == "none") {
935                     myCurrentEdge->mySidewalkType = WAY_NONE;
936                 } else if (value == "both") {
937                     myCurrentEdge->mySidewalkType = WAY_BOTH;
938                 } else if (value == "right") {
939                     myCurrentEdge->mySidewalkType = WAY_FORWARD;
940                 } else if (value == "left") {
941                     myCurrentEdge->mySidewalkType = WAY_BACKWARD;
942                 }
943                 // no need to extend the type id
944                 return;
945             }
946             // special busway stuff
947             if (key == "busway") {
948                 if (value == "no") {
949                     return;
950                 }
951                 if (value == "opposite_track") {
952                     myCurrentEdge->myBuswayType = WAY_BACKWARD;
953                 } else if (value == "opposite_lane") {
954                     myCurrentEdge->myBuswayType = WAY_BACKWARD;
955                 }
956                 // no need to extend the type id
957                 return;
958             }
959             // build type id
960             const std::string singleTypeID = key + "." + value;
961             if (!myCurrentEdge->myHighWayType.empty()) {
962                 // osm-ways may be used by more than one mode (eg railway.tram + highway.residential. this is relevant for multimodal traffic)
963                 // we create a new type for this kind of situation which must then be resolved in insertEdge()
964                 std::vector<std::string> types = StringTokenizer(myCurrentEdge->myHighWayType,
965                                                  compoundTypeSeparator).getVector();
966                 types.push_back(singleTypeID);
967                 myCurrentEdge->myHighWayType = joinToStringSorting(types, compoundTypeSeparator);
968             } else {
969                 myCurrentEdge->myHighWayType = singleTypeID;
970             }
971         } else if (key == "lanes") {
972             try {
973                 myCurrentEdge->myNoLanes = StringUtils::toInt(value);
974             } catch (NumberFormatException&) {
975                 // might be a list of values
976                 StringTokenizer st(value, ";", true);
977                 std::vector<std::string> list = st.getVector();
978                 if (list.size() >= 2) {
979                     int minLanes = std::numeric_limits<int>::max();
980                     try {
981                         for (auto& i : list) {
982                             int numLanes = StringUtils::toInt(StringUtils::prune(i));
983                             minLanes = MIN2(minLanes, numLanes);
984                         }
985                         myCurrentEdge->myNoLanes = minLanes;
986                         WRITE_WARNING(
987                             "Using minimum lane number from list (" + value + ") for edge '"
988                             + toString(myCurrentEdge->id)
989                             + "'.");
990                     } catch (NumberFormatException&) {
991                         WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
992                                       toString(myCurrentEdge->id) + "'.");
993                     }
994                 }
995             } catch (EmptyData&) {
996                 WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
997                               toString(myCurrentEdge->id) + "'.");
998             }
999         } else if (key == "lanes:forward") {
1000             try {
1001                 myCurrentEdge->myNoLanesForward = StringUtils::toInt(value);
1002             } catch (...) {
1003                 WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
1004                               toString(myCurrentEdge->id) + "'.");
1005             }
1006         } else if (key == "lanes:backward") {
1007             try {
1008                 // denote backwards count with a negative sign
1009                 myCurrentEdge->myNoLanesForward = -StringUtils::toInt(value);
1010             } catch (...) {
1011                 WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
1012                               toString(myCurrentEdge->id) + "'.");
1013             }
1014         } else if (key == "maxspeed") {
1015             if (mySpeedMap.find(value) != mySpeedMap.end()) {
1016                 myCurrentEdge->myMaxSpeed = mySpeedMap[value];
1017             } else {
1018                 double conversion = 1; // OSM default is km/h
1019                 if (StringUtils::to_lower_case(value).find("km/h") != std::string::npos) {
1020                     value = StringUtils::prune(value.substr(0, value.find_first_not_of("0123456789")));
1021                 } else if (StringUtils::to_lower_case(value).find("mph") != std::string::npos) {
1022                     value = StringUtils::prune(value.substr(0, value.find_first_not_of("0123456789")));
1023                     conversion = 1.609344; // kilometers per mile
1024                 }
1025                 try {
1026                     myCurrentEdge->myMaxSpeed = StringUtils::toDouble(value) * conversion;
1027                 } catch (...) {
1028                     WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
1029                                   toString(myCurrentEdge->id) + "'.");
1030                 }
1031             }
1032         } else if (key == "junction") {
1033             if ((value == "roundabout") && (myCurrentEdge->myIsOneWay.empty())) {
1034                 myCurrentEdge->myIsOneWay = "yes";
1035             }
1036         } else if (key == "oneway") {
1037             myCurrentEdge->myIsOneWay = value;
1038         } else if (key == "name") {
1039             myCurrentEdge->streetName = value;
1040         } else if (key == "ref") {
1041             myCurrentEdge->ref = value;
1042         } else if (key == "layer") {
1043             if (myAllAttributes) {
1044                 myCurrentEdge->setParameter(key, value);
1045             }
1046             try {
1047                 myCurrentEdge->myLayer = StringUtils::toInt(value);
1048             } catch (...) {
1049                 WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
1050                               toString(myCurrentEdge->id) + "'.");
1051             }
1052         } else if (key == "tracks") {
1053             try {
1054                 if (StringUtils::toInt(value) > 1) {
1055                     myCurrentEdge->myIsOneWay = "false";
1056                 } else {
1057                     myCurrentEdge->myIsOneWay = "true";
1058                 }
1059             } catch (...) {
1060                 WRITE_WARNING("Value of key '" + key + "' is not numeric ('" + value + "') in edge '" +
1061                               toString(myCurrentEdge->id) + "'.");
1062             }
1063         } else if (myAllAttributes && key == "postal_code") {
1064             myCurrentEdge->setParameter(key, value);
1065         } else if (key == "railway:preferred_direction") {
1066             // this param is special because it influences network building (duplicate rail edges)
1067             myCurrentEdge->setParameter(key, value);
1068         } else if (key == "public_transport" && value == "platform") {
1069             myCurrentEdge->myCurrentIsPlatform = true;
1070         } else if (key == "parking:lane:both" && !StringUtils::startsWith(value, "no")) {
1071             myCurrentEdge->myParkingType |= PARKING_BOTH;
1072         } else if (key == "parking:lane:left" && !StringUtils::startsWith(value, "no")) {
1073             myCurrentEdge->myParkingType |= PARKING_LEFT;
1074         } else if (key == "parking:lane:right" && !StringUtils::startsWith(value, "no")) {
1075             myCurrentEdge->myParkingType |= PARKING_RIGHT;
1076         }
1077     }
1078 }
1079 
1080 void
myEndElement(int element)1081 NIImporter_OpenStreetMap::EdgesHandler::myEndElement(int element) {
1082     myParentElements.pop_back();
1083     if (element == SUMO_TAG_WAY && myCurrentEdge != nullptr) {
1084         if (myCurrentEdge->myCurrentIsRoad) {
1085             myEdgeMap[myCurrentEdge->id] = myCurrentEdge;
1086         } else if (myCurrentEdge->myCurrentIsPlatform) {
1087             myPlatformShapesMap[myCurrentEdge->id] = myCurrentEdge;
1088         } else {
1089             delete myCurrentEdge;
1090         }
1091         myCurrentEdge = nullptr;
1092     }
1093 }
1094 
1095 // ---------------------------------------------------------------------------
1096 // definitions of NIImporter_OpenStreetMap::RelationHandler-methods
1097 // ---------------------------------------------------------------------------
RelationHandler(const std::map<long long int,NIOSMNode * > & osmNodes,const std::map<long long int,Edge * > & osmEdges,NBPTStopCont * nbptStopCont,const std::map<long long int,Edge * > & platformShapes,NBPTLineCont * nbptLineCont,const OptionsCont & oc)1098 NIImporter_OpenStreetMap::RelationHandler::RelationHandler(
1099     const std::map<long long int, NIOSMNode*>& osmNodes,
1100     const std::map<long long int, Edge*>& osmEdges, NBPTStopCont* nbptStopCont,
1101     const std::map<long long int, Edge*>& platformShapes,
1102     NBPTLineCont* nbptLineCont,
1103     const OptionsCont& oc)
1104     :
1105     SUMOSAXHandler("osm - file"),
1106     myOSMNodes(osmNodes),
1107     myOSMEdges(osmEdges),
1108     myPlatformShapes(platformShapes),
1109     myNBPTStopCont(nbptStopCont),
1110     myNBPTLineCont(nbptLineCont),
1111     myOptionsCont(oc) {
1112     resetValues();
1113 }
1114 
1115 NIImporter_OpenStreetMap::RelationHandler::~RelationHandler() = default;
1116 
1117 void
resetValues()1118 NIImporter_OpenStreetMap::RelationHandler::resetValues() {
1119     myCurrentRelation = INVALID_ID;
1120     myIsRestriction = false;
1121     myFromWay = INVALID_ID;
1122     myToWay = INVALID_ID;
1123     myViaNode = INVALID_ID;
1124     myViaWay = INVALID_ID;
1125     myRestrictionType = RESTRICTION_UNKNOWN;
1126     myPlatforms.clear();
1127     myStops.clear();
1128     myWays.clear();
1129     myIsStopArea = false;
1130     myIsRoute = false;
1131     myPTRouteType = "";
1132 }
1133 
1134 void
myStartElement(int element,const SUMOSAXAttributes & attrs)1135 NIImporter_OpenStreetMap::RelationHandler::myStartElement(int element,
1136         const SUMOSAXAttributes& attrs) {
1137     myParentElements.push_back(element);
1138     // parse "way" elements
1139     if (element == SUMO_TAG_RELATION) {
1140         bool ok = true;
1141         myCurrentRelation = attrs.get<long long int>(SUMO_ATTR_ID, nullptr, ok);
1142         const std::string action = attrs.hasAttribute("action") ? attrs.getStringSecure("action", "") : "";
1143         if (action == "delete" || !ok) {
1144             myCurrentRelation = INVALID_ID;
1145         }
1146         myName = "";
1147         myRef = "";
1148         myInterval = -1;
1149         myNightService = "";
1150         return;
1151     }
1152     if (myCurrentRelation == INVALID_ID) {
1153         return;
1154     }
1155     // parse member elements
1156     if (element == SUMO_TAG_MEMBER) {
1157         bool ok = true;
1158         std::string role = attrs.hasAttribute("role") ? attrs.getStringSecure("role", "") : "";
1159         auto ref = attrs.get<long
1160                    long
1161                    int>(SUMO_ATTR_REF, nullptr, ok);
1162         if (role == "via") {
1163             // u-turns for divided ways may be given with 2 via-nodes or 1 via-way
1164             std::string memberType = attrs.get<std::string>(SUMO_ATTR_TYPE, nullptr, ok);
1165             if (memberType == "way" && checkEdgeRef(ref)) {
1166                 myViaWay = ref;
1167             } else if (memberType == "node") {
1168                 if (myOSMNodes.find(ref) != myOSMNodes.end()) {
1169                     myViaNode = ref;
1170                 } else {
1171                     WRITE_WARNING(
1172                         "No node found for reference '" + toString(ref) + "' in relation '"
1173                         + toString(myCurrentRelation)
1174                         + "'");
1175                 }
1176             }
1177         } else if (role == "from" && checkEdgeRef(ref)) {
1178             myFromWay = ref;
1179         } else if (role == "to" && checkEdgeRef(ref)) {
1180             myToWay = ref;
1181         } else if (role == "stop") {
1182             myStops.push_back(ref);
1183         } else if (role == "platform") {
1184             std::string memberType = attrs.get<std::string>(SUMO_ATTR_TYPE, nullptr, ok);
1185             if (memberType == "way") {
1186                 const std::map<long long int,
1187                       NIImporter_OpenStreetMap::Edge*>::const_iterator& wayIt = myPlatformShapes.find(ref);
1188                 if (wayIt != myPlatformShapes.end()) {
1189 
1190                     NIIPTPlatform platform;
1191                     platform.isWay = true;
1192                     platform.ref = ref;
1193                     myPlatforms.push_back(platform);
1194                 }
1195             } else if (memberType == "node") {
1196                 NIIPTPlatform platform;
1197                 platform.isWay = false;
1198                 platform.ref = ref;
1199                 myPlatforms.push_back(platform);
1200             }
1201 
1202         } else if (role.empty()) {
1203             std::string memberType = attrs.get<std::string>(SUMO_ATTR_TYPE, nullptr, ok);
1204             if (memberType == "way") {
1205                 myWays.push_back(ref);
1206             }
1207         }
1208         return;
1209     }
1210     // parse values
1211     if (element == SUMO_TAG_TAG) {
1212         bool ok = true;
1213         std::string key = attrs.get<std::string>(SUMO_ATTR_K, toString(myCurrentRelation).c_str(), ok, false);
1214         // we check whether the key is relevant (and we really need to transcode the value) to avoid hitting #1636
1215         if (key == "type" || key == "restriction") {
1216             std::string value = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1217             if (key == "type" && value == "restriction") {
1218                 myIsRestriction = true;
1219                 return;
1220             }
1221             if (key == "type" && value == "route") {
1222                 myIsRoute = true;
1223                 return;
1224             }
1225             if (key == "restriction") {
1226                 // @note: the 'right/left/straight' part is ignored since the information is
1227                 // redundantly encoded in the 'from', 'to' and 'via' members
1228                 if (value.substr(0, 5) == "only_") {
1229                     myRestrictionType = RESTRICTION_ONLY;
1230                 } else if (value.substr(0, 3) == "no_") {
1231                     myRestrictionType = RESTRICTION_NO;
1232                 } else {
1233                     WRITE_WARNING(
1234                         "Found unknown restriction type '" + value + "' in relation '" + toString(myCurrentRelation)
1235                         + "'");
1236                 }
1237                 return;
1238             }
1239         } else if (key == "public_transport") {
1240             std::string value = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1241             if (value == "stop_area") {
1242                 myIsStopArea = true;
1243             }
1244         } else if (key == "route") {
1245             std::string value = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1246             if (value == "train" || value == "subway" || value == "light_rail" || value == "monorail" || value == "tram" || value == "bus"
1247                     || value == "trolleybus" || value == "arialway" || value == "ferry") {
1248                 myPTRouteType = value;
1249             }
1250 
1251         } else if (key == "name") {
1252             myName = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1253         } else if (key == "ref") {
1254             myRef = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1255         } else if (key == "interval" || key == "headway") {
1256             myInterval = attrs.get<int>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1257         } else if (key == "by_night") {
1258             myNightService = attrs.get<std::string>(SUMO_ATTR_V, toString(myCurrentRelation).c_str(), ok, false);
1259         }
1260     }
1261 }
1262 
1263 bool
checkEdgeRef(long long int ref) const1264 NIImporter_OpenStreetMap::RelationHandler::checkEdgeRef(long long int ref) const {
1265     if (myOSMEdges.find(ref) != myOSMEdges.end()) {
1266         return true;
1267     }
1268 
1269     WRITE_WARNING(
1270         "No way found for reference '" + toString(ref) + "' in relation '" + toString(myCurrentRelation) + "'");
1271     return false;
1272 
1273 }
1274 
1275 void
myEndElement(int element)1276 NIImporter_OpenStreetMap::RelationHandler::myEndElement(int element) {
1277     myParentElements.pop_back();
1278     if (element == SUMO_TAG_RELATION) {
1279         if (myIsRestriction) {
1280             assert(myCurrentRelation != INVALID_ID);
1281             bool ok = true;
1282             if (myRestrictionType == RESTRICTION_UNKNOWN) {
1283                 WRITE_WARNING("Ignoring restriction relation '" + toString(myCurrentRelation) + "' with unknown type.");
1284                 ok = false;
1285             }
1286             if (myFromWay == INVALID_ID) {
1287                 WRITE_WARNING(
1288                     "Ignoring restriction relation '" + toString(myCurrentRelation) + "' with unknown from-way.");
1289                 ok = false;
1290             }
1291             if (myToWay == INVALID_ID) {
1292                 WRITE_WARNING(
1293                     "Ignoring restriction relation '" + toString(myCurrentRelation) + "' with unknown to-way.");
1294                 ok = false;
1295             }
1296             if (myViaNode == INVALID_ID && myViaWay == INVALID_ID) {
1297                 WRITE_WARNING("Ignoring restriction relation '" + toString(myCurrentRelation) + "' with unknown via.");
1298                 ok = false;
1299             }
1300             if (ok && !applyRestriction()) {
1301                 WRITE_WARNING("Ignoring restriction relation '" + toString(myCurrentRelation) + "'.");
1302             }
1303         } else if (myIsStopArea && OptionsCont::getOptions().isSet("ptstop-output")) {
1304             for (long long ref : myStops) {
1305                 if (myOSMNodes.find(ref) == myOSMNodes.end()) {
1306                     //WRITE_WARNING(
1307                     //    "Referenced node: '" + toString(ref) + "' in relation: '" + toString(myCurrentRelation)
1308                     //    + "' does not exist. Probably OSM file is incomplete.");
1309                     continue;
1310                 }
1311 
1312                 NIOSMNode* n = myOSMNodes.find(ref)->second;
1313                 NBPTStop* ptStop = myNBPTStopCont->get(toString(n->id));
1314                 if (ptStop == nullptr) {
1315                     //WRITE_WARNING(
1316                     //    "Relation '" + toString(myCurrentRelation) + "' refers to a non existing pt stop at node: '"
1317                     //    + toString(n->id) + "'. Probably OSM file is incomplete.");
1318                     continue;
1319                 }
1320                 for (NIIPTPlatform& myPlatform : myPlatforms) {
1321                     if (myPlatform.isWay) {
1322                         assert(myPlatformShapes.find(myPlatform.ref) != myPlatformShapes.end()); //already tested earlier
1323                         Edge* edge = (*myPlatformShapes.find(myPlatform.ref)).second;
1324                         if (edge->myCurrentNodes[0] == *(edge->myCurrentNodes.end() - 1)) {
1325                             WRITE_WARNING("Platform '" + toString(myPlatform.ref) + "' in  relation: '" + toString(myCurrentRelation)
1326                                           + "'  is given as polygon, which currently is not supported.");
1327                             continue;
1328 
1329                         }
1330                         PositionVector p;
1331                         for (auto nodeRef : edge->myCurrentNodes) {
1332                             if (myOSMNodes.find(nodeRef) == myOSMNodes.end()) {
1333                                 //WRITE_WARNING(
1334                                 //    "Referenced node: '" + toString(ref) + "' in relation: '" + toString(myCurrentRelation)
1335                                 //    + "' does not exist. Probably OSM file is incomplete.");
1336                                 continue;
1337                             }
1338                             NIOSMNode* pNode = myOSMNodes.find(nodeRef)->second;
1339                             Position pNodePos(pNode->lon, pNode->lat, pNode->ele);
1340                             if (!NBNetBuilder::transformCoordinate(pNodePos)) {
1341                                 WRITE_ERROR("Unable to project coordinates for node '" + toString(pNode->id) + "'.");
1342                                 continue;
1343                             }
1344                             p.push_back(pNodePos);
1345                         }
1346                         if (p.size() == 0) {
1347                             WRITE_WARNING(
1348                                 "Referenced platform: '" + toString(myPlatform.ref) + "' in relation: '" + toString(myCurrentRelation)
1349                                 + "' is corrupt. Probably OSM file is incomplete.");
1350                             continue;
1351                         }
1352                         NBPTPlatform platform(p[(int)p.size() / 2], p.length());
1353                         ptStop->addPlatformCand(platform);
1354                     } else {
1355                         if (myOSMNodes.find(myPlatform.ref) == myOSMNodes.end()) {
1356                             //WRITE_WARNING(
1357                             //    "Referenced node: '" + toString(ref) + "' in relation: '" + toString(myCurrentRelation)
1358                             //    + "' does not exist. Probably OSM file is incomplete.");
1359                             continue;
1360                         }
1361                         NIOSMNode* pNode = myOSMNodes.find(myPlatform.ref)->second;
1362                         Position platformPos(pNode->lon, pNode->lat, pNode->ele);
1363                         if (!NBNetBuilder::transformCoordinate(platformPos)) {
1364                             WRITE_ERROR("Unable to project coordinates for node '" + toString(pNode->id) + "'.");
1365                         }
1366                         NBPTPlatform platform(platformPos, myOptionsCont.getFloat(
1367                                                   "osm.stop-output.length"));
1368                         ptStop->addPlatformCand(platform);
1369 
1370                     }
1371                 }
1372                 ptStop->setIsMultipleStopPositions(myStops.size() > 1);;
1373             }
1374         } else if (myPTRouteType != "" && myIsRoute && OptionsCont::getOptions().isSet("ptline-output") && myStops.size() > 1) {
1375             NBPTLine* ptLine = new NBPTLine(toString(myCurrentRelation), myName, myPTRouteType, myRef, myInterval, myNightService);
1376             ptLine->setMyNumOfStops((int)myStops.size());
1377             for (long long ref : myStops) {
1378                 if (myOSMNodes.find(ref) == myOSMNodes.end()) {
1379                     //WRITE_WARNING(
1380                     //    "Referenced node: '" + toString(ref) + "' in relation: '" + toString(myCurrentRelation)
1381                     //    + "' does not exist. Probably OSM file is incomplete.");
1382 //                    resetValues();
1383 //                    return;
1384                     if (!ptLine->getStops().empty()) {
1385                         WRITE_WARNING("Done reading first coherent chunk of pt stops. Further stops in relation " + toString(myCurrentRelation) + " are ignored");
1386                         break;
1387                     }
1388                     continue;
1389                 }
1390 
1391                 NIOSMNode* n = myOSMNodes.find(ref)->second;
1392                 NBPTStop* ptStop = myNBPTStopCont->get(toString(n->id));
1393                 if (ptStop == nullptr) {
1394                     //WRITE_WARNING("Relation '" + toString(myCurrentRelation)
1395                     //              + "' refers to a non existing pt stop at node: '" + toString(n->id)
1396                     //              + "'. Probably OSM file is incomplete.");
1397 //                    resetValues();
1398 //                    return;
1399                     if (!ptLine->getStops().empty()) {
1400                         WRITE_WARNING("Done reading first coherent chunk of pt stops. Further stops in relation " + toString(myCurrentRelation) + " are ignored");
1401                         break;
1402                     }
1403                     continue;
1404                 }
1405                 ptLine->addPTStop(ptStop);
1406             }
1407             for (long long& myWay : myWays) {
1408                 auto entr = myOSMEdges.find(myWay);
1409                 if (entr != myOSMEdges.end()) {
1410                     Edge* edge = entr->second;
1411                     for (long long& myCurrentNode : edge->myCurrentNodes) {
1412                         ptLine->addWayNode(myWay, myCurrentNode);
1413                     }
1414                 }
1415             }
1416             if (ptLine->getStops().empty()) {
1417                 WRITE_WARNING("PT line in relation " + toString(myCurrentRelation) + " with no stops ignored. Probably OSM file is incomplete.");
1418                 resetValues();
1419                 return;
1420             }
1421             if (myNBPTLineCont->getLines().count(ptLine->getLineID()) == 0) {
1422                 myNBPTLineCont->insert(ptLine);
1423             } else {
1424                 WRITE_WARNING("Ignoring duplicate PT line " + toString(myCurrentRelation) + ".");
1425                 delete ptLine;
1426             }
1427         }
1428         // other relations might use similar subelements so reset in any case
1429         resetValues();
1430     }
1431 }
1432 
1433 bool
applyRestriction() const1434 NIImporter_OpenStreetMap::RelationHandler::applyRestriction() const {
1435     // since OSM ways are bidirectional we need the via to figure out which direction was meant
1436     if (myViaNode != INVALID_ID) {
1437         NBNode* viaNode = myOSMNodes.find(myViaNode)->second->node;
1438         if (viaNode == nullptr) {
1439             WRITE_WARNING("Via-node '" + toString(myViaNode) + "' was not instantiated");
1440             return false;
1441         }
1442         NBEdge* from = findEdgeRef(myFromWay, viaNode->getIncomingEdges());
1443         NBEdge* to = findEdgeRef(myToWay, viaNode->getOutgoingEdges());
1444         if (from == nullptr) {
1445             WRITE_WARNING("from-edge of restriction relation could not be determined");
1446             return false;
1447         }
1448         if (to == nullptr) {
1449             WRITE_WARNING("to-edge of restriction relation could not be determined");
1450             return false;
1451         }
1452         if (myRestrictionType == RESTRICTION_ONLY) {
1453             from->addEdge2EdgeConnection(to);
1454         } else {
1455             from->removeFromConnections(to, -1, -1, true);
1456         }
1457     } else {
1458         // XXX interpreting via-ways or via-node lists not yet implemented
1459         WRITE_WARNING("direction of restriction relation could not be determined");
1460         return false;
1461     }
1462     return true;
1463 }
1464 
1465 NBEdge*
findEdgeRef(long long int wayRef,const std::vector<NBEdge * > & candidates) const1466 NIImporter_OpenStreetMap::RelationHandler::findEdgeRef(long long int wayRef,
1467         const std::vector<NBEdge*>& candidates) const {
1468     const std::string prefix = toString(wayRef);
1469     const std::string backPrefix = "-" + prefix;
1470     NBEdge* result = nullptr;
1471     int found = 0;
1472     for (auto candidate : candidates) {
1473         if ((candidate->getID().substr(0, prefix.size()) == prefix) ||
1474                 (candidate->getID().substr(0, backPrefix.size()) == backPrefix)) {
1475             result = candidate;
1476             found++;
1477         }
1478     }
1479     if (found > 1) {
1480         WRITE_WARNING("Ambigous way reference '" + prefix + "' in restriction relation");
1481         result = nullptr;
1482     }
1483     return result;
1484 }
1485 
1486 void
reconstructLayerElevation(const double layerElevation,NBNetBuilder & nb)1487 NIImporter_OpenStreetMap::reconstructLayerElevation(const double layerElevation, NBNetBuilder& nb) {
1488     NBNodeCont& nc = nb.getNodeCont();
1489     NBEdgeCont& ec = nb.getEdgeCont();
1490     // reconstruct elevation from layer info
1491     // build a map of raising and lowering forces (attractor and distance)
1492     // for all nodes unknownElevation
1493     std::map<NBNode*, std::vector<std::pair<double, double> > > layerForces;
1494 
1495     // collect all nodes that belong to a way with layer information
1496     std::set<NBNode*> knownElevation;
1497     for (auto& myEdge : myEdges) {
1498         Edge* e = myEdge.second;
1499         if (e->myLayer != 0) {
1500             for (auto j = e->myCurrentNodes.begin(); j != e->myCurrentNodes.end(); ++j) {
1501                 NBNode* node = nc.retrieve(toString(*j));
1502                 if (node != nullptr) {
1503                     knownElevation.insert(node);
1504                     layerForces[node].emplace_back(e->myLayer * layerElevation, POSITION_EPS);
1505                 }
1506             }
1507         }
1508     }
1509 #ifdef DEBUG_LAYER_ELEVATION
1510     std::cout << "known elevations:\n";
1511     for (std::set<NBNode*>::iterator it = knownElevation.begin(); it != knownElevation.end(); ++it) {
1512         const std::vector<std::pair<double, double> >& primaryLayers = layerForces[*it];
1513         std::cout << "  node=" << (*it)->getID() << " ele=";
1514         for (std::vector<std::pair<double, double> >::const_iterator it_ele = primaryLayers.begin(); it_ele != primaryLayers.end(); ++it_ele) {
1515             std::cout << it_ele->first << " ";
1516         }
1517         std::cout << "\n";
1518     }
1519 #endif
1520     // layer data only provides a lower bound on elevation since it is used to
1521     // resolve the relation among overlapping ways.
1522     // Perform a sanity check for steep inclines and raise the knownElevation if necessary
1523     std::map<NBNode*, double> knownEleMax;
1524     for (auto it : knownElevation) {
1525         double eleMax = -std::numeric_limits<double>::max();
1526         const std::vector<std::pair<double, double> >& primaryLayers = layerForces[it];
1527         for (const auto& primaryLayer : primaryLayers) {
1528             eleMax = MAX2(eleMax, primaryLayer.first);
1529         }
1530         knownEleMax[it] = eleMax;
1531     }
1532     const double gradeThreshold = OptionsCont::getOptions().getFloat("osm.layer-elevation.max-grade") / 100;
1533     bool changed = true;
1534     while (changed) {
1535         changed = false;
1536         for (auto it = knownElevation.begin(); it != knownElevation.end(); ++it) {
1537             std::map<NBNode*, std::pair<double, double> > neighbors = getNeighboringNodes(*it,
1538                     knownEleMax[*it]
1539                     / gradeThreshold * 3,
1540                     knownElevation);
1541             for (auto& neighbor : neighbors) {
1542                 if (knownElevation.count(neighbor.first) != 0) {
1543                     const double grade = fabs(knownEleMax[*it] - knownEleMax[neighbor.first])
1544                                          / MAX2(POSITION_EPS, neighbor.second.first);
1545 #ifdef DEBUG_LAYER_ELEVATION
1546                     std::cout << "   grade at node=" << (*it)->getID() << " ele=" << knownEleMax[*it] << " neigh=" << it_neigh->first->getID() << " neighEle=" << knownEleMax[it_neigh->first] << " grade=" << grade << " dist=" << it_neigh->second.first << " speed=" << it_neigh->second.second << "\n";
1547 #endif
1548                     if (grade > gradeThreshold * 50 / 3.6 / neighbor.second.second) {
1549                         // raise the lower node to the higher level
1550                         const double eleMax = MAX2(knownEleMax[*it], knownEleMax[neighbor.first]);
1551                         if (knownEleMax[*it] < eleMax) {
1552                             knownEleMax[*it] = eleMax;
1553                         } else {
1554                             knownEleMax[neighbor.first] = eleMax;
1555                         }
1556                         changed = true;
1557                     }
1558                 }
1559             }
1560         }
1561     }
1562 
1563     // collect all nodes within a grade-dependent range around knownElevation-nodes and apply knowElevation forces
1564     std::set<NBNode*> unknownElevation;
1565     for (auto it = knownElevation.begin(); it != knownElevation.end(); ++it) {
1566         const double eleMax = knownEleMax[*it];
1567         const double maxDist = fabs(eleMax) * 100 / layerElevation;
1568         std::map<NBNode*, std::pair<double, double> > neighbors = getNeighboringNodes(*it, maxDist, knownElevation);
1569         for (auto& neighbor : neighbors) {
1570             if (knownElevation.count(neighbor.first) == 0) {
1571                 unknownElevation.insert(neighbor.first);
1572                 layerForces[neighbor.first].emplace_back(eleMax, neighbor.second.first);
1573             }
1574         }
1575     }
1576 
1577     // apply forces to ground-level nodes (neither in knownElevation nor unknownElevation)
1578     for (auto it = unknownElevation.begin(); it != unknownElevation.end(); ++it) {
1579         double eleMax = -std::numeric_limits<double>::max();
1580         const std::vector<std::pair<double, double> >& primaryLayers = layerForces[*it];
1581         for (const auto& primaryLayer : primaryLayers) {
1582             eleMax = MAX2(eleMax, primaryLayer.first);
1583         }
1584         const double maxDist = fabs(eleMax) * 100 / layerElevation;
1585         std::map<NBNode*, std::pair<double, double> > neighbors = getNeighboringNodes(*it, maxDist, knownElevation);
1586         for (auto& neighbor : neighbors) {
1587             if (knownElevation.count(neighbor.first) == 0 && unknownElevation.count(neighbor.first) == 0) {
1588                 layerForces[*it].emplace_back(0, neighbor.second.first);
1589             }
1590         }
1591     }
1592     // compute the elevation for each node as the weighted average of all forces
1593 #ifdef DEBUG_LAYER_ELEVATION
1594     std::cout << "summation of forces\n";
1595 #endif
1596     std::map<NBNode*, double> nodeElevation;
1597     for (auto& layerForce : layerForces) {
1598         const std::vector<std::pair<double, double> >& forces = layerForce.second;
1599         if (knownElevation.count(layerForce.first) != 0) {
1600             // use the maximum value
1601             /*
1602             double eleMax = -std::numeric_limits<double>::max();
1603             for (std::vector<std::pair<double, double> >::const_iterator it_force = forces.begin(); it_force != forces.end(); ++it_force) {
1604                 eleMax = MAX2(eleMax, it_force->first);
1605             }
1606             */
1607 #ifdef DEBUG_LAYER_ELEVATION
1608             std::cout << "   node=" << it->first->getID() << " knownElevation=" << knownEleMax[it->first] << "\n";
1609 #endif
1610             nodeElevation[layerForce.first] = knownEleMax[layerForce.first];
1611         } else if (forces.size() == 1) {
1612             nodeElevation[layerForce.first] = forces.front().first;
1613         } else {
1614             // use the weighted sum
1615             double distSum = 0;
1616             for (const auto& force : forces) {
1617                 distSum += force.second;
1618             }
1619             double weightSum = 0;
1620             double elevation = 0;
1621 #ifdef DEBUG_LAYER_ELEVATION
1622             std::cout << "   node=" << it->first->getID() << "  distSum=" << distSum << "\n";
1623 #endif
1624             for (const auto& force : forces) {
1625                 const double weight = (distSum - force.second) / distSum;
1626                 weightSum += weight;
1627                 elevation += force.first * weight;
1628 
1629 #ifdef DEBUG_LAYER_ELEVATION
1630                 std::cout << "       force=" << it_force->first << " dist=" << it_force->second << "  weight=" << weight << " ele=" << elevation << "\n";
1631 #endif
1632             }
1633             nodeElevation[layerForce.first] = elevation / weightSum;
1634         }
1635     }
1636 #ifdef DEBUG_LAYER_ELEVATION
1637     std::cout << "final elevations:\n";
1638     for (std::map<NBNode*, double>::iterator it = nodeElevation.begin(); it != nodeElevation.end(); ++it) {
1639         std::cout << "  node=" << (it->first)->getID() << " ele=" << it->second << "\n";;
1640     }
1641 #endif
1642     // apply node elevations
1643     for (auto& it : nodeElevation) {
1644         NBNode* n = it.first;
1645         Position pos = n->getPosition();
1646         n->reinit(n->getPosition() + Position(0, 0, it.second), n->getType());
1647     }
1648 
1649     // apply way elevation to all edges that had layer information
1650     for (const auto& it : ec) {
1651         NBEdge* edge = it.second;
1652         const PositionVector& geom = edge->getGeometry();
1653         const double length = geom.length2D();
1654         const double zFrom = nodeElevation[edge->getFromNode()];
1655         const double zTo = nodeElevation[edge->getToNode()];
1656         // XXX if the from- or to-node was part of multiple ways with
1657         // different layers, reconstruct the layer value from origID
1658         double dist = 0;
1659         PositionVector newGeom;
1660         for (auto it_pos = geom.begin(); it_pos != geom.end(); ++it_pos) {
1661             if (it_pos != geom.begin()) {
1662                 dist += (*it_pos).distanceTo2D(*(it_pos - 1));
1663             }
1664             newGeom.push_back((*it_pos) + Position(0, 0, zFrom + (zTo - zFrom) * dist / length));
1665         }
1666         edge->setGeometry(newGeom);
1667     }
1668 }
1669 
1670 std::map<NBNode*, std::pair<double, double> >
getNeighboringNodes(NBNode * node,double maxDist,const std::set<NBNode * > & knownElevation)1671 NIImporter_OpenStreetMap::getNeighboringNodes(NBNode* node, double maxDist, const std::set<NBNode*>& knownElevation) {
1672     std::map<NBNode*, std::pair<double, double> > result;
1673     std::set<NBNode*> visited;
1674     std::vector<NBNode*> open;
1675     open.push_back(node);
1676     result[node] = std::make_pair(0, 0);
1677     while (!open.empty()) {
1678         NBNode* n = open.back();
1679         open.pop_back();
1680         if (visited.count(n) != 0) {
1681             continue;
1682         }
1683         visited.insert(n);
1684         const EdgeVector& edges = n->getEdges();
1685         for (auto e : edges) {
1686             NBNode* s = nullptr;
1687             if (n->hasIncoming(e)) {
1688                 s = e->getFromNode();
1689             } else {
1690                 s = e->getToNode();
1691             }
1692             const double dist = result[n].first + e->getGeometry().length2D();
1693             const double speed = MAX2(e->getSpeed(), result[n].second);
1694             if (result.count(s) == 0) {
1695                 result[s] = std::make_pair(dist, speed);
1696             } else {
1697                 result[s] = std::make_pair(MIN2(dist, result[s].first), MAX2(speed, result[s].second));
1698             }
1699             if (dist < maxDist && knownElevation.count(s) == 0) {
1700                 open.push_back(s);
1701             }
1702         }
1703     }
1704     result.erase(node);
1705     return result;
1706 }
1707 
1708 
1709 /****************************************************************************/
1710 
1711