LCOV - code coverage report
Current view: top level - src/netbuild - NBNodeCont.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 94.9 % 1277 1212
Test Date: 2026-04-16 16:39:47 Functions: 93.9 % 66 62

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2001-2026 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    NBNodeCont.cpp
      15              : /// @author  Daniel Krajzewicz
      16              : /// @author  Jakob Erdmann
      17              : /// @author  Yun-Pang Floetteroed
      18              : /// @author  Walter Bamberger
      19              : /// @author  Laura Bieker
      20              : /// @author  Michael Behrisch
      21              : /// @author  Sascha Krieg
      22              : /// @date    Tue, 20 Nov 2001
      23              : ///
      24              : // Container for nodes during the netbuilding process
      25              : /****************************************************************************/
      26              : #include <config.h>
      27              : 
      28              : #include <string>
      29              : #include <map>
      30              : #include <algorithm>
      31              : #include <cmath>
      32              : #include <utils/options/OptionsCont.h>
      33              : #include <utils/geom/Boundary.h>
      34              : #include <utils/geom/GeomHelper.h>
      35              : #include <utils/common/MsgHandler.h>
      36              : #include <utils/common/UtilExceptions.h>
      37              : #include <utils/common/StringTokenizer.h>
      38              : #include <utils/common/StringUtils.h>
      39              : #include <utils/common/StdDefs.h>
      40              : #include <utils/common/ToString.h>
      41              : #include <utils/common/StringUtils.h>
      42              : #include <utils/common/IDSupplier.h>
      43              : #include <utils/xml/SUMOXMLDefinitions.h>
      44              : #include <utils/geom/GeoConvHelper.h>
      45              : #include <utils/iodevices/OutputDevice.h>
      46              : #include "NBHelpers.h"
      47              : #include "NBAlgorithms.h"
      48              : #include "NBDistrict.h"
      49              : #include "NBEdgeCont.h"
      50              : #include "NBTrafficLightLogicCont.h"
      51              : #include "NBOwnTLDef.h"
      52              : #include "NBPTStop.h"
      53              : #include "NBNodeCont.h"
      54              : #include "NBPTStopCont.h"
      55              : #include "NBPTLineCont.h"
      56              : #include "NBParking.h"
      57              : 
      58              : // ===========================================================================
      59              : // Algorithm constants
      60              : // ===========================================================================
      61              : #define MAX_SLIPLANE_LENGTH 1000
      62              : 
      63              : // ===========================================================================
      64              : // Debug Flags
      65              : // ===========================================================================
      66              : 
      67              : //#define DEBUG_JOINJUNCTIONS
      68              : //#define DEBUG_REDUCE
      69              : //#define DEBUG_JOINJUNCTIONS_CONNECTIONS
      70              : //#define DEBUG_GUESSSIGNALS
      71              : #define DEBUGNODEID ""
      72              : #define DEBUGNODEID2 ""
      73              : //#define DEBUGNODEID "5548037023"
      74              : #define DEBUGCOND(obj) ((obj) != 0 && ((obj)->getID() == DEBUGNODEID || (obj)->getID() == DEBUGNODEID2))
      75              : //#define DEBUGCOND(obj) (true)
      76              : 
      77              : 
      78              : // ===========================================================================
      79              : // method definitions
      80              : // ===========================================================================
      81         2506 : NBNodeCont::~NBNodeCont() {
      82         2506 :     clear();
      83         5012 : }
      84              : 
      85              : 
      86              : // ----------- Insertion/removal/retrieval of nodes
      87              : bool
      88         1505 : NBNodeCont::insert(const std::string& id, const Position& position,
      89              :                    NBDistrict* district) {
      90              :     NodeCont::iterator i = myNodes.find(id);
      91         1505 :     if (i != myNodes.end()) {
      92              :         return false;
      93              :     }
      94         1463 :     NBNode* node = new NBNode(id, position, district);
      95         1463 :     myNodes[id] = node;
      96         1463 :     const float pos[2] = {(float)position.x(), (float)position.y()};
      97         1463 :     myRTree.Insert(pos, pos, node);
      98         1463 :     return true;
      99              : }
     100              : 
     101              : 
     102              : bool
     103        75644 : NBNodeCont::insert(NBNode* node) {
     104              :     std::string id = node->getID();
     105              :     NodeCont::iterator i = myNodes.find(id);
     106        75644 :     if (i != myNodes.end()) {
     107              :         return false;
     108              :     }
     109        75447 :     myNodes[id] = node;
     110        75447 :     const float pos[2] = {(float)node->getPosition().x(), (float)node->getPosition().y()};
     111        75447 :     myRTree.Insert(pos, pos, node);
     112        75447 :     return true;
     113              : }
     114              : 
     115              : 
     116              : NBNode*
     117       453038 : NBNodeCont::retrieve(const std::string& id) const {
     118              :     NodeCont::const_iterator i = myNodes.find(id);
     119       453038 :     if (i == myNodes.end()) {
     120              :         return nullptr;
     121              :     }
     122       409330 :     return (*i).second;
     123              : }
     124              : 
     125              : 
     126              : std::vector<NBNode*>
     127         1484 : NBNodeCont::retrieveByPos(const Position& position, const double offset) const {
     128              :     std::vector<NBNode*> result;
     129         1484 :     const double extOffset = offset + POSITION_EPS;
     130         1484 :     const float cmin[2] = {(float)(position.x() - extOffset), (float)(position.y() - extOffset)};
     131         1484 :     const float cmax[2] = {(float)(position.x() + extOffset), (float)(position.y() + extOffset)};
     132              :     std::set<const Named*> into;
     133              :     Named::StoringVisitor sv(into);
     134              :     myRTree.Search(cmin, cmax, sv);
     135         2966 :     for (const Named* namedNode : into) {
     136         1482 :         NBNode* node = const_cast<NBNode*>(dynamic_cast<const NBNode*>(namedNode));
     137         1482 :         if (fabs(node->getPosition().x() - position.x()) <= offset
     138         1482 :                 &&
     139         1482 :                 fabs(node->getPosition().y() - position.y()) <= offset) {
     140         1482 :             result.push_back(node);
     141              :         }
     142              :     }
     143         1484 :     return result;
     144            0 : }
     145              : 
     146              : 
     147              : bool
     148         3026 : NBNodeCont::erase(NBNode* node) {
     149         3026 :     if (extract(node)) {
     150         3026 :         delete node;
     151         3026 :         return true;
     152              :     } else {
     153              :         return false;
     154              :     }
     155              : }
     156              : 
     157              : 
     158              : bool
     159        14351 : NBNodeCont::extract(NBNode* node, bool remember) {
     160              :     NodeCont::iterator i = myNodes.find(node->getID());
     161        14351 :     if (i == myNodes.end()) {
     162              :         return false;
     163              :     }
     164              :     myNodes.erase(i);
     165        14351 :     const float pos[2] = {(float)node->getPosition().x(), (float)node->getPosition().y()};
     166        14351 :     myRTree.Remove(pos, pos, node);
     167        14351 :     node->removeTrafficLights();
     168        14351 :     if (remember) {
     169        11325 :         myExtractedNodes[node->getID()] = node;
     170              :     }
     171              :     return true;
     172              : }
     173              : 
     174              : 
     175              : // ----------- Adapting the input
     176              : int
     177         2206 : NBNodeCont::removeSelfLoops(NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tc) {
     178         2206 :     int no = 0;
     179        77301 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
     180        75095 :         no += (*i).second->removeSelfLoops(dc, ec, tc);
     181              :     }
     182         2206 :     if (no != 0) {
     183            0 :         WRITE_WARNING(toString(no) + " self-looping edge(s) removed.");
     184              :     }
     185         2206 :     return no;
     186              : }
     187              : 
     188              : 
     189              : void
     190           19 : NBNodeCont::joinSimilarEdges(NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tlc, bool removeDuplicates) {
     191              :     // magic values
     192              :     const double distanceThreshold = 7.; // don't merge edges further apart
     193              :     const double lengthThreshold = 0.10; // don't merge edges with higher relative length-difference
     194              : 
     195         1408 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
     196              :         // count the edges to other nodes outgoing from the current node
     197              :         std::map<NBNode*, EdgeVector> connectionCount;
     198         1389 :         const EdgeVector& outgoing = (*i).second->getOutgoingEdges();
     199         3069 :         for (EdgeVector::const_iterator j = outgoing.begin(); j != outgoing.end(); j++) {
     200         1680 :             connectionCount[(*j)->getToNode()].push_back(*j);
     201              :         }
     202              :         // check whether more than a single edge connect another node and join them
     203              :         std::map<NBNode*, EdgeVector>::iterator k;
     204         2995 :         for (k = connectionCount.begin(); k != connectionCount.end(); k++) {
     205              :             // possibly we do not have anything to join...
     206         1606 :             if ((*k).second.size() < 2) {
     207              :                 continue;
     208              :             }
     209              :             // for the edges that seem to be a single street,
     210              :             //  check whether the geometry is similar
     211           65 :             const EdgeVector& ev = (*k).second;
     212           65 :             const NBEdge* const first = ev.front();
     213              :             EdgeVector::const_iterator jci; // join candidate iterator
     214           87 :             for (jci = ev.begin() + 1; jci != ev.end(); ++jci) {
     215           65 :                 const double relativeLengthDifference = fabs(first->getLoadedLength() - (*jci)->getLoadedLength()) / first->getLoadedLength();
     216           94 :                 if ((!first->isNearEnough2BeJoined2(*jci, distanceThreshold)) ||
     217           23 :                         (relativeLengthDifference > lengthThreshold) ||
     218          110 :                         (fabs(first->getSpeed() - (*jci)->getSpeed()) >= 0.01) || // output accuracy
     219           22 :                         (first->getPermissions() != (*jci)->getPermissions())
     220              :                    ) {
     221              :                     break;
     222              :                 }
     223              :             }
     224              :             // @bug If there are 3 edges of which 2 can be joined, no joining will
     225              :             //   take place with the current implementation
     226           65 :             if (jci == ev.end()) {
     227           22 :                 if (removeDuplicates) {
     228           20 :                     for (int ei = 1; ei < (int)ev.size(); ei++) {
     229           10 :                         ec.extract(dc, ev[ei], true);
     230              :                     }
     231              :                 } else {
     232           12 :                     ec.joinSameNodeConnectingEdges(dc, tlc, ev);
     233              :                 }
     234              :             }
     235              :         }
     236              :     }
     237           19 : }
     238              : 
     239              : 
     240              : int
     241            5 : NBNodeCont::removeIsolatedRoads(NBDistrictCont& dc, NBEdgeCont& ec) {
     242              :     int numRemovedEdges = 0;
     243              :     // Warn of isolated edges, i.e. a single edge with no connection to another edge
     244            5 :     const std::vector<std::string>& edgeNames = ec.getAllNames();
     245          370 :     for (std::vector<std::string>::const_iterator it = edgeNames.begin(); it != edgeNames.end(); ++it) {
     246              :         // Test whether this node starts at a dead end, i.e. it has only one adjacent node
     247              :         // to which an edge exists and from which an edge may come.
     248          365 :         NBEdge* e = ec.retrieve(*it);
     249          365 :         if (e == nullptr) {
     250          309 :             continue;
     251              :         }
     252              :         NBNode* from = e->getFromNode();
     253              :         const EdgeVector& outgoingEdges = from->getOutgoingEdges();
     254          355 :         if (outgoingEdges.size() != 1) {
     255              :             // At this node, several edges or no edge start; so, this node is no dead end.
     256          262 :             continue;
     257              :         }
     258              :         const EdgeVector& incomingEdges = from->getIncomingEdges();
     259           93 :         if (incomingEdges.size() > 1) {
     260              :             // At this node, several edges end; so, this node is no dead end.
     261           11 :             continue;
     262           82 :         } else if (incomingEdges.size() == 1) {
     263           61 :             NBNode* fromNodeOfIncomingEdge = incomingEdges[0]->getFromNode();
     264           61 :             NBNode* toNodeOfOutgoingEdge = outgoingEdges[0]->getToNode();
     265           61 :             if (fromNodeOfIncomingEdge != toNodeOfOutgoingEdge) {
     266              :                 // At this node, an edge ends which is not the inverse direction of
     267              :                 // the starting node.
     268           26 :                 continue;
     269              :             }
     270              :         }
     271              :         // Now we know that the edge e starts a dead end.
     272              :         // Next we test if the dead end is isolated, i.e. does not lead to a junction
     273              :         bool hasJunction = false;
     274              :         EdgeVector road;
     275              :         NBEdge* eOld = nullptr;
     276              :         NBNode* to;
     277              :         NodeSet adjacentNodes;
     278              :         do {
     279           89 :             road.push_back(e);
     280           89 :             eOld = e;
     281              :             from = e->getFromNode();
     282           89 :             to = e->getToNode();
     283              :             const EdgeVector& outgoingEdgesOfToNode = to->getOutgoingEdges();
     284              :             const EdgeVector& incomingEdgesOfToNode = to->getIncomingEdges();
     285              :             adjacentNodes.clear();
     286          248 :             for (EdgeVector::const_iterator itOfOutgoings = outgoingEdgesOfToNode.begin(); itOfOutgoings != outgoingEdgesOfToNode.end(); ++itOfOutgoings) {
     287          159 :                 if ((*itOfOutgoings)->getToNode() != from        // The back path
     288          159 :                         && (*itOfOutgoings)->getToNode() != to   // A loop / dummy edge
     289              :                    ) {
     290          107 :                     e = *itOfOutgoings; // Probably the next edge
     291              :                 }
     292          159 :                 adjacentNodes.insert((*itOfOutgoings)->getToNode());
     293              :             }
     294          273 :             for (EdgeVector::const_iterator itOfIncomings = incomingEdgesOfToNode.begin(); itOfIncomings != incomingEdgesOfToNode.end(); ++itOfIncomings) {
     295          184 :                 adjacentNodes.insert((*itOfIncomings)->getFromNode());
     296              :             }
     297              :             adjacentNodes.erase(to);  // Omit loops
     298           89 :             if (adjacentNodes.size() > 2) {
     299              :                 hasJunction = true;
     300              :             }
     301           47 :         } while (!hasJunction && eOld != e);
     302           56 :         if (!hasJunction) {
     303              :             std::string warningString;
     304           46 :             for (EdgeVector::iterator roadIt = road.begin(); roadIt != road.end(); ++roadIt) {
     305           32 :                 if (roadIt == road.begin()) {
     306           14 :                     warningString += (*roadIt)->getID();
     307              :                 } else {
     308           36 :                     warningString += "," + (*roadIt)->getID();
     309              :                 }
     310              : 
     311           32 :                 NBNode* fromNode = (*roadIt)->getFromNode();
     312              :                 NBNode* toNode = (*roadIt)->getToNode();
     313           32 :                 ec.erase(dc, *roadIt);
     314           32 :                 numRemovedEdges++;
     315           32 :                 if (fromNode->getIncomingEdges().size() == 0 && fromNode->getOutgoingEdges().size() == 0) {
     316              :                     // Node is empty; can be removed
     317           16 :                     erase(fromNode);
     318              :                 }
     319           32 :                 if (toNode->getIncomingEdges().size() == 0 && toNode->getOutgoingEdges().size() == 0) {
     320              :                     // Node is empty; can be removed
     321            7 :                     erase(toNode);
     322              :                 }
     323              :             }
     324           42 :             WRITE_WARNINGF(TL("Removed a road without junctions: %."), warningString);
     325              :         }
     326           56 :     }
     327            5 :     return numRemovedEdges;
     328            5 : }
     329              : 
     330              : 
     331              : int
     332           23 : NBNodeCont::removeComponents(NBDistrictCont& dc, NBEdgeCont& ec, const int numKeep, bool hasPTStops) {
     333              :     myRailComponents.clear();
     334              :     std::vector<std::set<NBEdge*> > components;
     335              :     // need to use ids here to have the same ordering on all platforms
     336              :     std::set<std::string> edgesLeft;
     337         2314 :     for (std::map<std::string, NBEdge*>::const_iterator edgeIt = ec.begin(); edgeIt != ec.end(); ++edgeIt) {
     338         2291 :         edgesLeft.insert(edgeIt->first);
     339              :     }
     340              :     EdgeVector queue;
     341              :     std::set<NBEdge*> toRemove;
     342           23 :     int foundComponents = 0;
     343           23 :     int numRemoved = 0;
     344          102 :     while (!edgesLeft.empty()) {
     345           79 :         queue.push_back(ec.getByID(*edgesLeft.begin()));
     346              :         std::set<NBEdge*> component;
     347         2449 :         while (!queue.empty()) {
     348         2370 :             NBEdge* const e = queue.back();
     349              :             queue.pop_back();
     350              :             component.insert(e);
     351              :             std::vector<EdgeVector> edgeLists;
     352         2370 :             edgeLists.push_back(e->getFromNode()->getOutgoingEdges());
     353         2370 :             edgeLists.push_back(e->getFromNode()->getIncomingEdges());
     354         2370 :             edgeLists.push_back(e->getToNode()->getOutgoingEdges());
     355         2370 :             edgeLists.push_back(e->getToNode()->getIncomingEdges());
     356        11850 :             for (std::vector<EdgeVector>::const_iterator listIt = edgeLists.begin(); listIt != edgeLists.end(); ++listIt) {
     357        26327 :                 for (EdgeVector::const_iterator edgeIt = listIt->begin(); edgeIt != listIt->end(); ++edgeIt) {
     358        16847 :                     std::set<std::string>::iterator leftIt = edgesLeft.find((*edgeIt)->getID());
     359        16847 :                     if (leftIt != edgesLeft.end()) {
     360         2291 :                         queue.push_back(*edgeIt);
     361              :                         edgesLeft.erase(leftIt);
     362              :                     }
     363              :                 }
     364              :             }
     365         2370 :         }
     366           79 :         foundComponents++;
     367              :         std::vector<std::set<NBEdge*> >::iterator cIt;
     368          150 :         for (cIt = components.begin(); cIt != components.end(); ++cIt) {
     369           79 :             if (cIt->size() < component.size()) {
     370              :                 break;
     371              :             }
     372              :         }
     373           79 :         components.insert(cIt, component);
     374           79 :         if ((int)components.size() > numKeep) {
     375              :             bool recheck = false;
     376           54 :             if (hasPTStops) {
     377          116 :                 for (NBEdge* e : components.back()) {
     378          102 :                     SVCPermissions permissions = e->getPermissions();
     379          102 :                     if (isRailway(permissions) || isWaterway(permissions)) {
     380              :                         // recheck for connection to other components via access definitions
     381              :                         recheck = true;
     382              :                         break;
     383              :                     }
     384              :                 }
     385              :             }
     386           16 :             if (!recheck) {
     387           52 :                 toRemove.insert(components.back().begin(), components.back().end());
     388           52 :                 numRemoved++;
     389              :             } else {
     390              :                 std::vector<std::string> edgeIDs;
     391            4 :                 for (NBEdge* e : components.back()) {
     392            2 :                     edgeIDs.push_back(e->getID());
     393              :                 }
     394            2 :                 myRailComponents.push_back(edgeIDs);
     395            2 :             }
     396              :             components.pop_back();
     397              :         }
     398              :     }
     399           23 :     ec.removeRoundaboutEdges(toRemove);
     400          328 :     for (NBEdge* e : toRemove) {
     401              :         NBNode* const fromNode = e->getFromNode();
     402              :         NBNode* const toNode = e->getToNode();
     403          305 :         ec.erase(dc, e);
     404          305 :         if (fromNode->getIncomingEdges().size() == 0 && fromNode->getOutgoingEdges().size() == 0) {
     405          150 :             erase(fromNode);
     406              :         }
     407          305 :         if (toNode->getIncomingEdges().size() == 0 && toNode->getOutgoingEdges().size() == 0) {
     408          110 :             erase(toNode);
     409              :         }
     410              :     }
     411           23 :     if (foundComponents > 1) {
     412           24 :         WRITE_MESSAGEF(TL("Found % components and removed % (% edges)."), toString(foundComponents), toString(numRemoved), toString(toRemove.size()));
     413              :     }
     414           46 :     return (int)toRemove.size();
     415           46 : }
     416              : 
     417              : 
     418              : int
     419           23 : NBNodeCont::removeRailComponents(NBDistrictCont& dc, NBEdgeCont& ec, NBPTStopCont& sc) {
     420              :     std::set<std::string> stopEdges;
     421          122 :     for (const auto& item : sc.getStops()) {
     422           99 :         stopEdges.insert(item.second->getEdgeId());
     423              :     }
     424           23 :     int numRemoved = 0;
     425           23 :     int numRemovedEdges = 0;
     426           25 :     for (auto& component : myRailComponents) {
     427              :         bool keep = false;
     428            3 :         for (std::string edgeID : component) {
     429              :             if (stopEdges.count(edgeID) != 0) {
     430              :                 keep = true;
     431              :                 break;
     432              :             }
     433              :         }
     434              :         if (!keep) {
     435            1 :             numRemoved++;
     436            1 :             numRemovedEdges += (int)component.size();
     437            2 :             for (std::string edgeID : component) {
     438            1 :                 NBEdge* e = ec.retrieve(edgeID);
     439            1 :                 if (e != nullptr) {
     440              :                     NBNode* const fromNode = e->getFromNode();
     441              :                     NBNode* const toNode = e->getToNode();
     442            1 :                     ec.erase(dc, e);
     443            1 :                     if (fromNode->getIncomingEdges().size() == 0 && fromNode->getOutgoingEdges().size() == 0) {
     444            1 :                         erase(fromNode);
     445              :                     }
     446            1 :                     if (toNode->getIncomingEdges().size() == 0 && toNode->getOutgoingEdges().size() == 0) {
     447            1 :                         erase(toNode);
     448              :                     }
     449              :                 }
     450              :             }
     451              :         }
     452              :     }
     453           23 :     if (numRemoved > 0) {
     454            2 :         WRITE_MESSAGEF(TL("Removed % railway components (% edges)."), toString(numRemoved), toString(numRemovedEdges));
     455              :     }
     456           23 :     return numRemoved;
     457              : }
     458              : 
     459              : 
     460              : int
     461         2207 : NBNodeCont::removeUnwishedNodes(NBDistrictCont& dc, NBEdgeCont& ec,
     462              :                                 NBTrafficLightLogicCont& /*tlc*/, NBPTStopCont& sc,
     463              :                                 NBPTLineCont& lc,
     464              :                                 NBParkingCont& pc,
     465              :                                 bool removeGeometryNodes) {
     466         2207 :     const OptionsCont& oc = OptionsCont::getOptions();
     467              :     // load edges that shall not be modified
     468              :     std::set<std::string> edges2keep;
     469         2207 :     if (removeGeometryNodes) {
     470          226 :         if (oc.isSet("geometry.remove.keep-edges.input-file")) {
     471            2 :             NBHelpers::loadEdgesFromFile(oc.getString("geometry.remove.keep-edges.input-file"), edges2keep);
     472              :         }
     473          226 :         if (oc.isSet("geometry.remove.keep-edges.explicit")) {
     474            2 :             const std::vector<std::string> edges = oc.getStringVector("geometry.remove.keep-edges.explicit");
     475              :             edges2keep.insert(edges.begin(), edges.end());
     476            1 :         }
     477              :         // no need to keep pt stop edges, they are remapped later
     478              :         // no need to keep all pt route edges. They are validated again before writing
     479          113 :         pc.addEdges2Keep(oc, edges2keep);
     480          225 :         if (oc.exists("geometry.remove.keep-ptstops") && oc.getBool("geometry.remove.keep-ptstops")) {
     481            1 :             sc.addEdges2Keep(oc, edges2keep);
     482              :         }
     483              :     }
     484              : 
     485              :     std::map<NBEdge*, std::set<NBTrafficLightDefinition*> > tlsLookup;
     486       119159 :     for (auto it = ec.begin(); it != ec.end(); it++) {
     487       116952 :         NBEdge* e = it->second;
     488              :         NBNode* to = e->getToNode();
     489       116952 :         if (to->isTLControlled()) {
     490         6920 :             tlsLookup[e] = to->getControllingTLS();
     491              :         }
     492              :     }
     493         4414 :     const bool outputRemoved = oc.getBool("output.removed-nodes");
     494              :     std::vector<NBNode*> toRemove;
     495        75161 :     for (const auto& i : myNodes) {
     496        72954 :         NBNode* const current = i.second;
     497              :         bool remove = false;
     498              :         // check for completely empty nodes and check for nodes which are only geometry nodes and ask the node whether to join
     499        90375 :         if (current->getEdges().empty() || (removeGeometryNodes && mySplit.count(current) == 0 && current->checkIsRemovable())) {
     500              :             remove = true;
     501              :             // check whether any of the edges must be kept
     502        22378 :             for (NBEdge* const it_edge : current->getEdges()) {
     503        11064 :                 if (edges2keep.find(it_edge->getID()) != edges2keep.end()) {
     504              :                     remove = false;
     505              :                     break;
     506              :                 }
     507              :             }
     508              :         }
     509              :         // remove the node and join the geometries when wished
     510        11353 :         if (!remove) {
     511        61640 :             continue;
     512              :         }
     513        16826 :         for (const std::pair<NBEdge*, NBEdge*>& j : current->getEdgesToJoin()) {
     514         5512 :             NBEdge* const begin = j.first;
     515         5512 :             NBEdge* const continuation = j.second;
     516         5512 :             begin->append(continuation);
     517         5512 :             continuation->getToNode()->replaceIncoming(continuation, begin, 0);
     518              :             auto itTL = tlsLookup.find(continuation);
     519         5512 :             if (itTL != tlsLookup.end()) {
     520         1026 :                 for (NBTrafficLightDefinition* tls : itTL->second) {
     521          513 :                     tls->replaceRemoved(continuation, -1, begin, -1, true);
     522              :                 }
     523          513 :                 tlsLookup[begin] = itTL->second;
     524              :             }
     525         5512 :             sc.replaceEdge(continuation->getID(), { begin });
     526         5512 :             lc.replaceEdge(continuation->getID(), { begin });
     527         5512 :             ec.extract(dc, continuation, true);
     528         5512 :             if (outputRemoved) {
     529            7 :                 begin->updateRemovedNodes(current->getID());
     530              :             }
     531        11314 :         }
     532        11314 :         toRemove.push_back(current);
     533              :     }
     534              :     // erase all
     535        13521 :     for (NBNode* n : toRemove) {
     536        11314 :         extract(n, true);
     537              :     }
     538         2207 :     return (int)toRemove.size();
     539         2207 : }
     540              : 
     541              : 
     542              : void
     543         1593 : NBNodeCont::avoidOverlap() {
     544        34829 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
     545        33236 :         (*i).second->avoidOverlap();
     546              :     }
     547         1593 : }
     548              : 
     549              : 
     550              : // ----------- (Helper) methods for joining nodes
     551              : void
     552          154 : NBNodeCont::generateNodeClusters(double maxDist, NodeClusters& into) const {
     553              :     std::set<NBNode*> visited;
     554        33908 :     for (const auto& i : myNodes) {
     555         5148 :         if (visited.count(i.second) > 0) {
     556        31590 :             continue;
     557              :         }
     558              :         std::vector<NodeAndDist> toProc;
     559        28606 :         toProc.emplace_back(i.second, 0.);
     560              :         NodeSet c;
     561        65005 :         while (!toProc.empty()) {
     562        36399 :             NBNode* const n = toProc.back().first;
     563        36399 :             const double dist = toProc.back().second;
     564              :             toProc.pop_back();
     565         2645 :             if (visited.count(n) > 0) {
     566         7740 :                 continue;
     567              :             }
     568              :             visited.insert(n);
     569              :             bool pureRail = true;
     570              :             bool railAndPeds = true;
     571        71923 :             for (NBEdge* e : n->getEdges()) {
     572        54297 :                 if ((e->getPermissions() & ~(SVC_RAIL_CLASSES | SVC_PEDESTRIAN)) != 0) {
     573              :                     railAndPeds = false;
     574              :                     pureRail = false;
     575              :                     break;
     576              :                 }
     577        38169 :                 if ((e->getPermissions() & ~(SVC_RAIL_CLASSES)) != 0) {
     578              :                     pureRail = false;
     579              :                 }
     580              :             }
     581        33754 :             if (pureRail) {
     582              :                 // do not join pure rail nodes
     583         5095 :                 continue;
     584              :             }
     585              :             c.insert(n);
     586       124335 :             for (NBEdge* e : n->getEdges()) {
     587       191352 :                 NBNode* s = n->hasIncoming(e) ? e->getFromNode() : e->getToNode();
     588              :                 const double length = e->getLoadedLength();
     589              : #ifdef DEBUG_JOINJUNCTIONS
     590              :                 if (DEBUGCOND(s)) {
     591              :                     std::cout << "generateNodeClusters: consider s=" << s->getID()
     592              :                               << " clusterNode=" << n->getID() << " edge=" << e->getID() << " dist=" << dist << " length=" << length << " with cluster " << joinNamedToString(c, ' ') << "\n";
     593              :                 }
     594              : #endif
     595        95676 :                 if (railAndPeds && n->getType() != SumoXMLNodeType::RAIL_CROSSING) {
     596              :                     bool railAndPeds2 = true;
     597       105800 :                     for (NBEdge* e2 : n->getEdges()) {
     598        77521 :                         if ((e2->getPermissions() & ~(SVC_RAIL_CLASSES | SVC_PEDESTRIAN)) != 0) {
     599              :                             railAndPeds2 = false;
     600              :                             break;
     601              :                         }
     602              :                     }
     603        28279 :                     if (railAndPeds2 && s->getType() != SumoXMLNodeType::RAIL_CROSSING) {
     604              :                         // do not join rail/ped nodes unless at a rail crossing
     605              :                         // (neither nodes nor the traffic lights)
     606        74622 :                         continue;
     607              :                     }
     608              :                 }
     609        67675 :                 const bool bothCrossing = n->getType() == SumoXMLNodeType::RAIL_CROSSING && s->getType() == SumoXMLNodeType::RAIL_CROSSING;
     610          658 :                 const bool joinPedCrossings = bothCrossing && e->getPermissions() == SVC_PEDESTRIAN;
     611        24825 :                 if ( // never join pedestrian stuff (unless at a rail crossing
     612        67503 :                     !joinPedCrossings && (
     613        67503 :                         e->getPermissions() == SVC_PEDESTRIAN
     614              :                         // only join edges for regular passenger traffic or edges that are extremely short
     615        60728 :                         || (length > 3 * POSITION_EPS
     616        54705 :                             && (e->getPermissions() & (SVC_PASSENGER | SVC_TRAM)) == 0
     617        18082 :                             && n->getPosition().distanceTo2D(s->getPosition()) > SUMO_const_laneWidth))) {
     618              : #ifdef DEBUG_JOINJUNCTIONS
     619              :                     if (DEBUGCOND(s)) {
     620              :                         std::cout << " ignored s=" << s->getID() << " pedestrian edge=" << e->getID() << " cd=" << n->getPosition().distanceTo2D(s->getPosition()) << "\n";
     621              :                     }
     622              : #endif
     623        24825 :                     continue;
     624              :                 }
     625              :                 // never join rail_crossings with other node types unless the crossing is only for tram
     626         1215 :                 if ((n->getType() == SumoXMLNodeType::RAIL_CROSSING && s->getType() != SumoXMLNodeType::RAIL_CROSSING)
     627        43496 :                         || (n->getType() != SumoXMLNodeType::RAIL_CROSSING && s->getType() == SumoXMLNodeType::RAIL_CROSSING)) {
     628              :                     const SVCPermissions railNoTram = (SVC_RAIL_CLASSES & ~SVC_TRAM);
     629              :                     bool foundRail = false;
     630          776 :                     NBNode* crossingNode = n->getType() == SumoXMLNodeType::RAIL_CROSSING ? n : s;
     631         2844 :                     for (NBEdge* e2 : crossingNode->getIncomingEdges()) {
     632         2084 :                         if ((e2->getPermissions() & railNoTram) != 0) {
     633              :                             foundRail = true;
     634              :                             break;
     635              :                         }
     636              :                     }
     637          776 :                     if (foundRail) {
     638           16 :                         continue;
     639              :                     }
     640              :                 }
     641              :                 // never join rail_crossings via a rail edge
     642        42834 :                 if (bothCrossing && (e->getPermissions() & ~SVC_RAIL_CLASSES) == 0) {
     643          418 :                     continue;
     644              :                 }
     645        42416 :                 if (visited.find(s) != visited.end()) {
     646        21362 :                     continue;
     647              :                 }
     648        21054 :                 if (length + dist < maxDist) {
     649              :                     // don't add long "boring" appendages but always join the whole rail crossing or tls
     650         7793 :                     const bool trueGeomLike = s->geometryLike();
     651         7793 :                     if (trueGeomLike || geometryLikeForClass(s, SVC_VULNERABLE | SVC_DELIVERY)) {
     652         9525 :                         const bool hasTLS = n->isTrafficLight() || s->isTrafficLight();
     653         5070 :                         const double fullLength = e->getGeometry().length2D();
     654         5070 :                         const double length2 = bothCrossing || hasTLS || trueGeomLike ? length : fullLength;
     655         5070 :                         toProc.emplace_back(s, dist + length2);
     656              :                     } else {
     657         2723 :                         toProc.emplace_back(s, 0.);
     658              :                     }
     659              :                 }
     660              :             }
     661              :         }
     662        28606 :         if (c.size() < 2) {
     663              :             continue;
     664              :         }
     665              : #ifdef DEBUG_JOINJUNCTIONS
     666              :         std::cout << " DEBUG: consider cluster " << joinNamedToString(c, ' ') << "\n";
     667              : #endif
     668         2164 :         into.push_back(c);
     669        28606 :     }
     670          154 : }
     671              : 
     672              : 
     673              : bool
     674         5539 : NBNodeCont::geometryLikeForClass(const NBNode* n, SVCPermissions ignored) {
     675              :     EdgeVector allowedIn;
     676              :     EdgeVector allowedOut;
     677        20344 :     for (NBEdge* e : n->getIncomingEdges()) {
     678        14805 :         if ((e->getPermissions() & ~ignored) != 0) {
     679        11155 :             allowedIn.push_back(e);
     680              :         }
     681              :     }
     682        20318 :     for (NBEdge* e : n->getOutgoingEdges()) {
     683        14779 :         if ((e->getPermissions() & ~ignored) != 0) {
     684        11143 :             allowedOut.push_back(e);
     685              :         }
     686              :     }
     687         5539 :     if (allowedIn.size() > 0 && allowedOut.size() > 0) {
     688              :         //std::cout << n->getID() << " geometryLikeForClass=" << n->geometryLike(allowedIn, allowedOut) << " in=" << toString(allowedIn) << " out=" << toString(allowedOut) << "\n";
     689         5125 :         return n->geometryLike(allowedIn, allowedOut);
     690              :     }
     691              :     return true;
     692         5539 : }
     693              : 
     694              : 
     695              : void
     696           16 : NBNodeCont::addJoinExclusion(const std::vector<std::string>& ids) {
     697          116 :     for (const std::string& nodeID : ids) {
     698              :         // error handling has to take place here since joinExclusions could be
     699              :         // loaded from multiple files / command line
     700              :         if (myJoined.count(nodeID) > 0) {
     701            0 :             WRITE_WARNINGF(TL("Ignoring join exclusion for junction '%' since it already occurred in a list of nodes to be joined."), nodeID);
     702              :         } else {
     703              :             myJoinExclusions.insert(nodeID);
     704              :         }
     705              :     }
     706           16 : }
     707              : 
     708              : 
     709              : std::string
     710            3 : NBNodeCont::createUnusedID(const std::string& base, const std::string& sep) {
     711              :     std::string result = base;
     712              :     int i = 0;
     713            4 :     while (myNodes.find(result) != myNodes.end()) {
     714            3 :         result = base + sep + toString(i++);
     715              :     }
     716            3 :     return result;
     717              : }
     718              : 
     719              : 
     720              : std::string
     721          893 : NBNodeCont::createClusterId(const std::set<std::string>& cluster, const std::string& prefix) {
     722          893 :     int maxIds = OptionsCont::getOptions().getInt("max-join-ids");
     723          893 :     if (maxIds <= 0) {
     724            0 :         maxIds = (int)cluster.size();
     725              :     }
     726          893 :     if ((int)cluster.size() > maxIds) {
     727              :         auto clusterIt = cluster.begin();
     728              :         std::string result = prefix + *clusterIt;
     729          516 :         for (int i = 1; i < maxIds; i++) {
     730              :             ++clusterIt;
     731          774 :             result += "_" + *clusterIt;
     732              :         }
     733          258 :         return result + "_#" + toString((int)cluster.size() - maxIds) + "more";
     734              :     }
     735         1528 :     return prefix + joinToString(cluster, "_");
     736              : }
     737              : 
     738              : 
     739              : void
     740           11 : NBNodeCont::addCluster2Join(const std::set<std::string>& cluster, NBNode* node, const bool resetConnections) {
     741              :     // error handling has to take place here since joins could be loaded from multiple files
     742           11 :     JoinCluster join{{}, node, resetConnections};
     743           99 :     for (std::string nodeID : cluster) {
     744              :         if (myJoinExclusions.count(nodeID) > 0) {
     745            0 :             WRITE_WARNINGF(TL("Ignoring join-cluster because junction '%' was already excluded from joining."), nodeID);
     746            0 :             return;
     747              :         } else if (myJoined.count(nodeID) > 0) {
     748            0 :             WRITE_WARNINGF(TL("Ignoring join-cluster because junction '%' already occurred in another join-cluster."), nodeID);
     749            0 :             return;
     750              :         } else {
     751           88 :             if (retrieve(nodeID) != nullptr) {
     752              :                 join.cluster.insert(nodeID);
     753              :             } else {
     754            0 :                 WRITE_ERRORF(TL("Unknown junction '%' in join-cluster."), nodeID);
     755              :             }
     756              :         }
     757              :     }
     758           11 :     if (join.cluster.size() > 1) {
     759           10 :         myJoined.insert(join.cluster.begin(), join.cluster.end());
     760           10 :         myClusters2Join.emplace_back(join);
     761              :     } else {
     762            3 :         WRITE_WARNINGF(TL("Ignoring join-cluster '%' because it has size '%'."), node->getID(), join.cluster.size());
     763              :     }
     764              : }
     765              : 
     766              : 
     767              : int
     768         2206 : NBNodeCont::joinLoadedClusters(NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tlc) {
     769              :     int numJoined = 0;
     770         2216 :     for (JoinCluster& item : myClusters2Join) {
     771              :         // verify loaded cluster
     772              :         NodeSet cluster;
     773           97 :         for (std::string nodeID : item.cluster) {
     774           87 :             NBNode* node = retrieve(nodeID);
     775           87 :             if (node == nullptr) {
     776            0 :                 WRITE_ERRORF(TL("unknown junction '%' while joining."), nodeID);
     777              :             } else {
     778              :                 cluster.insert(node);
     779              :             }
     780              :         }
     781           10 :         if (cluster.size() > 1) {
     782           10 :             joinNodeCluster(cluster, dc, ec, tlc, item.node, item.resetConnections);
     783           10 :             numJoined++;
     784           10 :             myJoinExclusions.insert(item.node->getID());
     785              :         }
     786              :     }
     787              :     myClusters2Join.clear(); // make save for recompute
     788         2206 :     return numJoined;
     789              : }
     790              : 
     791              : 
     792              : int
     793          114 : NBNodeCont::joinJunctions(double maxDist, NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tlc, NBPTStopCont& sc) {
     794              : #ifdef DEBUG_JOINJUNCTIONS
     795              :     std::cout << "joinJunctions...\n";
     796              : #endif
     797              :     NodeClusters cands;
     798              :     NodeClusters clusters;
     799              :     std::map<const NBNode*, std::vector<NBNode*> > ptStopEnds;
     800              :     // check for stop edges within the cluster
     801         1159 :     for (const auto& stopIt : sc.getStops()) {
     802         1045 :         NBEdge* edge = ec.retrieve(stopIt.second->getEdgeId());
     803         1045 :         if (edge != nullptr) {
     804          971 :             ptStopEnds[edge->getFromNode()].push_back(edge->getToNode());
     805              :         }
     806              :     }
     807          114 :     generateNodeClusters(maxDist, cands);
     808         1624 :     for (NodeSet& cluster : cands) {
     809              : #ifdef DEBUG_JOINJUNCTIONS
     810              :         gDebugFlag1 = false;
     811              :         for (NBNode* n : cluster) {
     812              :             if (DEBUGCOND(n)) {
     813              :                 gDebugFlag1 = true;
     814              :             }
     815              :         }
     816              : #endif
     817              :         // remove join exclusions
     818         6733 :         for (NodeSet::iterator j = cluster.begin(); j != cluster.end();) {
     819              :             NodeSet::iterator check = j;
     820              :             ++j;
     821         5223 :             if (myJoinExclusions.count((*check)->getID()) > 0) {
     822              :                 cluster.erase(check);
     823              :             }
     824              :         }
     825         1510 :         std::string origCluster = joinNamedToString(cluster, ',');
     826              :         // remove nodes that can be eliminated by geometry.remove
     827         1510 :         pruneClusterFringe(cluster, maxDist);
     828         1510 :         if (cluster.size() < 2) {
     829          719 :             continue;
     830              :         }
     831              :         // remove nodes that are part of a bypass lane (typically for turning right without waiting at a traffic light)
     832          791 :         pruneSlipLaneNodes(cluster, maxDist);
     833          791 :         if (cluster.size() < 2) {
     834            9 :             WRITE_WARNINGF(TL("Not joining junctions % (%)."), origCluster, "slip lane");
     835            3 :             continue;
     836              :         }
     837          788 :         origCluster = joinNamedToString(cluster, ',');
     838          788 :         NBNode* tryRemove = nullptr;
     839              :         std::string reason;
     840              :         std::string origReason;
     841              :         // pruneLongEdges might remove too much, so we check first to have a fallback with the circles
     842          788 :         bool feasible = feasibleCluster(cluster, ptStopEnds, maxDist, origReason, tryRemove);
     843          788 :         if (feasible && ((int)cluster.size() - pruneLongEdges(cluster, maxDist, true) < 2)) {
     844              :             origReason = "long edge";
     845              :             feasible = false;
     846              :         }
     847          783 :         if (!feasible) {
     848              : #ifdef DEBUG_JOINJUNCTIONS
     849              :             if (gDebugFlag1) {
     850              :                 std::cout << "   try to reduce to 4-circle nodes=" << joinNamedToString(cluster, ',') << "\n";
     851              :             }
     852              : #endif
     853          122 :             if (reduceToCircle(cluster, 4, cluster, maxDist)) {
     854            1 :                 feasible = feasibleCluster(cluster, ptStopEnds, maxDist, reason, tryRemove);
     855            1 :                 if (feasible) {
     856            3 :                     WRITE_WARNINGF(TL("Reducing junction cluster % (%)."), origCluster, origReason);
     857              :                 }
     858              :             }
     859              :         }
     860           61 :         if (!feasible) {
     861              : #ifdef DEBUG_JOINJUNCTIONS
     862              :             if (gDebugFlag1) {
     863              :                 std::cout << "   try to reduce to 2-circle nodes=" << joinNamedToString(cluster, ',') << "\n";
     864              :             }
     865              : #endif
     866          120 :             if (reduceToCircle(cluster, 2, cluster, maxDist)) {
     867           20 :                 feasible = feasibleCluster(cluster, ptStopEnds, maxDist, reason, tryRemove);
     868           20 :                 if (feasible) {
     869           54 :                     WRITE_WARNINGF(TL("Reducing junction cluster % (%)."), origCluster, origReason);
     870              :                 }
     871              :             }
     872              :         }
     873          795 :         while (!feasible && tryRemove != nullptr) {
     874              :             cluster.erase(tryRemove);
     875            7 :             pruneClusterFringe(cluster, maxDist);
     876            7 :             tryRemove = nullptr;
     877            7 :             feasible = feasibleCluster(cluster, ptStopEnds, maxDist, reason, tryRemove);
     878            7 :             if (feasible) {
     879           12 :                 WRITE_WARNINGF(TL("Reducing junction cluster % (%)."), origCluster, origReason);
     880              :             }
     881              :         }
     882          788 :         if (cluster.size() < 2) {
     883            3 :             WRITE_WARNINGF(TL("Not joining junctions % (%)."), origCluster, "after reduction");
     884            1 :             continue;
     885              :         }
     886              :         // avoid removal of long edges (must have been added via an alternative path).
     887          787 :         const int numPruned = pruneLongEdges(cluster, maxDist);
     888          787 :         if (cluster.size() < 2) {
     889            0 :             WRITE_WARNINGF(TL("Not joining junctions % (%)."), origCluster, "long edge");
     890            0 :             continue;
     891              :         }
     892              :         // after pruning long edges we have to recheck
     893          787 :         if (numPruned > 0) {
     894            5 :             pruneClusterFringe(cluster, maxDist);
     895            5 :             if (cluster.size() < 2) {
     896            0 :                 WRITE_WARNINGF(TL("Not joining junctions % (%)."), origCluster, "long edge");
     897            0 :                 continue;
     898              :             }
     899            5 :             pruneSlipLaneNodes(cluster, maxDist);
     900            5 :             if (cluster.size() < 2) {
     901            0 :                 WRITE_WARNINGF(TL("Not joining junctions % (%)."), origCluster, "slip lane");
     902            0 :                 continue;
     903              :             }
     904              :         }
     905          787 :         feasible = feasibleCluster(cluster, ptStopEnds, maxDist, origReason, tryRemove);
     906          787 :         if (!feasible) {
     907          117 :             WRITE_WARNINGF(TL("Not joining junctions % (%)."), origCluster, origReason);
     908           39 :             continue;
     909              :         }
     910              :         // compute all connected components of this cluster
     911              :         // (may be more than 1 if intermediate nodes were removed)
     912              :         NodeClusters components;
     913         3264 :         for (NBNode* current : cluster) {
     914              :             // merge all connected components into newComp
     915              :             NodeSet newComp;
     916              :             //std::cout << "checking connectivity for " << current->getID() << "\n";
     917              :             newComp.insert(current);
     918         5093 :             for (NodeClusters::iterator it_comp = components.begin(); it_comp != components.end();) {
     919              :                 NodeClusters::iterator check = it_comp;
     920              :                 //std::cout << "   connected with " << toString(*check) << "?\n";
     921              :                 bool connected = false;
     922         4985 :                 for (NBNode* k : *check) {
     923         4176 :                     if (current->getConnectionTo(k) != nullptr || k->getConnectionTo(current) != nullptr) {
     924              :                         //std::cout << "joining with connected component " << toString(*check) << "\n";
     925         1768 :                         newComp.insert((*check).begin(), (*check).end());
     926              :                         it_comp = components.erase(check);
     927              :                         connected = true;
     928              :                         break;
     929              :                     }
     930              :                 }
     931              :                 if (!connected) {
     932              :                     it_comp++;
     933              :                 }
     934              :             }
     935              :             //std::cout << "adding new component " << toString(newComp) << "\n";
     936         2516 :             components.push_back(newComp);
     937              :         }
     938         1496 :         for (NodeClusters::iterator it_comp = components.begin(); it_comp != components.end(); ++it_comp) {
     939          748 :             if ((*it_comp).size() > 1) {
     940              :                 //std::cout << "adding cluster " << toString(*it_comp) << "\n";
     941          748 :                 clusters.push_back(*it_comp);
     942              :             }
     943              :         }
     944              : #ifdef DEBUG_JOINJUNCTIONS
     945              :         gDebugFlag1 = false;
     946              : #endif
     947          748 :     }
     948          114 :     joinNodeClusters(clusters, dc, ec, tlc);
     949          228 :     return (int)clusters.size();
     950          114 : }
     951              : 
     952              : 
     953              : int
     954           13 : NBNodeCont::joinSameJunctions(NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tlc, double maxDist) {
     955              : #ifdef DEBUG_JOINJUNCTIONS
     956              :     std::cout << "joinSameJunctions...\n";
     957              : #endif
     958              :     std::set<NBNode*> checked;
     959              :     NodeClusters clusters;
     960         1261 :     for (auto& item : myNodes) {
     961         1248 :         NBNode* n = item.second;
     962         1248 :         if (myJoinExclusions.count(item.first) > 0) {
     963            0 :             continue;
     964              :         }
     965         1248 :         std::vector<NBNode*> nearby = retrieveByPos(n->getPosition(), maxDist);
     966              :         NodeSet cluster;
     967         2652 :         for (NBNode* n2 : nearby) {
     968              :             if (myJoinExclusions.count(n2->getID()) == 0 && checked.count(n2) == 0) {
     969              :                 cluster.insert(n2);
     970              :             }
     971              :         }
     972         1248 :         if (cluster.size() > 1) {
     973           66 :             checked.insert(cluster.begin(), cluster.end());
     974           66 :             clusters.push_back(cluster);
     975              :         }
     976         1248 :     }
     977           13 :     joinNodeClusters(clusters, dc, ec, tlc, true);
     978           13 :     return (int)clusters.size();
     979           13 : }
     980              : 
     981              : void
     982         1577 : NBNodeCont::pruneClusterFringe(NodeSet& cluster, double maxDist, bool remove2TLS) const {
     983              : #ifdef DEBUG_JOINJUNCTIONS
     984              :     if (gDebugFlag1) {
     985              :         std::cout << "pruning cluster=" << joinNamedToString(cluster, ' ') << "\n";
     986              :     }
     987              : #endif
     988              :     // iteratively remove the fringe
     989              :     NodeSet geometryLikeTLS;
     990              :     bool pruneFringe = true;
     991              :     bool pruneNoisyFringe = false;
     992              :     // collect nodes that shall be joined due to distance but are not connected
     993              :     // to the cluster for passenger traffic
     994         6141 :     while (pruneFringe) {
     995              :         pruneFringe = false;
     996        15257 :         for (NodeSet::iterator j = cluster.begin(); j != cluster.end();) {
     997              :             NodeSet::iterator check = j;
     998        10693 :             NBNode* n = *check;
     999              :             ++j;
    1000              : 
    1001              :             // compute clusterDist for node (length of shortest edge which connects this node to the cluster)
    1002              :             double clusterDist = std::numeric_limits<double>::max();
    1003              :             bool touchingCluster = false;
    1004        33766 :             for (EdgeVector::const_iterator it_edge = n->getOutgoingEdges().begin(); it_edge != n->getOutgoingEdges().end(); ++it_edge) {
    1005        23073 :                 NBNode* neighbor = (*it_edge)->getToNode();
    1006              :                 if (cluster.count(neighbor) != 0) {
    1007              :                     clusterDist = MIN2(clusterDist, (*it_edge)->getLoadedLength());
    1008        11177 :                     touchingCluster |= n->getPosition().distanceTo2D(neighbor->getPosition()) <= SUMO_const_laneWidth;
    1009              :                 }
    1010              :             }
    1011        33875 :             for (EdgeVector::const_iterator it_edge = n->getIncomingEdges().begin(); it_edge != n->getIncomingEdges().end(); ++it_edge) {
    1012        23182 :                 NBNode* neighbor = (*it_edge)->getFromNode();
    1013              :                 if (cluster.count(neighbor) != 0) {
    1014              :                     clusterDist = MIN2(clusterDist, (*it_edge)->getLoadedLength());
    1015        11090 :                     touchingCluster |= n->getPosition().distanceTo2D(neighbor->getPosition()) <= SUMO_const_laneWidth;
    1016              :                 }
    1017              :             }
    1018              :             // remove geometry-like nodes at fringe of the cluster
    1019              :             // (they have 1 neighbor in the cluster and at most 1 neighbor outside the cluster)
    1020              :             std::set<NBNode*> outsideNeighbors;
    1021              :             std::set<NBNode*> clusterNeighbors;
    1022              :             const double pedestrianFringeThreshold = 0.3;
    1023        56948 :             for (NBEdge* e : n->getEdges()) {
    1024        69328 :                 NBNode* neighbor = e->getFromNode() == n ? e->getToNode() : e->getFromNode();
    1025              :                 if (cluster.count(neighbor) == 0) {
    1026        23988 :                     if ((e->getPermissions() & SVC_PASSENGER) != 0
    1027         9582 :                             || isRailway(e->getPermissions()) // join railway crossings
    1028         7358 :                             || (clusterDist <= pedestrianFringeThreshold
    1029         5110 :                                 && (!pruneNoisyFringe
    1030         1979 :                                     || isForVulnerableModes(e->getPermissions())
    1031              :                                     // permit joining small opposite merges
    1032          797 :                                     || getDiameter(cluster) < maxDist
    1033          170 :                                     || cluster.size() == 2))
    1034        26342 :                             || touchingCluster) {
    1035              :                         outsideNeighbors.insert(neighbor);
    1036              :                     }
    1037              :                 } else {
    1038              :                     clusterNeighbors.insert(neighbor);
    1039              :                 }
    1040              :             }
    1041              : #ifdef DEBUG_JOINJUNCTIONS
    1042              :             if (gDebugFlag1) std::cout << "  check n=" << n->getID()
    1043              :                                            << " clusterDist=" << clusterDist
    1044              :                                            << " cd<th=" << (clusterDist <= pedestrianFringeThreshold)
    1045              :                                            << " touching=" << touchingCluster
    1046              :                                            << " out=" << joinNamedToString(outsideNeighbors, ',')
    1047              :                                            << " in=" << joinNamedToString(clusterNeighbors, ',')
    1048              :                                            << " dia=" << getDiameter(cluster)
    1049              :                                            << "\n";
    1050              : #endif
    1051              :             if (clusterNeighbors.size() == 0
    1052        10693 :                     || (outsideNeighbors.size() <= 1
    1053         5622 :                         && clusterNeighbors.size() == 1
    1054         2272 :                         && !(n->isTLControlled() /*|| n->hadSignal()*/))) {
    1055              :                 cluster.erase(check);
    1056              :                 pruneFringe = true; // other nodes could belong to the fringe now
    1057              : #ifdef DEBUG_JOINJUNCTIONS
    1058              :                 if (gDebugFlag1) {
    1059              :                     std::cout << "  pruned n=" << n->getID() << "\n";
    1060              :                 }
    1061              : #endif
    1062         8297 :             } else if (outsideNeighbors.size() <= 1 && clusterNeighbors.size() == 1) {
    1063              :                 geometryLikeTLS.insert(n);
    1064              :             }
    1065              :         }
    1066         4564 :         if (!pruneFringe && !pruneNoisyFringe) {
    1067              :             // run once more and prune more things (with a look at cluster size)
    1068              :             pruneFringe = true;
    1069              :             pruneNoisyFringe = true;
    1070              : 
    1071              :         }
    1072              :     }
    1073         1577 :     if (remove2TLS && geometryLikeTLS.size() == cluster.size()) {
    1074              :         cluster.clear();
    1075              :     }
    1076         1577 : }
    1077              : 
    1078              : double
    1079          797 : NBNodeCont::getDiameter(const NodeSet& cluster) {
    1080              :     double result = 0;
    1081         3007 :     for (const NBNode* n1 : cluster) {
    1082        12308 :         for (const NBNode* n2 : cluster) {
    1083              :             result = MAX2(result, n1->getPosition().distanceTo2D(n2->getPosition()));
    1084              :         }
    1085              :     }
    1086          797 :     return result;
    1087              : }
    1088              : 
    1089              : int
    1090         1519 : NBNodeCont::pruneLongEdges(NodeSet& cluster, double maxDist, const bool dryRun) {
    1091              :     std::set<NBNode*> toRemove;
    1092              :     int maxPassengerLanes = 0;
    1093         6643 :     for (NBNode* n : cluster) {
    1094        28837 :         for (NBEdge* edge : n->getEdges()) {
    1095        23713 :             maxPassengerLanes = MAX2(maxPassengerLanes, edge->getNumLanesThatAllow(SVC_PASSENGER));
    1096              :         }
    1097              :     }
    1098         6643 :     for (NBNode* n : cluster) {
    1099        16956 :         for (NBEdge* edge : n->getOutgoingEdges()) {
    1100              :             // we must track the edge length across geometry like nodes
    1101              :             // Also, intersections that are geometry-like
    1102              :             // from the perspective of passenger traffic should be tracked across
    1103              :             std::vector<NBNode*> passed;
    1104              :             double length = 0;
    1105              :             NBEdge* cur = edge;
    1106        11832 :             NBNode* to = edge->getToNode();
    1107        11832 :             while (cluster.count(to) != 0) {
    1108         6841 :                 length += cur->getLoadedLength();
    1109         6841 :                 bool goStraight = (std::find(passed.begin(), passed.end(), to) == passed.end()
    1110         6841 :                                    && (edge->getPermissions() & SVC_PASSENGER) != 0
    1111        12166 :                                    && to->geometryLike(
    1112        12166 :                                        NBEdge::filterByPermissions(to->getIncomingEdges(), SVC_PASSENGER),
    1113        12166 :                                        NBEdge::filterByPermissions(to->getOutgoingEdges(), SVC_PASSENGER)));
    1114         6841 :                 passed.push_back(to);
    1115         6841 :                 if (goStraight) {
    1116         2738 :                     cur = cur->getStraightContinuation(SVC_PASSENGER);
    1117         2738 :                     if (cur != nullptr) {
    1118         2716 :                         to = cur->getToNode();
    1119              :                     } else {
    1120              :                         break;
    1121              :                     }
    1122              :                 } else {
    1123              :                     break;
    1124              :                 }
    1125              :             }
    1126              :             // allow higher threshold at larger junctions
    1127        11832 :             double longThreshold = maxDist + SUMO_const_laneWidth * MAX2(0, maxPassengerLanes - 1);
    1128              : #ifdef DEBUG_JOINJUNCTIONS
    1129              :             if (gDebugFlag1) {
    1130              :                 std::cout << "check edge length " << edge->getID() << " (" << length << ", passed=" << passed.size() << ", max=" << longThreshold << ")\n";
    1131              :             }
    1132              : #endif
    1133        11832 :             if (length > longThreshold) {
    1134              :                 // we found an edge that should not be removed. Maybe we can
    1135              :                 // still keep the start or end in the cluster
    1136              :                 // (keep the start if the end can be removed and vice versa)
    1137           35 :                 const bool keepStart = getClusterNeighbors(passed.back(), longThreshold, cluster).size() == 1;
    1138           35 :                 const bool keepEnd = !keepStart && getClusterNeighbors(n, longThreshold, cluster).size() == 1;
    1139              : #ifdef DEBUG_JOINJUNCTIONS
    1140              :                 if (gDebugFlag1) {
    1141              :                     std::cout << "node=" << n->getID() << " long edge " << edge->getID() << " (" << length << ", passed=" << toString(passed) << ", max=" << longThreshold << ") keepStart=" << keepStart << " keepEnd=" << keepEnd << "\n";
    1142              :                 }
    1143              : #endif
    1144           35 :                 if (!keepStart) {
    1145              :                     toRemove.insert(n);
    1146              :                 }
    1147              :                 toRemove.insert(passed.begin(), passed.end() - 1);
    1148           35 :                 if (!keepEnd) {
    1149              :                     toRemove.insert(passed.back());
    1150              :                 }
    1151              : 
    1152              :             }
    1153        11832 :         }
    1154              :     }
    1155         1519 :     if (!dryRun) {
    1156          798 :         for (std::set<NBNode*>::iterator j = toRemove.begin(); j != toRemove.end(); ++j) {
    1157              :             cluster.erase(*j);
    1158              :         }
    1159              :     }
    1160         1519 :     return (int)toRemove.size();
    1161              : }
    1162              : 
    1163              : 
    1164              : NodeSet
    1165           49 : NBNodeCont::getClusterNeighbors(const NBNode* n, double longThreshold, NodeSet& cluster) {
    1166              :     NodeSet result;
    1167          323 :     for (NBEdge* e : n->getEdges()) {
    1168          274 :         if (e->getLength() > longThreshold) {
    1169           94 :             continue;
    1170              :         }
    1171          273 :         NBNode* neighbor = e->getFromNode() == n ? e->getToNode() : e->getFromNode();
    1172              :         if (cluster.count(neighbor) != 0) {
    1173              :             result.insert(neighbor);
    1174              :         }
    1175              :     }
    1176           49 :     return result;
    1177              : }
    1178              : 
    1179              : 
    1180              : void
    1181          796 : NBNodeCont::pruneSlipLaneNodes(NodeSet& cluster, double maxDist) const {
    1182              : #ifdef DEBUG_JOINJUNCTIONS
    1183              :     if (gDebugFlag1) {
    1184              :         std::cout << "pruning slip-lanes at cluster=" << joinNamedToString(cluster, ' ') << "\n";
    1185              :     }
    1186              : #endif
    1187              :     // fringe has already been removed
    1188          796 :     if (cluster.size() <= 2) {
    1189          469 :         return;
    1190              :     }
    1191              :     NodeSet toRemove;
    1192         2208 :     for (NBNode* n : cluster) {
    1193              :         EdgeVector outgoing;
    1194              :         double inAngle;
    1195              :         // find slip lanes where the start is part of the cluster
    1196         1881 :         if (maybeSlipLaneStart(n, outgoing, inAngle)) {
    1197              :             // potential slip lane start but we don't know which of the outgoing edges it is
    1198              : #ifdef DEBUG_JOINJUNCTIONS
    1199              :             if (gDebugFlag1) {
    1200              :                 std::cout << "   candidate slip-lane start=" << n->getID() << " outgoing=" << toString(outgoing) << "\n";
    1201              :             }
    1202              : #endif
    1203          213 :             for (NBEdge* contEdge : outgoing) {
    1204          142 :                 if ((contEdge->getPermissions() & SVC_PASSENGER) == 0) {
    1205            0 :                     continue;
    1206              :                 }
    1207          142 :                 double slipLength = contEdge->getLength();
    1208          142 :                 NBNode* cont = contEdge->getToNode();
    1209              :                 NodeSet cands;
    1210              :                 cands.insert(n);
    1211          287 :                 while (isSlipLaneContinuation(cont) && slipLength < MAX_SLIPLANE_LENGTH) {
    1212              :                     if (cands.count(cont) != 0) {
    1213              :                         break; // circle, should not happen
    1214              :                     }
    1215              :                     cands.insert(cont);
    1216              : #ifdef DEBUG_JOINJUNCTIONS
    1217              :                     if (gDebugFlag1) {
    1218              :                         std::cout << "   candidate slip-lane cont=" << cont->getID() << "\n";
    1219              :                     }
    1220              : #endif
    1221          145 :                     NBEdge* next = cont->getOutgoingEdges().front();
    1222          145 :                     slipLength += next->getLength();
    1223          145 :                     cont = next->getToNode();
    1224              :                 }
    1225              : #ifdef DEBUG_JOINJUNCTIONS
    1226              :                 if (gDebugFlag1) {
    1227              :                     std::cout << "   candidate slip-lane end=" << cont->getID() << " slipLength=" << slipLength << "\n";
    1228              :                 }
    1229              : #endif
    1230          180 :                 if (cont->getIncomingEdges().size() >= 2 && cont->getOutgoingEdges().size() == 1 &&
    1231              :                         // slip lanes are for turning so there needs to be a sufficient angle
    1232           38 :                         abs(NBHelpers::relAngle(inAngle, cont->getOutgoingEdges().front()->getAngleAtNode(cont))) > 45) {
    1233              :                     // check whether the other continuation at n is also connected to the sliplane end
    1234           31 :                     const NBEdge* const otherEdge = (contEdge == outgoing.front() ? outgoing.back() : outgoing.front());
    1235              :                     NodeSet visited;
    1236              :                     visited.insert(n);
    1237              :                     std::vector<NodeAndDist> toProc;
    1238           31 :                     toProc.push_back(std::make_pair(otherEdge->getToNode(), otherEdge->getLength()));
    1239              :                     bool found = false;
    1240          138 :                     while (!toProc.empty()) {
    1241          130 :                         NodeAndDist nodeAndDist = toProc.back();
    1242          130 :                         NBNode* cont2 = nodeAndDist.first;
    1243              :                         double dist = nodeAndDist.second;
    1244              : #ifdef DEBUG_JOINJUNCTIONS
    1245              :                         if (gDebugFlag1) {
    1246              :                             std::cout << "   search alternative cont2=" << cont2->getID() << " dist=" << dist << "\n";
    1247              :                         }
    1248              : #endif
    1249              :                         toProc.pop_back();
    1250          130 :                         if (visited.find(cont2) != visited.end()) {
    1251           10 :                             continue;
    1252              :                         }
    1253              :                         visited.insert(cont2);
    1254          120 :                         if (cont2 == cont) {
    1255              :                             found = true;
    1256           23 :                             break;
    1257              :                         }
    1258          232 :                         for (NBEdge* e : cont2->getOutgoingEdges()) {
    1259          135 :                             const double dist2 = dist + e->getLength();
    1260          135 :                             if (dist2 < slipLength * 2 && (e->getPermissions() & SVC_PASSENGER) != 0) {
    1261          118 :                                 toProc.push_back(std::make_pair(e->getToNode(), dist2));
    1262              :                             }
    1263              :                         }
    1264              :                     }
    1265              :                     if (found) {
    1266              :                         // found slip lane
    1267              :                         cands.insert(cont);
    1268           23 :                         toRemove.insert(cands.begin(), cands.end());
    1269              : #ifdef DEBUG_JOINJUNCTIONS
    1270              :                         if (gDebugFlag1) {
    1271              :                             std::cout << "   found slip-lane with nodes=" << joinNamedToString(cands, ' ') << "\n";
    1272              :                         }
    1273              : #endif
    1274              :                     }
    1275           31 :                 }
    1276              :             }
    1277              :         }
    1278              : 
    1279              :         EdgeVector incoming;
    1280              :         double outAngle;
    1281              :         // find slip lanes where the end is part of the cluster
    1282         1881 :         if (maybeSlipLaneEnd(n, incoming, outAngle)) {
    1283              :             // potential slip lane end but we don't know which of the incoming edges it is
    1284              : #ifdef DEBUG_JOINJUNCTIONS
    1285              :             if (gDebugFlag1) {
    1286              :                 std::cout << "   candidate slip-lane end=" << n->getID() << " incoming=" << toString(incoming) << "\n";
    1287              :             }
    1288              : #endif
    1289          273 :             for (NBEdge* contEdge : incoming) {
    1290          182 :                 if ((contEdge->getPermissions() & SVC_PASSENGER) == 0) {
    1291            0 :                     continue;
    1292              :                 }
    1293          182 :                 double slipLength = contEdge->getLength();
    1294          182 :                 NBNode* cont = contEdge->getFromNode();
    1295              :                 NodeSet cands;
    1296              :                 cands.insert(n);
    1297          365 :                 while (isSlipLaneContinuation(cont) && slipLength < MAX_SLIPLANE_LENGTH) {
    1298              :                     if (cands.count(cont) != 0) {
    1299              :                         break; // circle, should not happen
    1300              :                     }
    1301              :                     cands.insert(cont);
    1302              : #ifdef DEBUG_JOINJUNCTIONS
    1303              :                     if (gDebugFlag1) {
    1304              :                         std::cout << "   candidate slip-lane cont=" << cont->getID() << "\n";
    1305              :                     }
    1306              : #endif
    1307          183 :                     NBEdge* next = cont->getIncomingEdges().front();
    1308          183 :                     slipLength += next->getLength();
    1309          183 :                     cont = next->getFromNode();
    1310              :                 }
    1311              : #ifdef DEBUG_JOINJUNCTIONS
    1312              :                 if (gDebugFlag1) {
    1313              :                     std::cout << "   candidate slip-lane start=" << cont->getID() << " slipLength=" << slipLength << "\n";
    1314              :                 }
    1315              : #endif
    1316          235 :                 if (cont->getOutgoingEdges().size() >= 2 && cont->getIncomingEdges().size() == 1 &&
    1317              :                         // slip lanes are for turning so there needs to be a sufficient angle
    1318           53 :                         abs(NBHelpers::relAngle(outAngle, cont->getIncomingEdges().front()->getAngleAtNode(cont))) > 45) {
    1319              :                     // check whether the other continuation at n is also connected to the sliplane end
    1320           38 :                     const NBEdge* const otherEdge = (contEdge == incoming.front() ? incoming.back() : incoming.front());
    1321              :                     NodeSet visited;
    1322              :                     visited.insert(n);
    1323              :                     std::vector<NodeAndDist> toProc;
    1324           38 :                     toProc.push_back(std::make_pair(otherEdge->getFromNode(), otherEdge->getLength()));
    1325              :                     bool found = false;
    1326          377 :                     while (!toProc.empty()) {
    1327          362 :                         NodeAndDist nodeAndDist = toProc.back();
    1328          362 :                         NBNode* cont2 = nodeAndDist.first;
    1329              :                         double dist = nodeAndDist.second;
    1330              : #ifdef DEBUG_JOINJUNCTIONS
    1331              :                         if (gDebugFlag1) {
    1332              :                             std::cout << "   search alternative cont2=" << cont2->getID() << " dist=" << dist << "\n";
    1333              :                         }
    1334              : #endif
    1335              :                         toProc.pop_back();
    1336          362 :                         if (visited.find(cont2) != visited.end()) {
    1337           61 :                             continue;
    1338              :                         }
    1339              :                         visited.insert(cont2);
    1340          301 :                         if (cont2 == cont) {
    1341              :                             found = true;
    1342           23 :                             break;
    1343              :                         }
    1344          708 :                         for (NBEdge* e : cont2->getIncomingEdges()) {
    1345          430 :                             const double dist2 = dist + e->getLength();
    1346          430 :                             if (dist2 < slipLength * 2 && (e->getPermissions() & SVC_PASSENGER) != 0) {
    1347          343 :                                 toProc.push_back(std::make_pair(e->getFromNode(), dist2));
    1348              :                             }
    1349              :                         }
    1350              :                     }
    1351              :                     if (found) {
    1352              :                         // found slip lane
    1353              :                         cands.insert(cont);
    1354           23 :                         toRemove.insert(cands.begin(), cands.end());
    1355              : #ifdef DEBUG_JOINJUNCTIONS
    1356              :                         if (gDebugFlag1) {
    1357              :                             std::cout << "   found slip-lane start with nodes=" << joinNamedToString(cands, ' ') << "\n";
    1358              :                         }
    1359              : #endif
    1360              :                     }
    1361           38 :                 }
    1362              :             }
    1363              :         }
    1364              : 
    1365              : 
    1366              : 
    1367         1881 :     }
    1368              :     int numRemoved = 0;
    1369          421 :     for (NBNode* n : toRemove) {
    1370           94 :         numRemoved += (int)cluster.erase(n);
    1371              :     }
    1372          327 :     if (numRemoved > 0) {
    1373              : #ifdef DEBUG_JOINJUNCTIONS
    1374              :         if (gDebugFlag1) {
    1375              :             std::cout << "   removed " << numRemoved << " nodes from cluster: " << joinNamedToString(toRemove, ' ') << "\n";
    1376              :         }
    1377              : #endif
    1378           19 :         pruneClusterFringe(cluster, maxDist);
    1379              :     }
    1380              : }
    1381              : 
    1382              : 
    1383              : bool
    1384          652 : NBNodeCont::isSlipLaneContinuation(const NBNode* cont) {
    1385          980 :     return cont->getPassengerEdges(true).size() == 1 && cont->getPassengerEdges(false).size() == 1;
    1386              : }
    1387              : 
    1388              : 
    1389              : bool
    1390         1881 : NBNodeCont::maybeSlipLaneStart(const NBNode* n, EdgeVector& outgoing, double& inAngle) const {
    1391         1881 :     EdgeVector inPE = n->getPassengerEdges(true);
    1392         1881 :     EdgeVector outPE = n->getPassengerEdges(false);
    1393         1881 :     if (inPE.size() == 1 && outPE.size() == 2) {
    1394           65 :         outgoing.insert(outgoing.begin(), outPE.begin(), outPE.end());
    1395           65 :         inAngle = inPE.front()->getAngleAtNode(n);
    1396           65 :         return true;
    1397         1816 :     } else if (inPE.size() >= 2 && outPE.size() == 3) {
    1398              :         // check if the incoming edges are going in opposite directions and then
    1399              :         // use the incoming edge that has 2 almost-straight outgoing edges
    1400          182 :         const double inRelAngle = fabs(NBHelpers::relAngle(inPE.front()->getAngleAtNode(n), inPE.back()->getAngleAtNode(n)));
    1401              :         //std::cout << "n=" << n->getID() << " inRelAngle=" << inRelAngle << "\n";
    1402          182 :         if (inRelAngle < 135) {
    1403              :             return false; // not opposite incoming
    1404              :         }
    1405          327 :         for (NBEdge* in : inPE) {
    1406              :             EdgeVector straight;
    1407              :             int numReverse = 0;
    1408          984 :             for (NBEdge* out : outPE) {
    1409          738 :                 const double outRelAngle = fabs(NBHelpers::relAngle(in->getAngleAtNode(n), out->getAngleAtNode(n)));
    1410          738 :                 if (outRelAngle <= 45) {
    1411          230 :                     straight.push_back(out);
    1412          508 :                 } else if (outRelAngle >= 135) {
    1413          200 :                     numReverse++;
    1414              :                 }
    1415              :             }
    1416          246 :             if (straight.size() == 2 && numReverse == 1) {
    1417            6 :                 outgoing.insert(outgoing.begin(), straight.begin(), straight.end());
    1418            6 :                 inAngle = in->getAngleAtNode(n);
    1419              :                 return true;
    1420              :             }
    1421          246 :         }
    1422              :     }
    1423              :     return false;
    1424         1881 : }
    1425              : 
    1426              : 
    1427              : bool
    1428         1881 : NBNodeCont::maybeSlipLaneEnd(const NBNode* n, EdgeVector& incoming, double& outAngle) const {
    1429         1881 :     EdgeVector inPE = n->getPassengerEdges(true);
    1430         1881 :     EdgeVector outPE = n->getPassengerEdges(false);
    1431         1881 :     if (inPE.size() == 2 && outPE.size() == 1) {
    1432           86 :         incoming.insert(incoming.begin(), inPE.begin(), inPE.end());
    1433           86 :         outAngle = outPE.front()->getAngleAtNode(n);
    1434           86 :         return true;
    1435         1795 :     } else if (inPE.size() == 3 && outPE.size() >= 2) {
    1436              :         // check if the outgoing edges are going in opposite directions and then
    1437              :         // use the outgoing edge that has 2 almost-straight incoming edges
    1438          188 :         const double outRelAngle = fabs(NBHelpers::relAngle(outPE.front()->getAngleAtNode(n), outPE.back()->getAngleAtNode(n)));
    1439              :         //std::cout << "n=" << n->getID() << " outRelAngle=" << outRelAngle << "\n";
    1440          188 :         if (outRelAngle < 135) {
    1441              :             return false; // not opposite outgoing
    1442              :         }
    1443          418 :         for (NBEdge* out : outPE) {
    1444              :             EdgeVector straight;
    1445              :             int numReverse = 0;
    1446         1252 :             for (NBEdge* in : inPE) {
    1447          939 :                 const double inRelAngle = fabs(NBHelpers::relAngle(in->getAngleAtNode(n), out->getAngleAtNode(n)));
    1448          939 :                 if (inRelAngle <= 45) {
    1449          295 :                     straight.push_back(in);
    1450          644 :                 } else if (inRelAngle >= 135) {
    1451          249 :                     numReverse++;
    1452              :                 }
    1453              :             }
    1454          313 :             if (straight.size() == 2 && numReverse == 1) {
    1455            5 :                 incoming.insert(incoming.begin(), straight.begin(), straight.end());
    1456            5 :                 outAngle = out->getAngleAtNode(n);
    1457              :                 return true;
    1458              :             }
    1459          313 :         }
    1460              :     }
    1461              :     return false;
    1462         1881 : }
    1463              : 
    1464              : bool
    1465         1603 : NBNodeCont::feasibleCluster(const NodeSet& cluster, const std::map<const NBNode*, std::vector<NBNode*> >& ptStopEnds,
    1466              :                             double maxDist, std::string& reason, NBNode*& tryRemove) const {
    1467              :     // check for clusters which are to complex and probably won't work very well
    1468              :     // we count the incoming edges of the final junction
    1469              :     std::map<NBEdge*, double, ComparatorIdLess> finalIncomingAngles;
    1470              :     std::map<NBEdge*, double, ComparatorIdLess> finalOutgoingAngles;
    1471         7015 :     for (NBNode* n : cluster) {
    1472        17905 :         for (EdgeVector::const_iterator it_edge = n->getIncomingEdges().begin(); it_edge != n->getIncomingEdges().end(); ++it_edge) {
    1473        12493 :             NBEdge* edge = *it_edge;
    1474        18487 :             if (cluster.count(edge->getFromNode()) == 0 && (edge->getPermissions() & SVC_PASSENGER) != 0) {
    1475              :                 // incoming edge, does not originate in the cluster
    1476         3460 :                 finalIncomingAngles[edge] = edge->getAngleAtNode(edge->getToNode());
    1477              :             }
    1478              :         }
    1479        17834 :         for (EdgeVector::const_iterator it_edge = n->getOutgoingEdges().begin(); it_edge != n->getOutgoingEdges().end(); ++it_edge) {
    1480        12422 :             NBEdge* edge = *it_edge;
    1481        18416 :             if (cluster.count(edge->getToNode()) == 0 && (edge->getPermissions() & SVC_PASSENGER) != 0) {
    1482              :                 // outgoing edge, does not end in the cluster
    1483         3467 :                 finalOutgoingAngles[edge] = edge->getAngleAtNode(edge->getFromNode());
    1484              :             }
    1485              :         }
    1486              : 
    1487              :     }
    1488              : #ifdef DEBUG_JOINJUNCTIONS
    1489              :     for (NBNode* n : cluster) {
    1490              :         if (DEBUGCOND(n)) {
    1491              :             std::cout << "feasibleCluster c=" << joinNamedToString(cluster, ',')
    1492              :                       << "\n inAngles=" << joinNamedToString(finalIncomingAngles, ' ', ':')
    1493              :                       << "\n outAngles=" << joinNamedToString(finalOutgoingAngles, ' ', ':')
    1494              :                       << "\n";
    1495              :         }
    1496              :     }
    1497              : #endif
    1498         1603 :     if (finalIncomingAngles.size() > 5) {
    1499            6 :         reason = toString(finalIncomingAngles.size()) + " incoming edges";
    1500            3 :         return false;
    1501              :     }
    1502              :     // check for incoming parallel edges
    1503         1600 :     const double PARALLEL_THRESHOLD_DIFF_NODE = OptionsCont::getOptions().getFloat("junctions.join.parallel-threshold");
    1504         1600 :     const double PARALLEL_THRESHOLD_SAME_NODE = PARALLEL_THRESHOLD_DIFF_NODE / 3;
    1505              :     bool foundParallel = false;
    1506         4925 :     for (auto j = finalIncomingAngles.begin(); j != finalIncomingAngles.end() && !foundParallel; ++j) {
    1507              :         auto k = j;
    1508         6721 :         for (++k; k != finalIncomingAngles.end() && !foundParallel; ++k) {
    1509         3396 :             const double angleDiff = fabs(j->second - k->second);
    1510         3396 :             if (angleDiff < PARALLEL_THRESHOLD_DIFF_NODE) {
    1511           38 :                 NBEdge* e1 = j->first;
    1512           38 :                 NBEdge* e2 = k->first;
    1513              :                 // for edge targeting the same node, permit a narrower angle
    1514           38 :                 const double edgeDist = e1->getLaneShape(0).back().distanceTo2D(e2->getLaneShape(0).back());
    1515              : #ifdef DEBUG_JOINJUNCTIONS
    1516              :                 if (DEBUGCOND(e1->getToNode())) {
    1517              :                     std::cout << " angleDiff=" << angleDiff << " shapeDist=" << edgeDist << "\n";
    1518              :                 }
    1519              : #endif
    1520           38 :                 if (angleDiff >= PARALLEL_THRESHOLD_SAME_NODE && (
    1521              :                             (e1->getToNode() == e2->getToNode()
    1522           14 :                              || (edgeDist  < maxDist)))) {
    1523            6 :                     continue;
    1524              :                 }
    1525           96 :                 reason = "parallel incoming " + e1->getID() + "," + e2->getID();
    1526           32 :                 if (e1->getToNode() != e2->getToNode() && (int)cluster.size() > 2) {
    1527              :                     // removing one of the nodes and try again
    1528              :                     if (e1->getPriority() > e2->getPriority()
    1529           18 :                             || (e1->getPriority() == e2->getPriority() && e1->getNumLanesThatAllow(SVC_PASSENGER) > e2->getNumLanesThatAllow(SVC_PASSENGER))) {
    1530            6 :                         tryRemove = e2->getToNode();
    1531              :                     } else {
    1532           12 :                         tryRemove = e1->getToNode();
    1533              :                     }
    1534              :                 }
    1535              :                 return false;
    1536              :             }
    1537              :         }
    1538              :     }
    1539              :     // check for outgoing parallel edges
    1540         4846 :     for (auto j = finalOutgoingAngles.begin(); j != finalOutgoingAngles.end() && !foundParallel; ++j) {
    1541              :         auto k = j;
    1542         6595 :         for (++k; k != finalOutgoingAngles.end() && !foundParallel; ++k) {
    1543         3317 :             const double angleDiff = fabs(j->second - k->second);
    1544         3317 :             if (angleDiff < PARALLEL_THRESHOLD_DIFF_NODE) {
    1545           25 :                 NBEdge* e1 = j->first;
    1546           25 :                 NBEdge* e2 = k->first;
    1547              :                 // for edge leaving the same node, permit a narrower angle
    1548           25 :                 const double edgeDist = e1->getLaneShape(0).front().distanceTo2D(e2->getLaneShape(0).front());
    1549              : #ifdef DEBUG_JOINJUNCTIONS
    1550              :                 if (DEBUGCOND(e1->getFromNode())) {
    1551              :                     std::cout << " angleDiff=" << angleDiff << " shapeDist=" << edgeDist << "\n";
    1552              :                 }
    1553              : #endif
    1554           25 :                 if (angleDiff >= PARALLEL_THRESHOLD_SAME_NODE && (
    1555              :                             (e1->getFromNode() == e2->getFromNode()
    1556           13 :                              || (edgeDist < maxDist)))) {
    1557            8 :                     continue;
    1558              :                 }
    1559           51 :                 reason = "parallel outgoing " + e1->getID() + "," + e2->getID();
    1560           17 :                 if (e1->getFromNode() != e2->getFromNode() && (int)cluster.size() > 2) {
    1561              :                     // removing one of the nodes and try again
    1562              :                     if (e1->getPriority() > e2->getPriority()
    1563            3 :                             || (e1->getPriority() == e2->getPriority() && e1->getNumLanesThatAllow(SVC_PASSENGER) > e2->getNumLanesThatAllow(SVC_PASSENGER))) {
    1564            1 :                         tryRemove = e2->getFromNode();
    1565              :                     } else {
    1566            2 :                         tryRemove = e1->getFromNode();
    1567              :                     }
    1568              :                 }
    1569              :                 return false;
    1570              :             }
    1571              :         }
    1572              :     }
    1573              :     // check for stop edges and tls within the cluster
    1574              :     bool hasTLS = false;
    1575         6769 :     for (NBNode* n : cluster) {
    1576         5224 :         if (n->isTLControlled() || n->hadSignal()) {
    1577              :             hasTLS = true;
    1578              :         }
    1579              :         const auto& stopEnds = ptStopEnds.find(n);
    1580         5224 :         if (stopEnds != ptStopEnds.end()) {
    1581          626 :             for (NBNode* const to : stopEnds->second) {
    1582              :                 if (cluster.count(to) != 0) {
    1583              :                     reason = "it contains a pt stop edge";
    1584            6 :                     return false;
    1585              :                 }
    1586              :             }
    1587              :         }
    1588              :     }
    1589              :     // prevent removal of long edges unless there is weak circle or a traffic light
    1590         1545 :     if (cluster.size() > 2) {
    1591              :         // find the nodes with the biggests physical distance between them
    1592          591 :         double maxLength = -1;
    1593              :         NBEdge* maxEdge = nullptr;
    1594         3879 :         for (NBNode* n1 : cluster) {
    1595        29200 :             for (NBNode* n2 : cluster) {
    1596        25912 :                 NBEdge* e1 = n1->getConnectionTo(n2);
    1597        25912 :                 NBEdge* e2 = n2->getConnectionTo(n1);
    1598        30026 :                 if (e1 != nullptr && e1->getLoadedLength() > maxLength) {
    1599          752 :                     maxLength = e1->getLoadedLength();
    1600              :                     maxEdge = e1;
    1601              :                 }
    1602        30026 :                 if (e2 != nullptr && e2->getLoadedLength() > maxLength) {
    1603          595 :                     maxLength = e2->getLoadedLength();
    1604              :                     maxEdge = e2;
    1605              :                 }
    1606              :             }
    1607              :         }
    1608              : #ifdef DEBUG_JOINJUNCTIONS
    1609              :         for (NBNode* n : cluster) {
    1610              :             if (DEBUGCOND(n)) {
    1611              :                 std::cout << "feasible hasTLS=" << hasTLS << " maxLength=" << maxLength << " maxEdge=" << maxEdge->getID() << "\n";
    1612              :             }
    1613              :         }
    1614              : #endif
    1615          591 :         if (!hasTLS && maxLength > 5) {
    1616              :             // find a weak circle within cluster that does not use maxEdge
    1617              :             std::vector<NBNode*> toCheck;
    1618              :             std::set<NBNode*> visited;
    1619           27 :             toCheck.push_back(maxEdge->getToNode());
    1620              :             bool foundCircle = false;
    1621           76 :             while (!toCheck.empty()) {
    1622           74 :                 NBNode* n = toCheck.back();
    1623           74 :                 if (n == maxEdge->getFromNode()) {
    1624              :                     foundCircle = true;
    1625           25 :                     break;
    1626              :                 }
    1627              :                 toCheck.pop_back();
    1628              :                 visited.insert(n);
    1629          269 :                 for (NBEdge* e : n->getEdges()) {
    1630          220 :                     if (e != maxEdge) {
    1631          310 :                         NBNode* cand = e->getFromNode() == n ? e->getToNode() : e->getFromNode();
    1632              :                         if (visited.count(cand) == 0 && cluster.count(cand) != 0) {
    1633           95 :                             toCheck.push_back(cand);
    1634              :                         }
    1635              :                     }
    1636              :                 }
    1637              :             }
    1638              :             if (!foundCircle) {
    1639            8 :                 reason = "not compact (maxEdge=" + maxEdge->getID() + " length=" + toString(maxLength) + ")";
    1640              :                 return false;
    1641              :             }
    1642           27 :         }
    1643              :     }
    1644              :     // prevent joining of simple merging/spreading structures
    1645         1543 :     if (cluster.size() >= 2) {
    1646              :         int entryNodes = 0;
    1647              :         int exitNodes = 0;
    1648              :         EdgeVector outsideIncoming;
    1649              :         EdgeVector outsideOutgoing;
    1650              :         int edgesWithin = 0;
    1651         6728 :         for (NBNode* n : cluster) {
    1652              :             bool foundOutsideIncoming = false;
    1653        17193 :             for (NBEdge* e : n->getIncomingEdges()) {
    1654        12007 :                 if (cluster.count(e->getFromNode()) == 0) {
    1655              :                     // edge entering from outside the cluster
    1656         6270 :                     outsideIncoming.push_back(e);
    1657              :                     foundOutsideIncoming = true;
    1658              :                 } else {
    1659         5737 :                     edgesWithin++;
    1660              :                 }
    1661              :             }
    1662         5186 :             if (foundOutsideIncoming) {
    1663         4139 :                 entryNodes++;
    1664              :             }
    1665              :             bool foundOutsideOutgoing = false;
    1666        17121 :             for (NBEdge* e : n->getOutgoingEdges()) {
    1667        11935 :                 if (cluster.count(e->getToNode()) == 0) {
    1668              :                     // edge leaving cluster
    1669         6198 :                     outsideOutgoing.push_back(e);
    1670              :                     foundOutsideOutgoing = true;
    1671              :                 }
    1672              :             }
    1673         5186 :             if (foundOutsideOutgoing) {
    1674         4045 :                 exitNodes++;
    1675              :             }
    1676              :         }
    1677         1542 :         if (!hasTLS) {
    1678          996 :             if (entryNodes < 2) {
    1679              :                 reason = "only 1 entry node";
    1680              :                 return false;
    1681              :             }
    1682          990 :             if (exitNodes < 2) {
    1683              :                 reason = "only 1 exit node";
    1684              :                 return false;
    1685              :             }
    1686          968 :             if (cluster.size() == 2) {
    1687          755 :                 if (edgesWithin == 1 && outsideIncoming.size() < 3 && outsideOutgoing.size() < 3) {
    1688              :                     reason = "only 1 edge within and no cross-traffic";
    1689              :                     return false;
    1690              :                 }
    1691              :             }
    1692              :         }
    1693              :         /*
    1694              :         if (NBNode::geometryLike(outsideIncoming, outsideOutgoing) && hasTLS && OptionsCont::getOptions().getBool("tls.discard-simple")) {
    1695              :             reason = "geometry-like simple tls";
    1696              :             return false;
    1697              :         }
    1698              :         */
    1699         1542 :     }
    1700              :     return true;
    1701              : }
    1702              : 
    1703              : 
    1704              : bool
    1705          965 : NBNodeCont::reduceToCircle(NodeSet& cluster, int circleSize, NodeSet startNodes, double maxDist, std::vector<NBNode*> cands) const {
    1706              : #ifdef DEBUG_REDUCE
    1707              :     std::cout << "reduceToCircle  cs=" << circleSize << " cands=" << toString(cands, ',') << " startNodes=" << joinNamedToString(startNodes, ',') << "\n";
    1708              : #endif
    1709              :     assert(circleSize >= 2);
    1710          965 :     if ((int)cands.size() == circleSize) {
    1711          183 :         if (cands.back()->getConnectionTo(cands.front()) != nullptr) {
    1712              :             // cluster found
    1713              :             NodeSet candCluster;
    1714              :             candCluster.insert(cands.begin(), cands.end());
    1715           36 :             pruneClusterFringe(candCluster, maxDist, true);
    1716           36 :             bool feasible = (int)candCluster.size() == circleSize;
    1717           36 :             if (feasible) {
    1718              :                 cluster.clear();
    1719              :                 cluster.insert(cands.begin(), cands.end());
    1720              :             }
    1721              :             return feasible;
    1722              :         } else {
    1723              :             return false;
    1724              :         }
    1725              :     }
    1726          782 :     if ((int)cluster.size() <= circleSize || startNodes.size() == 0) {
    1727              :         // no reduction possible
    1728              : #ifdef DEBUG_REDUCE
    1729              :         std::cout << "    abort\n";
    1730              : #endif
    1731              :         return false;
    1732              :     }
    1733          687 :     if (cands.size() == 0) {
    1734              :         // try to find a circle starting from another start node
    1735          259 :         NBEdge* e = shortestEdge(cluster, startNodes, cands);
    1736          259 :         if (e != nullptr) {
    1737          240 :             cands.push_back(e->getFromNode());
    1738          240 :             startNodes.erase(e->getFromNode());
    1739          720 :             if (reduceToCircle(cluster, circleSize, startNodes, maxDist, cands)) {
    1740              :                 return true;
    1741              :             } else {
    1742              :                 // try another start node
    1743          438 :                 return reduceToCircle(cluster, circleSize, startNodes, maxDist);
    1744              :             }
    1745              :         }
    1746              :     } else {
    1747              :         NodeSet singleStart;
    1748              :         singleStart.insert(cands.back());
    1749          428 :         NBEdge* e = shortestEdge(cluster, singleStart, cands);
    1750          428 :         if (e != nullptr) {
    1751          385 :             std::vector<NBNode*> cands2(cands);
    1752          385 :             cands2.push_back(e->getToNode());
    1753         1155 :             if (reduceToCircle(cluster, circleSize, startNodes, maxDist, cands2)) {
    1754              :                 return true;
    1755              :             }
    1756          385 :         }
    1757              :     }
    1758              : #ifdef DEBUG_REDUCE
    1759              :     std::cout << "    abort2\n";
    1760              : #endif
    1761              :     return false;
    1762              : }
    1763              : 
    1764              : 
    1765              : NBEdge*
    1766          687 : NBNodeCont::shortestEdge(const NodeSet& cluster, const NodeSet& startNodes, const std::vector<NBNode*>& exclude) const {
    1767              :     double minDist = std::numeric_limits<double>::max();
    1768              :     NBEdge* result = nullptr;
    1769         2637 :     for (NBNode* n : startNodes) {
    1770         5488 :         for (NBEdge* e : n->getOutgoingEdges()) {
    1771         3538 :             NBNode* neigh = e->getToNode();
    1772         3538 :             if (cluster.count(neigh) != 0 && std::find(exclude.begin(), exclude.end(), neigh) == exclude.end()) {
    1773              :                 const double dist = n->getPosition().distanceTo2D(neigh->getPosition());
    1774              :                 //std::cout << "    e=" << e->getID() << " dist=" << dist << " minD=" << minDist << "\n";
    1775         2365 :                 if (dist < minDist) {
    1776              :                     minDist = dist;
    1777              :                     result = e;
    1778              :                 }
    1779              :             }
    1780              :         }
    1781              :     }
    1782              :     //std::cout << "closestNeighbor startNodes=" << toString(startNodes) << " result=" << Named::getIDSecure(result) << "\n";
    1783          687 :     return result;
    1784              : }
    1785              : 
    1786              : void
    1787          127 : NBNodeCont::joinNodeClusters(NodeClusters clusters,
    1788              :                              NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tlc, bool resetConnections) {
    1789          941 :     for (NodeSet cluster : clusters) {
    1790         1628 :         joinNodeCluster(cluster, dc, ec, tlc, nullptr, resetConnections);
    1791              :     }
    1792          127 : }
    1793              : 
    1794              : 
    1795              : void
    1796          824 : NBNodeCont::joinNodeCluster(NodeSet cluster, NBDistrictCont& dc, NBEdgeCont& ec, NBTrafficLightLogicCont& tlc, NBNode* predefined, bool resetConnections) {
    1797          824 :     const bool origNames = OptionsCont::getOptions().getBool("output.original-names");
    1798              :     assert(cluster.size() > 1);
    1799          824 :     std::string id = "cluster_";
    1800              :     Position pos;
    1801          824 :     bool setTL = false;
    1802          824 :     SumoXMLNodeType nodeType = SumoXMLNodeType::UNKNOWN;
    1803         1648 :     TrafficLightType type = SUMOXMLDefinitions::TrafficLightTypes.get(OptionsCont::getOptions().getString("tls.default-type"));
    1804              :     // collect edges
    1805              :     std::set<NBEdge*, ComparatorIdLess> allEdges;
    1806         3565 :     for (NBNode* n : cluster) {
    1807              :         const EdgeVector& edges = n->getEdges();
    1808              :         allEdges.insert(edges.begin(), edges.end());
    1809              :     }
    1810              :     // determine edges with are incoming or fully inside
    1811              :     std::set<NBEdge*, ComparatorIdLess> clusterIncoming;
    1812              :     std::set<NBEdge*, ComparatorIdLess> inside;
    1813        10207 :     for (NBEdge* e : allEdges) {
    1814         9383 :         if (cluster.count(e->getToNode()) > 0) {
    1815         6147 :             if (cluster.count(e->getFromNode()) > 0) {
    1816              :                 inside.insert(e);
    1817              :                 if (e->getSignalPosition() != Position::INVALID) {
    1818           41 :                     setTL = true;
    1819           41 :                     nodeType = SumoXMLNodeType::TRAFFIC_LIGHT;
    1820              :                 }
    1821              :             } else {
    1822              :                 clusterIncoming.insert(e);
    1823              :             }
    1824              :         }
    1825              :     }
    1826              : #ifdef DEBUG_JOINJUNCTIONS_CONNECTIONS
    1827              :     std::cout << "joining cluster " << joinNamedToString(cluster, ' ')
    1828              :               << "  resetConnections=" << resetConnections << "\n"
    1829              :               << "  incoming=" << joinNamedToString(clusterIncoming, ' ') << "\n"
    1830              :               << "  inside=" << joinNamedToString(inside, ' ') << "\n";
    1831              : #endif
    1832          824 :     analyzeCluster(cluster, id, pos, setTL, type, nodeType);
    1833              :     NBNode* newNode = nullptr;
    1834          824 :     if (predefined != nullptr) {
    1835              :         newNode = predefined;
    1836              :     } else {
    1837          814 :         if (!insert(id, pos)) {
    1838              :             // should not fail
    1839            0 :             WRITE_WARNINGF(TL("Could not join junctions %."), id);
    1840              :             return;
    1841              :         }
    1842          814 :         newNode = retrieve(id);
    1843              :     }
    1844              :     std::string tlID = id;
    1845          824 :     if (predefined != nullptr) {
    1846           10 :         if (predefined->getType() != SumoXMLNodeType::UNKNOWN) {
    1847            1 :             nodeType = predefined->getType();
    1848            1 :             setTL = predefined->isTLControlled();
    1849              :         }
    1850           10 :         Position ppos = predefined->getPosition();
    1851           10 :         if (ppos.x() != Position::INVALID.x()) {
    1852              :             pos.setx(ppos.x());
    1853              :         }
    1854           10 :         if (ppos.y() != Position::INVALID.y()) {
    1855              :             pos.sety(ppos.y());
    1856              :         }
    1857           10 :         if (ppos.z() != Position::INVALID.z()) {
    1858              :             pos.setz(ppos.z());
    1859              :         }
    1860              :     }
    1861          824 :     newNode->reinit(pos, nodeType);
    1862          824 :     if (origNames) {
    1863          958 :         newNode->setParameter(SUMO_PARAM_ORIGID, joinNamedToString(cluster, ' '));
    1864              :     }
    1865          824 :     if (setTL && !newNode->isTLControlled()) {
    1866          224 :         NBTrafficLightDefinition* tlDef = new NBOwnTLDef(tlID, newNode, 0, type);
    1867          224 :         if (!tlc.insert(tlDef)) {
    1868              :             // actually, nothing should fail here
    1869            0 :             delete tlDef;
    1870            0 :             throw ProcessError(TLF("Could not allocate tls '%'.", id));
    1871              :         }
    1872              :     }
    1873              : 
    1874              :     // determine possible connectivity from outside edges
    1875              :     std::map<NBEdge*, EdgeSet> reachable;
    1876              :     std::map<std::pair<NBEdge*, NBEdge*>, SVCPermissions> conPermissions;
    1877              :     EdgeSet specialPermissions;
    1878         4074 :     for (NBEdge* const e : clusterIncoming) {
    1879              :         EdgeVector open;
    1880              :         EdgeSet seen;
    1881         3250 :         open.push_back(e);
    1882        25625 :         while (open.size() > 0) {
    1883        22375 :             NBEdge* const cur = open.back();
    1884        22375 :             const SVCPermissions pCur = conPermissions.count({e, cur}) == 0 ? cur->getPermissions() : conPermissions[ {e, cur}];
    1885              : #ifdef DEBUG_JOINJUNCTIONS_CONNECTIONS
    1886              :             std::cout << "e=" << e->getID() << " cur=" << cur->getID() << " open=" << toString(open) << "\n";
    1887              :             std::cout << "e=" << e->getID() << " cur=" << cur->getID() << " open=" << toString(open) << "\n";
    1888              : #endif
    1889              :             seen.insert(cur);
    1890              :             open.pop_back();
    1891        22375 :             if (cluster.count(cur->getToNode()) == 0) {
    1892              :                 //std::cout << "      continue\n";
    1893         9836 :                 continue;
    1894              :             }
    1895              :             const auto& cons = cur->getConnections();
    1896        13803 :             if (cons.size() == 0 || ec.hasPostProcessConnection(cur->getID()) || cur->getStep() == NBEdge::EdgeBuildingStep::INIT) {
    1897              :                 // check permissions to determine reachability
    1898        40597 :                 for (NBEdge* out : cur->getToNode()->getOutgoingEdges()) {
    1899              :                     if (allEdges.count(out) != 0) {
    1900        29279 :                         const SVCPermissions p = pCur & out->getPermissions();
    1901         7062 :                         if (seen.count(out) == 0 || (~conPermissions[ {e, out}] & p) != 0) {
    1902        22284 :                             if ((p & ~SVC_PEDESTRIAN) != 0) {
    1903        16970 :                                 open.push_back(out);
    1904        16970 :                                 conPermissions[ {e, out}] |= p;
    1905              : #ifdef DEBUG_JOINJUNCTIONS_CONNECTIONS
    1906              :                                 std::cout << "  e=" << e->getID() << " out=" << out->getID() << " pOut=" << getVehicleClassNames(out->getPermissions()) << "\n    p=" << getVehicleClassNames(p) << "\n    q=" << getVehicleClassNames(conPermissions[ {e, out}]) << "\n";
    1907              : #endif
    1908              :                             }
    1909              :                         }
    1910              :                     }
    1911              :                 }
    1912              :             } else {
    1913              :                 // check existing connections
    1914         4798 :                 for (const auto& con : cons) {
    1915         3577 :                     if (con.toEdge != nullptr && allEdges.count(con.toEdge) != 0) {
    1916         3577 :                         SVCPermissions p = pCur & con.toEdge->getPermissions();
    1917         3577 :                         if (con.permissions != SVC_UNSPECIFIED) {
    1918            7 :                             p &= con.permissions;
    1919              :                         }
    1920         1422 :                         if (seen.count(con.toEdge) == 0 || (~conPermissions[ {e, con.toEdge}] & p) != 0) {
    1921         2155 :                             open.push_back(con.toEdge);
    1922         2155 :                             conPermissions[ {e, con.toEdge}] |= p;
    1923              :                             //std::cout << "  e=" << e->getID() << " con.toEdge=" << con.toEdge->getID() << " pSpecial=" << toString(con.permissions) << " pOut=" << getVehicleClassNames(con.toEdge->getPermissions()) << "\n    p=" << getVehicleClassNames(p) << "\n    q=" << getVehicleClassNames(conPermissions[{e, con.toEdge}]) << "\n";
    1924              :                         }
    1925              :                     }
    1926              :                 }
    1927              :             }
    1928              :         }
    1929              :         seen.erase(e);
    1930        19284 :         for (NBEdge* reached : seen) {
    1931              :             // filter out inside edges from reached
    1932              :             if (inside.count(reached) == 0) {
    1933         7940 :                 if (e->getStep() > NBEdge::EdgeBuildingStep::INIT && reached->getFromNode() == e->getToNode() && !e->isConnectedTo(reached)) {
    1934              :                     // also filter out edges that are outgoing of the to-node of edge but aren't currently connected
    1935           14 :                     continue;
    1936              :                 }
    1937         7926 :                 reachable[e].insert(reached);
    1938         7926 :                 const SVCPermissions pDefault = e->getPermissions() & reached->getPermissions();
    1939         7926 :                 if (conPermissions[ {e, reached}] != pDefault) {
    1940              :                     specialPermissions.insert(e);
    1941              : #ifdef DEBUG_JOINJUNCTIONS_CONNECTIONS
    1942              :                     std::cout << "e=" << e->getID() << " out=" << reached->getID() << " special=" << getVehicleClassNames(conPermissions[ {e, reached}]) << "\n";
    1943              : #endif
    1944              :                 }
    1945              :             }
    1946              :         }
    1947              : #ifdef DEBUG_JOINJUNCTIONS_CONNECTIONS
    1948              :         std::cout << " reachable e=" << e->getID() << " seen=" << toString(seen) << " reachable=" << toString(reachable[e]) << "\n";
    1949              : #endif
    1950         3250 :     }
    1951              : 
    1952              :     // remap and remove edges which are completely within the new intersection
    1953          824 :     if (origNames) {
    1954          958 :         newNode->setParameter("origEdgeIds", joinNamedToString(inside, ' '));
    1955              :     }
    1956         3721 :     for (NBEdge* e : inside) {
    1957        52515 :         for (NBEdge* e2 : allEdges) {
    1958        49618 :             if (e != e2) {
    1959        46721 :                 e2->replaceInConnections(e, e->getConnections());
    1960              :             }
    1961              :         }
    1962         2897 :         ec.extract(dc, e, true);
    1963              :         allEdges.erase(e);
    1964              :     }
    1965              : 
    1966              :     // remap edges which are incoming / outgoing
    1967         7310 :     for (NBEdge* e : allEdges) {
    1968         6486 :         const bool outgoing = cluster.count(e->getFromNode()) > 0;
    1969              :         NBNode* from = outgoing ? newNode : e->getFromNode();
    1970              :         NBNode* to   = outgoing ? e->getToNode() : newNode;
    1971         6486 :         if (origNames) {
    1972         4053 :             if (outgoing) {
    1973         4048 :                 e->setParameter("origFrom", e->getFromNode()->getID());
    1974              :             } else {
    1975         4058 :                 e->setParameter("origTo", e->getToNode()->getID());
    1976              :             }
    1977              :         }
    1978         6486 :         if (e->getTurnSignTarget() != "") {
    1979        25097 :             for (NBNode* n : cluster) {
    1980        21098 :                 if (e->getTurnSignTarget() == n->getID()) {
    1981              :                     e->setTurnSignTarget(to->getID());
    1982              :                     break;
    1983              :                 }
    1984              :             }
    1985              :         }
    1986         6486 :         e->reinitNodes(from, to);
    1987              :         // re-add connections which previously existed and may still valid.
    1988              :         // connections to removed edges will be ignored
    1989         6486 :         std::vector<NBEdge::Connection> conns = e->getConnections();
    1990         8375 :         for (std::vector<NBEdge::Connection>::iterator k = conns.begin(); k != conns.end(); ++k) {
    1991         1889 :             if ((*k).toEdge == nullptr) {
    1992              :                 // edge explicitly set to have no connections
    1993            1 :                 continue;
    1994              :             }
    1995         1888 :             e->addLane2LaneConnection((*k).fromLane, (*k).toEdge, (*k).toLane, NBEdge::Lane2LaneInfoType::USER, false, (*k).mayDefinitelyPass);
    1996         1888 :             if ((*k).fromLane >= 0 && (*k).fromLane < e->getNumLanes() && e->getLaneStruct((*k).fromLane).connectionsDone) {
    1997              :                 // @note (see NIImporter_DlrNavteq::ConnectedLanesHandler)
    1998              :                 e->declareConnectionsAsLoaded(NBEdge::EdgeBuildingStep::INIT);
    1999              : #ifdef DEBUG_JOINJUNCTIONS_CONNECTIONS
    2000              :                 std::cout << "   e=" << e->getID() << " declareConnectionsAsLoaded\n";
    2001              : #endif
    2002              :             }
    2003              :         }
    2004         6486 :     }
    2005          824 :     if (!resetConnections) {
    2006              :         // disable connections that were impossible with the old topology
    2007              :         // if connectivity has special permissions, set edge to edge connections explicitly
    2008         3852 :         for (NBEdge* in : newNode->getIncomingEdges()) {
    2009        17816 :             for (NBEdge* out : newNode->getOutgoingEdges()) {
    2010        14720 :                 if (reachable[in].count(out) == 0) {
    2011         6966 :                     if (!ec.hasPostProcessConnection(in->getID(), out->getID())) {
    2012              :                         //std::cout << " removeUnreachable in=" << in->getID() << " out=" << out->getID() << "\n";
    2013         6966 :                         in->removeFromConnections(out, -1, -1, true, false, true);
    2014              :                     } else {
    2015              :                         //std::cout << " hasPostProcessConnection in=" << in->getID() << " out=" << out->getID() << "\n";
    2016              :                     }
    2017              :                 } else if (specialPermissions.count(in) != 0) {
    2018          399 :                     SVCPermissions pDefault = in->getPermissions() & out->getPermissions();
    2019          399 :                     SVCPermissions p = conPermissions[ {in, out}] == 0 ? pDefault : conPermissions[ {in, out}];
    2020          399 :                     in->addEdge2EdgeConnection(out, true, p == pDefault ? SVC_UNSPECIFIED : p);
    2021              :                     //std::cout << " addEdge2Edge in=" << in->getID() << " out=" << out->getID() << "\n";
    2022              :                 }
    2023              :             }
    2024              :         }
    2025              :     } else {
    2026          222 :         for (NBEdge* in : newNode->getIncomingEdges()) {
    2027          154 :             in->invalidateConnections(true);
    2028              :         }
    2029              :     }
    2030              : 
    2031              :     // remove original nodes
    2032          824 :     registerJoinedCluster(cluster);
    2033         3565 :     for (NBNode* n : cluster) {
    2034         2741 :         erase(n);
    2035              :     }
    2036              : }
    2037              : 
    2038              : 
    2039              : void
    2040          824 : NBNodeCont::registerJoinedCluster(const NodeSet& cluster) {
    2041              :     std::set<std::string> ids;
    2042         3565 :     for (NBNode* n : cluster) {
    2043              :         ids.insert(n->getID());
    2044              :     }
    2045          824 :     myJoinedClusters.push_back(ids);
    2046          824 : }
    2047              : 
    2048              : void
    2049            0 : NBNodeCont::registerJoinedCluster(const std::set<std::string>& cluster) {
    2050            0 :     myJoinedClusters.push_back(cluster);
    2051            0 : }
    2052              : 
    2053              : void
    2054            0 : NBNodeCont::unregisterJoinedCluster(const std::set<std::string>& cluster) {
    2055            0 :     auto it = std::find(myJoinedClusters.begin(), myJoinedClusters.end(), cluster);
    2056            0 :     if (it != myJoinedClusters.end()) {
    2057            0 :         myJoinedClusters.erase(it);
    2058              :     }
    2059            0 : }
    2060              : 
    2061              : 
    2062              : void
    2063          872 : NBNodeCont::analyzeCluster(NodeSet cluster, std::string& id, Position& pos,
    2064              :                            bool& hasTLS, TrafficLightType& type, SumoXMLNodeType& nodeType) {
    2065         1744 :     id = createClusterId(cluster, id);
    2066              :     bool ambiguousType = false;
    2067         3740 :     for (NBNode* j : cluster) {
    2068              :         pos.add(j->getPosition());
    2069              :         // add a traffic light if any of the cluster members was controlled
    2070         2868 :         if (j->isTLControlled()) {
    2071          763 :             if (!hasTLS) {
    2072              :                 // init type
    2073          237 :                 type = (*j->getControllingTLS().begin())->getType();
    2074          526 :             } else if (type != (*j->getControllingTLS().begin())->getType()) {
    2075              :                 ambiguousType = true;
    2076              :             }
    2077          763 :             hasTLS = true;
    2078              :         }
    2079         2868 :         SumoXMLNodeType otherType = j->getType();
    2080         2868 :         if (nodeType == SumoXMLNodeType::UNKNOWN) {
    2081         1606 :             nodeType = otherType;
    2082         1262 :         } else if (nodeType != otherType) {
    2083          533 :             if (hasTLS) {
    2084          486 :                 nodeType = SumoXMLNodeType::TRAFFIC_LIGHT;
    2085           47 :             } else if (otherType != SumoXMLNodeType::UNKNOWN) {
    2086           14 :                 if ((nodeType != SumoXMLNodeType::PRIORITY && (nodeType != SumoXMLNodeType::NOJUNCTION || otherType != SumoXMLNodeType::PRIORITY))
    2087           12 :                         || (otherType != SumoXMLNodeType::NOJUNCTION && otherType != SumoXMLNodeType::UNKNOWN && otherType != SumoXMLNodeType::PRIORITY)) {
    2088           24 :                     WRITE_WARNINGF("Ambiguous node type for node cluster '%' (%,%), setting to '" + toString(SumoXMLNodeType::PRIORITY) + "'.", id, toString(nodeType), toString(otherType));
    2089              :                 }
    2090           14 :                 nodeType = SumoXMLNodeType::PRIORITY;
    2091              :             }
    2092              :         }
    2093              :     }
    2094          872 :     pos.mul(1. / (double)cluster.size());
    2095          872 :     if (ambiguousType) {
    2096            0 :         type = SUMOXMLDefinitions::TrafficLightTypes.get(OptionsCont::getOptions().getString("tls.default-type"));
    2097            0 :         WRITE_WARNINGF(TL("Ambiguous traffic light type for node cluster '%', setting to '%'."), id, toString(type));
    2098              :     }
    2099          872 : }
    2100              : 
    2101              : 
    2102              : // ----------- (Helper) methods for guessing/computing traffic lights
    2103              : bool
    2104          991 : NBNodeCont::shouldBeTLSControlled(const NodeSet& c, double laneSpeedThreshold, bool recheck) const {
    2105              :     bool tooFast = false;
    2106              :     double laneSpeedSum = 0;
    2107              :     std::set<NBEdge*> seen;
    2108         2147 :     for (NBNode* j : c) {
    2109         6288 :         for (const NBEdge* e : j->getEdges()) {
    2110         5132 :             if (c.find(e->getFromNode()) != c.end() && c.find(e->getToNode()) != c.end()) {
    2111              :                 // edges fully within the cluster do not count
    2112          624 :                 continue;
    2113              :             }
    2114         4508 :             if (j->hasIncoming(e)) {
    2115         2250 :                 if (recheck && !j->hasConflict(e)) {
    2116              :                     // edges without conflict do not count
    2117              :                     // we can only check this after connections have been computed
    2118            2 :                     continue;
    2119              :                 }
    2120         2248 :                 laneSpeedSum += (double)e->getNumLanes() * e->getLaneSpeed(0);
    2121              :             }
    2122         4506 :             if (e->getLaneSpeed(0) * 3.6 > 79) {
    2123              :                 tooFast = true;
    2124              :             }
    2125              :         }
    2126              :     }
    2127              :     //std::cout << " c=" << joinNamedToString(c, ' ') << " size=" << c.size() << " laneSpeedSum=" << laneSpeedSum << " thresh=" << laneSpeedThreshold << " tooFast=" << tooFast << "\n";
    2128         1982 :     return !tooFast && laneSpeedSum >= laneSpeedThreshold && c.size() != 0;
    2129              : }
    2130              : 
    2131              : bool
    2132           55 : NBNodeCont::onlyCrossings(const NodeSet& c) const {
    2133              :     // check whether all component nodes are solely pedestrian crossings
    2134              :     // (these work fine without joining)
    2135           97 :     for (NBNode* node : c) {
    2136              :         EdgeVector nonPedIncoming;
    2137              :         EdgeVector nonPedOutgoing;
    2138          374 :         for (NBEdge* e : node->getIncomingEdges()) {
    2139          282 :             if (e->getPermissions() != SVC_PEDESTRIAN) {
    2140          216 :                 nonPedIncoming.push_back(e);
    2141              :             }
    2142              :         }
    2143          375 :         for (NBEdge* e : node->getOutgoingEdges()) {
    2144          283 :             if (e->getPermissions() != SVC_PEDESTRIAN) {
    2145          215 :                 nonPedOutgoing.push_back(e);
    2146              :             }
    2147              :         }
    2148           92 :         if (!node->geometryLike(nonPedIncoming, nonPedOutgoing)) {
    2149              :             //for (NBNode* node : c) {
    2150              :             //    if (node->getID() == "2480337678") {
    2151              :             //        std::cout << " node=" << node->getID() << " nonPedIncoming=" << toString(nonPedIncoming) << " nonPedOutgoing=" << toString(nonPedOutgoing) << "\n";
    2152              :             //    }
    2153              :             //}
    2154              :             return false;
    2155              :         }
    2156           92 :     }
    2157              :     return true;
    2158              : }
    2159              : 
    2160              : 
    2161              : bool
    2162           50 : NBNodeCont::customTLID(const NodeSet& c) const {
    2163          177 :     for (NBNode* node : c) {
    2164          129 :         if (node->isTLControlled()) {
    2165          129 :             NBTrafficLightDefinition* tl = (*node->getControllingTLS().begin());
    2166          129 :             if (tl->getNodes().size() > 1) {
    2167              :                 // joined tls also imply a customID
    2168            2 :                 return true;
    2169              :             }
    2170              :             const std::string tlID = tl->getID();
    2171          127 :             if (tlID != node->getID()
    2172          129 :                     && !StringUtils::startsWith(tlID, "joinedS_")
    2173          128 :                     && !StringUtils::startsWith(tlID, "joinedG_")
    2174          131 :                     && !StringUtils::startsWith(tlID, "GS")) {
    2175              :                 return true;
    2176              :             }
    2177              :         }
    2178              :     }
    2179              :     return false;
    2180              : }
    2181              : 
    2182              : 
    2183              : void
    2184         2206 : NBNodeCont::guessTLs(OptionsCont& oc, NBTrafficLightLogicCont& tlc) {
    2185              :     myGuessedTLS.clear();
    2186              :     // build list of definitely not tls-controlled junctions
    2187         2206 :     const double laneSpeedThreshold = oc.getFloat("tls.guess.threshold");
    2188         4412 :     if (oc.isSet("tls.unset")) {
    2189            2 :         std::vector<std::string> notTLControlledNodes = oc.getStringVector("tls.unset");
    2190            2 :         for (std::vector<std::string>::const_iterator i = notTLControlledNodes.begin(); i != notTLControlledNodes.end(); ++i) {
    2191            1 :             NBNode* n = NBNodeCont::retrieve(*i);
    2192            1 :             if (n == nullptr) {
    2193            0 :                 throw ProcessError(TLF(" The junction '%' to set as not-controlled is not known.", *i));
    2194              :             }
    2195              :             std::set<NBTrafficLightDefinition*> tls = n->getControllingTLS();
    2196            1 :             for (std::set<NBTrafficLightDefinition*>::const_iterator j = tls.begin(); j != tls.end(); ++j) {
    2197            0 :                 (*j)->removeNode(n);
    2198              :             }
    2199            1 :             n->removeTrafficLights();
    2200              :             myUnsetTLS.insert(n);
    2201              :         }
    2202            1 :     }
    2203              : 
    2204         2206 :     TrafficLightType type = SUMOXMLDefinitions::TrafficLightTypes.get(OptionsCont::getOptions().getString("tls.default-type"));
    2205              :     // loop#1 checking whether the node shall be tls controlled,
    2206              :     //  because it is assigned to a district
    2207         4324 :     if (oc.exists("tls.taz-nodes") && oc.getBool("tls.taz-nodes")) {
    2208            0 :         for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2209            0 :             NBNode* cur = (*i).second;
    2210            0 :             if (cur->isNearDistrict() && myUnsetTLS.count(cur) == 0) {
    2211            0 :                 setAsTLControlled(cur, tlc, type);
    2212              :             }
    2213              :         }
    2214              :     }
    2215              : 
    2216              :     // figure out which nodes mark the locations of TLS signals
    2217              :     // This assumes nodes are already joined
    2218         4324 :     if (oc.exists("tls.guess-signals") && oc.getBool("tls.guess-signals")) {
    2219              :         // prepare candidate edges
    2220           92 :         const double signalDist = oc.getFloat("tls.guess-signals.dist");
    2221        12348 :         for (const auto& item : myNodes) {
    2222        12302 :             const NBNode* node = item.second;
    2223        12302 :             if (node->isTLControlled() && (node->getIncomingEdges().size() == 1 || node->geometryLike())) {
    2224              : #ifdef DEBUG_GUESSSIGNALS
    2225              :                 if (DEBUGCOND(node) || true) {
    2226              :                     std::cout << " propagate TLS from " << node->getID() << " downstream\n";
    2227              :                 }
    2228              : #endif
    2229          181 :                 for (NBEdge* edge : node->getOutgoingEdges()) {
    2230              :                     // do not overwrite closer signals
    2231          111 :                     if (edge->getSignalOffset() == NBEdge::UNSPECIFIED_SIGNAL_OFFSET) {
    2232              :                         edge->setSignalPosition(node->getPosition(), node);
    2233              :                     }
    2234              :                 }
    2235              :             }
    2236              :         }
    2237              :         std::set<NBEdge*> seen;
    2238              :         std::set<NBEdge*> check;
    2239        12348 :         for (const auto& item : myNodes) {
    2240        32956 :             for (NBEdge* edge : item.second->getOutgoingEdges()) {
    2241              :                 if (edge->getSignalPosition() != Position::INVALID) {
    2242              :                     check.insert(edge);
    2243              :                     seen.insert(edge);
    2244              : #ifdef DEBUG_GUESSSIGNALS
    2245              :                     if (DEBUGCOND(edge->getSignalNode()) || true) {
    2246              :                         std::cout << "   primary signalPosition edge=" << edge->getID() << " pos=" << edge->getSignalPosition() << "\n";
    2247              :                     }
    2248              : #endif
    2249              :                 }
    2250              :             }
    2251              :         }
    2252              :         // propagate signal position until the next real intersection
    2253         1137 :         while (check.size() > 0) {
    2254         1091 :             NBEdge* const edge = *check.begin();
    2255              :             check.erase(check.begin());
    2256              :             seen.insert(edge);
    2257              :             NBNode* const nextNode = edge->getToNode();
    2258         1091 :             if (nextNode->geometryLike() && !nextNode->isTLControlled()) {
    2259          523 :                 for (NBEdge* const outEdge : nextNode->getOutgoingEdges()) {
    2260              :                     if (seen.count(outEdge) == 0) {
    2261              :                         outEdge->setSignalPosition(edge->getSignalPosition(), edge->getSignalNode());
    2262              : #ifdef DEBUG_GUESSSIGNALS
    2263              :                         if (DEBUGCOND(edge->getSignalNode()) || true) {
    2264              :                             std::cout << "   setSignalPosition edge=" << outEdge->getID() << " pos=" << edge->getSignalPosition() << "\n";
    2265              :                         }
    2266              : #endif
    2267              :                         check.insert(outEdge);
    2268              :                     }
    2269              :                 }
    2270              :             }
    2271              :         }
    2272              : 
    2273              :         // check which nodes should be controlled
    2274           92 :         const int slack = oc.getInt("tls.guess-signals.slack");
    2275        12348 :         for (std::map<std::string, NBNode*>::const_iterator i = myNodes.begin(); i != myNodes.end(); ++i) {
    2276        12302 :             NBNode* node = i->second;
    2277            1 :             if (myUnsetTLS.count(node) != 0) {
    2278              :                 continue;
    2279              :             }
    2280              :             const EdgeVector& incoming = node->getIncomingEdges();
    2281              :             const EdgeVector& outgoing = node->getOutgoingEdges();
    2282        11958 :             if (!node->isTLControlled() && incoming.size() > 1 && !node->geometryLike()
    2283         5292 :                     && !NBNodeTypeComputer::isRailwayNode(node)
    2284        17379 :                     && node->getType() != SumoXMLNodeType::RAIL_CROSSING) {
    2285              :                 std::vector<const NBNode*> signals;
    2286              :                 int foundSignals = 0;
    2287              :                 int missingSignals = 0;
    2288              :                 // check if there is a signal at every incoming edge
    2289        12151 :                 for (EdgeVector::const_iterator it_i = incoming.begin(); it_i != incoming.end(); ++it_i) {
    2290         9371 :                     const NBEdge* inEdge = *it_i;
    2291         9371 :                     if (inEdge->getSignalOffset() == NBEdge::UNSPECIFIED_SIGNAL_OFFSET) {
    2292         9042 :                         if ((inEdge->getPermissions() & SVC_PASSENGER) != 0) {
    2293              : #ifdef DEBUG_GUESSSIGNALS
    2294              :                             if (DEBUGCOND(node)) {
    2295              :                                 std::cout << " noTLS, edge=" << inEdge->getID() << "\n";
    2296              :                             }
    2297              : #endif
    2298         2182 :                             missingSignals++;
    2299         2182 :                             if (missingSignals > slack) {
    2300              :                                 break;
    2301              :                             }
    2302              :                         }
    2303              :                     } else {
    2304          329 :                         foundSignals++;
    2305              :                     }
    2306              :                 }
    2307              :                 missingSignals = 0;
    2308              :                 int foundSignalsAtDist = 0;
    2309         4958 :                 if (foundSignals > 1 && missingSignals <= slack && missingSignals < foundSignals) {
    2310           57 :                     node->updateSurroundingGeometry();
    2311              :                     // check if all signals are within the required distance
    2312              :                     // (requires detailed geometry computation)
    2313          237 :                     for (EdgeVector::const_iterator it_i = incoming.begin(); it_i != incoming.end(); ++it_i) {
    2314          189 :                         const NBEdge* inEdge = *it_i;
    2315          189 :                         if (inEdge->getSignalOffset() == NBEdge::UNSPECIFIED_SIGNAL_OFFSET || inEdge->getSignalOffset() > signalDist) {
    2316           19 :                             if ((inEdge->getPermissions() & SVC_PASSENGER) != 0) {
    2317              : #ifdef DEBUG_GUESSSIGNALS
    2318              :                                 if (DEBUGCOND(node)) {
    2319              :                                     std::cout << " noTLS, edge=" << inEdge->getID() << " offset=" << inEdge->getSignalOffset() << " tlsPos=" << inEdge->getSignalPosition() << "\n";
    2320              :                                 }
    2321              : #endif
    2322            9 :                                 missingSignals++;
    2323            9 :                                 if (missingSignals > slack) {
    2324              :                                     break;
    2325              :                                 }
    2326              :                             }
    2327              :                         } else {
    2328          170 :                             foundSignalsAtDist++;
    2329              :                         }
    2330          180 :                         const NBNode* signal = inEdge->getSignalNode();
    2331          180 :                         if (signal != nullptr) {
    2332           12 :                             signals.push_back(signal);
    2333              :                         }
    2334              :                     }
    2335              :                     // outgoing edges may be tagged with pedestrian crossings. These
    2336              :                     // should also be merged into the main TLS
    2337          243 :                     for (const NBEdge* outEdge : outgoing) {
    2338          186 :                         NBNode* cand = outEdge->getToNode();
    2339          186 :                         if (cand->isTLControlled() && cand->geometryLike() && outEdge->getLength() <= signalDist) {
    2340              : #ifdef DEBUG_GUESSSIGNALS
    2341              :                             if (DEBUGCOND(node)) {
    2342              :                                 std::cout << " node=" << node->getID() << " outEdge=" << outEdge->getID() << " signalNode=" << cand->getID() << " len=" << outEdge->getLength() << "\n";
    2343              :                             }
    2344              : #endif
    2345            1 :                             signals.push_back(cand);
    2346              :                         }
    2347              :                     }
    2348              :                 }
    2349         4958 :                 if (foundSignalsAtDist > 1 && missingSignals <= slack && missingSignals < foundSignalsAtDist) {
    2350           61 :                     for (const NBNode* s : signals) {
    2351              :                         std::set<NBTrafficLightDefinition*> tls = s->getControllingTLS();
    2352           13 :                         const_cast<NBNode*>(s)->reinit(s->getPosition(), SumoXMLNodeType::PRIORITY);
    2353           24 :                         for (std::set<NBTrafficLightDefinition*>::iterator k = tls.begin(); k != tls.end(); ++k) {
    2354           22 :                             tlc.removeFully(s->getID());
    2355              :                         }
    2356              :                     }
    2357              :                     //if (true) std::cout << " node=" << node->getID() << " signals=" << toString(signals) << "\n";
    2358           48 :                     NBTrafficLightDefinition* tlDef = new NBOwnTLDef("GS_" + node->getID(), node, 0, type);
    2359              :                     // @todo patch endOffset for all incoming lanes according to the signal positions
    2360           48 :                     if (!tlc.insert(tlDef)) {
    2361              :                         // actually, nothing should fail here
    2362            0 :                         WRITE_WARNINGF(TL("Could not build joined tls '%'."), node->getID());
    2363            0 :                         delete tlDef;
    2364              :                         return;
    2365              :                     }
    2366              :                 }
    2367         4958 :             }
    2368              :         }
    2369              :     }
    2370              : 
    2371              :     // guess joined tls first, if wished
    2372         4412 :     if (oc.getBool("tls.guess.joining")) {
    2373              :         // get node clusters
    2374              :         NodeClusters cands;
    2375            8 :         generateNodeClusters(oc.getFloat("tls.join-dist"), cands);
    2376              :         // check these candidates (clusters) whether they should be controlled by a tls
    2377           38 :         for (NodeClusters::iterator i = cands.begin(); i != cands.end();) {
    2378              :             NodeSet& c = (*i);
    2379              :             // regard only junctions which are not yet controlled and are not
    2380              :             //  forbidden to be controlled
    2381          245 :             for (NodeSet::iterator j = c.begin(); j != c.end();) {
    2382          211 :                 if ((*j)->isTLControlled() || myUnsetTLS.count(*j) != 0) {
    2383              :                     c.erase(j++);
    2384              :                 } else {
    2385              :                     ++j;
    2386              :                 }
    2387              :             }
    2388              :             // check whether the cluster should be controlled
    2389              :             // to avoid gigantic clusters, assume that at most 4 nodes should be needed for a guessed-joined-tls
    2390           50 :             if (c.size() == 0 || !shouldBeTLSControlled(c, laneSpeedThreshold * (double)c.size() / MIN2((double)c.size(), 4.))) {
    2391              :                 i = cands.erase(i);
    2392              :             } else {
    2393              :                 ++i;
    2394              :             }
    2395              :         }
    2396              :         // cands now only contain sets of junctions that shall be joined into being tls-controlled
    2397           14 :         for (auto nodeSet : cands) {
    2398              :             std::vector<NBNode*> nodes;
    2399           53 :             for (NBNode* node : nodeSet) {
    2400           43 :                 nodes.push_back(node);
    2401              :                 myGuessedTLS.insert(node);
    2402              :             }
    2403           10 :             const std::string& id = createClusterId(nodeSet, "joinedG_");
    2404           10 :             NBTrafficLightDefinition* tlDef = new NBOwnTLDef(id, nodes, 0, type);
    2405           10 :             if (!tlc.insert(tlDef)) {
    2406              :                 // actually, nothing should fail here
    2407            0 :                 WRITE_WARNING(TL("Could not build guessed, joined tls."));
    2408            0 :                 delete tlDef;
    2409              :                 return;
    2410              :             }
    2411           10 :         }
    2412            4 :     }
    2413              : 
    2414              :     // guess single tls
    2415         4412 :     if (oc.getBool("tls.guess")) {
    2416          922 :         for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2417          902 :             NBNode* cur = (*i).second;
    2418              :             //  do nothing if already is tl-controlled
    2419          902 :             if (cur->isTLControlled()) {
    2420          760 :                 continue;
    2421              :             }
    2422              :             // do nothing if in the list of explicit non-controlled junctions
    2423            1 :             if (myUnsetTLS.count(cur) != 0) {
    2424            1 :                 continue;
    2425              :             }
    2426              :             NodeSet c;
    2427              :             c.insert(cur);
    2428          831 :             if (!shouldBeTLSControlled(c, laneSpeedThreshold) || cur->geometryLike()) {
    2429              :                 continue;
    2430              :             }
    2431          284 :             setAsTLControlled(cur, tlc, type);
    2432              :             myGuessedTLS.insert(cur);
    2433              :         }
    2434              :     }
    2435              : }
    2436              : 
    2437              : 
    2438              : void
    2439         2205 : NBNodeCont::recheckGuessedTLS(NBTrafficLightLogicCont& tlc) {
    2440              :     std::set<NBTrafficLightDefinition*> recompute;
    2441         2390 :     for (NBNode* node : myGuessedTLS) {
    2442          185 :         if (!node->hasConflict() || !recheckTLSThreshold(node)) {
    2443              :             const std::set<NBTrafficLightDefinition*>& tlDefs = node->getControllingTLS();
    2444           21 :             recompute.insert(tlDefs.begin(), tlDefs.end());
    2445           21 :             node->removeTrafficLights(true);
    2446           49 :             for (NBEdge* edge : node->getIncomingEdges()) {
    2447           28 :                 edge->clearControllingTLInformation();
    2448              :             }
    2449              :         }
    2450              :     }
    2451         2217 :     for (NBTrafficLightDefinition* def : recompute) {
    2452           12 :         if (def->getNodes().size() == 0) {
    2453           10 :             tlc.removeFully(def->getID());
    2454              :         } else {
    2455            7 :             def->setParticipantsInformation();
    2456            7 :             def->setTLControllingInformation();
    2457            7 :             tlc.computeSingleLogic(OptionsCont::getOptions(), def);
    2458              :         }
    2459              :     }
    2460         2205 : }
    2461              : 
    2462              : 
    2463              : bool
    2464          166 : NBNodeCont::recheckTLSThreshold(NBNode* node) {
    2465          166 :     if (!node->isTLControlled()) {
    2466              :         return false;
    2467              :     }
    2468          166 :     if ((*node->getControllingTLS().begin())->getNodes().size() != 1) {
    2469              :         // unable to perform check for a joined tls
    2470              :         return true;
    2471              :     }
    2472              :     NodeSet c;
    2473              :     c.insert(node);
    2474          127 :     const double laneSpeedThreshold = OptionsCont::getOptions().getFloat("tls.guess.threshold");
    2475          127 :     return shouldBeTLSControlled(c, laneSpeedThreshold, true);
    2476              : }
    2477              : 
    2478              : 
    2479              : void
    2480         2205 : NBNodeCont::computeKeepClear() {
    2481        64041 :     for (const auto& item : myNodes) {
    2482        61836 :         item.second->computeKeepClear();
    2483              :     }
    2484         2205 : }
    2485              : 
    2486              : 
    2487              : void
    2488           36 : NBNodeCont::joinTLS(NBTrafficLightLogicCont& tlc, double maxdist) {
    2489           72 :     const std::vector<std::string> excludeList = OptionsCont::getOptions().getStringVector("tls.join-exclude");
    2490           38 :     for (const std::string& tlsID : excludeList) {
    2491            2 :         if (!tlc.exist(tlsID, false)) {
    2492            3 :             WRITE_WARNINGF("Unknown tls ID '%' in option tls.join-exclude", tlsID);
    2493              :         }
    2494              :     }
    2495           36 :     std::set<std::string> exclude(excludeList.begin(), excludeList.end());
    2496              :     NodeClusters cands;
    2497           36 :     generateNodeClusters(maxdist, cands);
    2498          656 :     for (NodeSet& c : cands) {
    2499         2392 :         for (NodeSet::iterator j = c.begin(); j != c.end();) {
    2500         1772 :             if (!(*j)->isTLControlled() || exclude.count((*(*j)->getControllingTLS().begin())->getID()) != 0) {
    2501              :                 c.erase(j++);
    2502              :             } else {
    2503              :                 ++j;
    2504              :             }
    2505              :         }
    2506          620 :         if (c.size() < 2 || onlyCrossings(c) || customTLID(c)) {
    2507          572 :             continue;
    2508              :         }
    2509              :         // figure out type of the joined TLS
    2510              :         Position dummyPos;
    2511           48 :         bool dummySetTL = false;
    2512           48 :         std::string id = "joinedS_"; // prefix (see #3871)
    2513              :         TrafficLightType type;
    2514           48 :         SumoXMLNodeType nodeType = SumoXMLNodeType::UNKNOWN;
    2515           96 :         analyzeCluster(c, id, dummyPos, dummySetTL, type, nodeType);
    2516          175 :         for (NBNode* j : c) {
    2517              :             std::set<NBTrafficLightDefinition*> tls = j->getControllingTLS();
    2518          127 :             j->removeTrafficLights();
    2519          254 :             for (NBTrafficLightDefinition* k : tls) {
    2520          254 :                 tlc.removeFully(k->getID());
    2521              :             }
    2522              :         }
    2523              :         std::vector<NBNode*> nodes;
    2524          175 :         for (NBNode* j : c) {
    2525          127 :             nodes.push_back(j);
    2526              :         }
    2527           48 :         NBTrafficLightDefinition* tlDef = new NBOwnTLDef(id, nodes, 0, type);
    2528           48 :         if (!tlc.insert(tlDef)) {
    2529              :             // actually, nothing should fail here
    2530            0 :             WRITE_WARNING(TL("Could not build a joined tls."));
    2531            0 :             delete tlDef;
    2532              :             return;
    2533              :         }
    2534           48 :     }
    2535           72 : }
    2536              : 
    2537              : 
    2538              : void
    2539          150 : NBNodeCont::setAsTLControlled(NBNode* node, NBTrafficLightLogicCont& tlc,
    2540              :                               TrafficLightType type, std::string id) {
    2541          150 :     if (id == "") {
    2542              :         id = node->getID();
    2543              :     }
    2544          150 :     NBTrafficLightDefinition* tlDef = new NBOwnTLDef(id, node, 0, type);
    2545          150 :     if (!tlc.insert(tlDef)) {
    2546              :         // actually, nothing should fail here
    2547            0 :         WRITE_WARNINGF(TL("Building a tl-logic for junction '%' twice is not possible."), id);
    2548            0 :         delete tlDef;
    2549            0 :         return;
    2550              :     }
    2551              : }
    2552              : 
    2553              : 
    2554              : // -----------
    2555              : void
    2556         2206 : NBNodeCont::computeLanes2Lanes() {
    2557        64046 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2558        61840 :         (*i).second->computeLanes2Lanes();
    2559              :     }
    2560         2206 : }
    2561              : 
    2562              : 
    2563              : // computes the "wheel" of incoming and outgoing edges for every node
    2564              : void
    2565         2205 : NBNodeCont::computeLogics(const NBEdgeCont& ec) {
    2566        64041 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2567        61836 :         (*i).second->computeLogic(ec);
    2568              :     }
    2569         2205 : }
    2570              : 
    2571              : 
    2572              : void
    2573         2205 : NBNodeCont::computeLogics2(const NBEdgeCont& ec, OptionsCont& oc) {
    2574              :     std::set<NBNode*> roundaboutNodes;
    2575         2205 :     const bool checkLaneFoesAll = oc.getBool("check-lane-foes.all");
    2576         2205 :     const bool checkLaneFoesRoundabout = !checkLaneFoesAll && oc.getBool("check-lane-foes.roundabout");
    2577         2205 :     if (checkLaneFoesRoundabout) {
    2578         2200 :         const std::set<EdgeSet>& roundabouts = ec.getRoundabouts();
    2579         2300 :         for (std::set<EdgeSet>::const_iterator i = roundabouts.begin(); i != roundabouts.end(); ++i) {
    2580          577 :             for (EdgeSet::const_iterator j = (*i).begin(); j != (*i).end(); ++j) {
    2581          477 :                 roundaboutNodes.insert((*j)->getToNode());
    2582              :             }
    2583              :         }
    2584              :     }
    2585        64041 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2586        61836 :         const bool checkLaneFoes = checkLaneFoesAll || (checkLaneFoesRoundabout && roundaboutNodes.count((*i).second) > 0);
    2587        61836 :         (*i).second->computeLogic2(checkLaneFoes);
    2588              :     }
    2589         2205 : }
    2590              : 
    2591              : 
    2592              : void
    2593         2506 : NBNodeCont::clear() {
    2594        65065 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2595        62559 :         delete ((*i).second);
    2596              :     }
    2597              :     myNodes.clear();
    2598        13831 :     for (auto& item : myExtractedNodes) {
    2599        11325 :         delete item.second;
    2600              :     }
    2601              :     myExtractedNodes.clear();
    2602         2506 : }
    2603              : 
    2604              : 
    2605              : std::string
    2606            0 : NBNodeCont::getFreeID() {
    2607            0 :     int counter = 0;
    2608            0 :     std::string freeID = "SUMOGenerated" + toString<int>(counter);
    2609              :     // While there is a node with id equal to freeID
    2610            0 :     while (retrieve(freeID) != nullptr) {
    2611              :         // update counter and generate a new freeID
    2612            0 :         counter++;
    2613            0 :         freeID = "SUMOGenerated" + toString<int>(counter);
    2614              :     }
    2615            0 :     return freeID;
    2616              : }
    2617              : 
    2618              : 
    2619              : void
    2620         2365 : NBNodeCont::computeNodeShapes(double mismatchThreshold) {
    2621        87476 :     for (NodeCont::iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2622        85111 :         (*i).second->computeNodeShape(mismatchThreshold);
    2623              :     }
    2624         2365 : }
    2625              : 
    2626              : 
    2627              : void
    2628         2203 : NBNodeCont::printBuiltNodesStatistics() const {
    2629         2203 :     WRITE_MESSAGE(TL("-----------------------------------------------------"));
    2630         2203 :     WRITE_MESSAGE(TL("Summary:"));
    2631              : 
    2632         2203 :     int numUnregulatedJunctions = 0;
    2633         2203 :     int numDeadEndJunctions = 0;
    2634         2203 :     int numTrafficLightJunctions = 0;
    2635         2203 :     int numPriorityJunctions = 0;
    2636         2203 :     int numRightBeforeLeftJunctions = 0;
    2637         2203 :     int numLeftBeforeRightJunctions = 0;
    2638         2203 :     int numAllWayStopJunctions = 0;
    2639         2203 :     int numZipperJunctions = 0;
    2640         2203 :     int numDistrictJunctions = 0;
    2641         2203 :     int numRailCrossing = 0;
    2642         2203 :     int numRailSignals = 0;
    2643        64028 :     for (NodeCont::const_iterator i = myNodes.begin(); i != myNodes.end(); i++) {
    2644        61825 :         switch ((*i).second->getType()) {
    2645          155 :             case SumoXMLNodeType::NOJUNCTION:
    2646          155 :                 ++numUnregulatedJunctions;
    2647          155 :                 break;
    2648        15420 :             case SumoXMLNodeType::DEAD_END:
    2649        15420 :                 ++numDeadEndJunctions;
    2650        15420 :                 break;
    2651         2760 :             case SumoXMLNodeType::TRAFFIC_LIGHT:
    2652              :             case SumoXMLNodeType::TRAFFIC_LIGHT_RIGHT_ON_RED:
    2653              :             case SumoXMLNodeType::TRAFFIC_LIGHT_NOJUNCTION:
    2654         2760 :                 ++numTrafficLightJunctions;
    2655         2760 :                 break;
    2656        37104 :             case SumoXMLNodeType::PRIORITY:
    2657              :             case SumoXMLNodeType::PRIORITY_STOP:
    2658        37104 :                 ++numPriorityJunctions;
    2659        37104 :                 break;
    2660         4405 :             case SumoXMLNodeType::RIGHT_BEFORE_LEFT:
    2661         4405 :                 ++numRightBeforeLeftJunctions;
    2662         4405 :                 break;
    2663           13 :             case SumoXMLNodeType::LEFT_BEFORE_RIGHT:
    2664           13 :                 ++numLeftBeforeRightJunctions;
    2665           13 :                 break;
    2666            6 :             case SumoXMLNodeType::ALLWAY_STOP:
    2667            6 :                 ++numAllWayStopJunctions;
    2668            6 :                 break;
    2669           98 :             case SumoXMLNodeType::ZIPPER:
    2670           98 :                 ++numZipperJunctions;
    2671           98 :                 break;
    2672            0 :             case SumoXMLNodeType::DISTRICT:
    2673            0 :                 ++numDistrictJunctions;
    2674            0 :                 break;
    2675          183 :             case SumoXMLNodeType::RAIL_CROSSING:
    2676          183 :                 ++numRailCrossing;
    2677          183 :                 break;
    2678         1681 :             case SumoXMLNodeType::RAIL_SIGNAL:
    2679         1681 :                 ++numRailSignals;
    2680         1681 :                 break;
    2681              :             case SumoXMLNodeType::UNKNOWN:
    2682              :                 // should not happen
    2683              :                 break;
    2684              :             default:
    2685              :                 break;
    2686              :         }
    2687              :     }
    2688         2203 :     WRITE_MESSAGE(TL(" Node type statistics:"));
    2689         4406 :     WRITE_MESSAGE("  Unregulated junctions       : " + toString(numUnregulatedJunctions));
    2690         2203 :     if (numDeadEndJunctions > 0) {
    2691         4269 :         WRITE_MESSAGE("  Dead-end junctions          : " + toString(numDeadEndJunctions));
    2692              :     }
    2693         4406 :     WRITE_MESSAGE("  Priority junctions          : " + toString(numPriorityJunctions));
    2694         4406 :     WRITE_MESSAGE("  Right-before-left junctions : " + toString(numRightBeforeLeftJunctions));
    2695         2203 :     if (numLeftBeforeRightJunctions > 0) {
    2696           24 :         WRITE_MESSAGE("  Left-before-right junctions      : " + toString(numLeftBeforeRightJunctions));
    2697              :     }
    2698         2203 :     if (numTrafficLightJunctions > 0) {
    2699         2049 :         WRITE_MESSAGE("  Traffic light junctions      : " + toString(numTrafficLightJunctions));
    2700              :     }
    2701         2203 :     if (numAllWayStopJunctions > 0) {
    2702            6 :         WRITE_MESSAGE("  All-way stop junctions      : " + toString(numAllWayStopJunctions));
    2703              :     }
    2704         2203 :     if (numZipperJunctions > 0) {
    2705           96 :         WRITE_MESSAGE("  Zipper-merge junctions      : " + toString(numZipperJunctions));
    2706              :     }
    2707         2203 :     if (numRailCrossing > 0) {
    2708           84 :         WRITE_MESSAGE("  Rail crossing junctions      : " + toString(numRailCrossing));
    2709              :     }
    2710         2203 :     if (numRailSignals > 0) {
    2711          222 :         WRITE_MESSAGE("  Rail signal junctions      : " + toString(numRailSignals));
    2712              :     }
    2713         2203 :     if (numDistrictJunctions > 0) {
    2714            0 :         WRITE_MESSAGE("  District junctions      : " + toString(numDistrictJunctions));
    2715              :     }
    2716              :     const GeoConvHelper& geoConvHelper = GeoConvHelper::getProcessing();
    2717         2203 :     WRITE_MESSAGE(TL(" Network boundaries:"));
    2718         4406 :     WRITE_MESSAGE("  Original boundary  : " + toString(geoConvHelper.getOrigBoundary()));
    2719         4406 :     WRITE_MESSAGE("  Applied offset     : " + toString(geoConvHelper.getOffsetBase()));
    2720         4406 :     WRITE_MESSAGE("  Converted boundary : " + toString(geoConvHelper.getConvBoundary()));
    2721         2203 :     WRITE_MESSAGE(TL("-----------------------------------------------------"));
    2722         2203 : }
    2723              : 
    2724              : 
    2725              : std::vector<std::string>
    2726           79 : NBNodeCont::getAllNames() const {
    2727              :     std::vector<std::string> ret;
    2728        15468 :     for (NodeCont::const_iterator i = myNodes.begin(); i != myNodes.end(); ++i) {
    2729        15389 :         ret.push_back((*i).first);
    2730              :     }
    2731           79 :     return ret;
    2732            0 : }
    2733              : 
    2734              : 
    2735              : void
    2736            0 : NBNodeCont::addPrefix(const std::string& prefix) {
    2737              :     // make a copy of node containers
    2738              :     const auto nodeContainerCopy = myNodes;
    2739              :     myNodes.clear();
    2740            0 :     for (const auto& node : nodeContainerCopy) {
    2741            0 :         node.second->setID(prefix + node.second->getID());
    2742            0 :         myNodes[node.second->getID()] = node.second;
    2743              :     }
    2744            0 : }
    2745              : 
    2746              : 
    2747              : void
    2748           83 : NBNodeCont::rename(NBNode* node, const std::string& newID) {
    2749              :     if (myNodes.count(newID) != 0) {
    2750            0 :         throw ProcessError(TLF("Attempt to rename node using existing id '%'", newID));
    2751              :     }
    2752              :     myNodes.erase(node->getID());
    2753           83 :     node->setID(newID);
    2754           83 :     myNodes[newID] = node;
    2755           83 : }
    2756              : 
    2757              : 
    2758              : void
    2759           56 : NBNodeCont::discardTrafficLights(NBTrafficLightLogicCont& tlc, bool geometryLike) {
    2760        19135 :     for (NodeCont::const_iterator i = myNodes.begin(); i != myNodes.end(); ++i) {
    2761        19079 :         NBNode* node = i->second;
    2762        19079 :         if (node->isTLControlled() && (!geometryLike || node->geometryLike())) {
    2763              :             // make a copy of tldefs
    2764              :             const std::set<NBTrafficLightDefinition*> tldefs = node->getControllingTLS();
    2765          958 :             if (geometryLike && (*tldefs.begin())->getNodes().size() > 1) {
    2766              :                 // do not remove joined tls when only removing geometry-like tls
    2767            1 :                 continue;
    2768              :             }
    2769          957 :             if (node->getCrossings().size() > 0) {
    2770              :                 // keep controlled pedestrian crossings
    2771            5 :                 continue;
    2772              :             }
    2773              :             // record signal location
    2774         2299 :             for (NBEdge* edge : node->getOutgoingEdges()) {
    2775              :                 edge->setSignalPosition(node->getPosition(), nullptr);
    2776              : #ifdef DEBUG_GUESSSIGNALS
    2777              :                 std::cout << "   discard-simple " << node->getID() << "  edge=" << edge->getID() << " pos=" << edge->getSignalPosition() << "\n";
    2778              : #endif
    2779              :             }
    2780         1905 :             for (std::set<NBTrafficLightDefinition*>::const_iterator it = tldefs.begin(); it != tldefs.end(); ++it) {
    2781          953 :                 NBTrafficLightDefinition* tlDef = *it;
    2782          953 :                 node->removeTrafficLight(tlDef);
    2783          953 :                 tlc.extract(tlDef);
    2784              :             }
    2785          952 :             SumoXMLNodeType newType = NBNodeTypeComputer::isRailwayNode(node) ? SumoXMLNodeType::RAIL_SIGNAL : SumoXMLNodeType::UNKNOWN;
    2786          952 :             node->reinit(node->getPosition(), newType);
    2787              :         }
    2788              :     }
    2789           56 : }
    2790              : 
    2791              : 
    2792              : void
    2793            1 : NBNodeCont::discardRailSignals() {
    2794           35 :     for (auto& item : myNodes) {
    2795           34 :         NBNode* node = item.second;
    2796           34 :         if (node->getType() == SumoXMLNodeType::RAIL_SIGNAL) {
    2797            5 :             node->reinit(node->getPosition(), SumoXMLNodeType::PRIORITY);
    2798              :         }
    2799              :     }
    2800            1 : }
    2801              : 
    2802              : 
    2803              : int
    2804         2206 : NBNodeCont::remapIDs(bool numericaIDs, bool reservedIDs, bool keptIDs, const std::string& prefix, NBTrafficLightLogicCont& tlc) {
    2805         2206 :     bool startGiven = !OptionsCont::getOptions().isDefault("numerical-ids.node-start");
    2806         2206 :     if (!numericaIDs && !reservedIDs && prefix == "" && !startGiven) {
    2807              :         return 0;
    2808              :     }
    2809              :     std::vector<std::string> avoid;
    2810           76 :     if (startGiven) {
    2811            6 :         avoid.push_back(toString(OptionsCont::getOptions().getInt("numerical-ids.node-start") - 1));
    2812              :     } else {
    2813           73 :         avoid = getAllNames();
    2814              :     }
    2815              :     std::set<std::string> reserve;
    2816           76 :     if (reservedIDs) {
    2817            4 :         NBHelpers::loadPrefixedIDsFomFile(OptionsCont::getOptions().getString("reserved-ids"), "node:", reserve); // backward compatibility
    2818            4 :         NBHelpers::loadPrefixedIDsFomFile(OptionsCont::getOptions().getString("reserved-ids"), "junction:", reserve); // selection format
    2819            2 :         avoid.insert(avoid.end(), reserve.begin(), reserve.end());
    2820              :     }
    2821          152 :     IDSupplier idSupplier("", avoid);
    2822              :     NodeSet toChange;
    2823        15146 :     for (NodeCont::iterator it = myNodes.begin(); it != myNodes.end(); it++) {
    2824        15070 :         if (startGiven) {
    2825           36 :             toChange.insert(it->second);
    2826           36 :             continue;
    2827              :         }
    2828        15034 :         if (numericaIDs) {
    2829              :             try {
    2830        14954 :                 StringUtils::toLong(it->first);
    2831          528 :             } catch (NumberFormatException&) {
    2832          528 :                 toChange.insert(it->second);
    2833          528 :             }
    2834              :         }
    2835        15034 :         if (reservedIDs && reserve.count(it->first) > 0) {
    2836            2 :             toChange.insert(it->second);
    2837              :         }
    2838              :     }
    2839              :     std::set<std::string> keep;
    2840           76 :     if (keptIDs) {
    2841            4 :         NBHelpers::loadPrefixedIDsFomFile(OptionsCont::getOptions().getString("kept-ids"), "node:", keep); // backward compatibility
    2842            4 :         NBHelpers::loadPrefixedIDsFomFile(OptionsCont::getOptions().getString("kept-ids"), "junction:", keep); // selection format
    2843            7 :         for (auto it = toChange.begin(); it != toChange.end();) {
    2844            5 :             if (keep.count((*it)->getID()) != 0) {
    2845              :                 it = toChange.erase(it++);
    2846              :             } else {
    2847              :                 it++;
    2848              :             }
    2849              :         }
    2850              :     }
    2851          152 :     const bool origNames = OptionsCont::getOptions().getBool("output.original-names");
    2852          641 :     for (NBNode* node : toChange) {
    2853              :         myNodes.erase(node->getID());
    2854              :     }
    2855          641 :     for (NBNode* node : toChange) {
    2856         1569 :         if (origNames && node->getParameter(SUMO_PARAM_ORIGID) == "") {
    2857          486 :             node->setParameter(SUMO_PARAM_ORIGID, node->getID());
    2858              :         }
    2859         1130 :         node->setID(idSupplier.getNext());
    2860          657 :         for (NBTrafficLightDefinition* tlDef : node->getControllingTLS()) {
    2861           92 :             tlc.rename(tlDef, node->getID());
    2862              :         }
    2863          565 :         myNodes[node->getID()] = node;
    2864              :     }
    2865           76 :     if (prefix.empty()) {
    2866           66 :         return (int)toChange.size();
    2867              :     } else {
    2868              :         int renamed = 0;
    2869              :         // make a copy because we will modify the map
    2870              :         auto oldNodes = myNodes;
    2871           95 :         for (auto item : oldNodes) {
    2872          170 :             if (!StringUtils::startsWith(item.first, prefix) && keep.count(item.first) == 0) {
    2873           83 :                 rename(item.second, prefix + item.first);
    2874           91 :                 for (NBTrafficLightDefinition* tlDef : item.second->getControllingTLS()) {
    2875           16 :                     if (!StringUtils::startsWith(tlDef->getID(), prefix)) {
    2876           12 :                         tlc.rename(tlDef, prefix + tlDef->getID());
    2877              :                     }
    2878              :                 }
    2879           83 :                 renamed++;
    2880              :             }
    2881              :         }
    2882              :         return renamed;
    2883              :     }
    2884          152 : }
    2885              : 
    2886              : 
    2887              : int
    2888            2 : NBNodeCont::guessFringe() {
    2889              :     // guess outer fringe by topology and being on the pareto-boundary
    2890              :     NodeSet topRightFront;
    2891              :     NodeSet topLeftFront;
    2892              :     NodeSet bottomRightFront;
    2893              :     NodeSet bottomLeftFront;
    2894           67 :     for (const auto& item : myNodes) {
    2895           65 :         paretoCheck(item.second, topRightFront, 1, 1);
    2896           65 :         paretoCheck(item.second, topLeftFront, -1, 1);
    2897           65 :         paretoCheck(item.second, bottomRightFront, 1, -1);
    2898           65 :         paretoCheck(item.second, bottomLeftFront, -1, -1);
    2899              :     }
    2900              :     NodeSet front;
    2901            2 :     front.insert(topRightFront.begin(), topRightFront.end());
    2902            2 :     front.insert(topLeftFront.begin(), topLeftFront.end());
    2903            2 :     front.insert(bottomRightFront.begin(), bottomRightFront.end());
    2904            2 :     front.insert(bottomLeftFront.begin(), bottomLeftFront.end());
    2905              :     int numFringe = 0;
    2906           19 :     for (NBNode* n : front) {
    2907           17 :         const int in = (int)n->getIncomingEdges().size();
    2908           17 :         const int out = (int)n->getOutgoingEdges().size();
    2909           17 :         if ((in <= 1 && out <= 1) &&
    2910           12 :                 (in == 0 || out == 0
    2911            8 :                  || n->getIncomingEdges().front()->isTurningDirectionAt(n->getOutgoingEdges().front()))) {
    2912              :             n->setFringeType(FringeType::OUTER);
    2913            9 :             numFringe++;
    2914              :         }
    2915              :     }
    2916              :     // guess outer fringe by topology and speed
    2917            4 :     const double speedThreshold = OptionsCont::getOptions().getFloat("fringe.guess.speed-threshold");
    2918           67 :     for (const auto& item : myNodes) {
    2919           65 :         NBNode* n = item.second;
    2920           17 :         if (front.count(n) != 0) {
    2921           17 :             continue;
    2922              :         }
    2923           48 :         if (n->getEdges().size() == 1 && n->getEdges().front()->getSpeed() > speedThreshold) {
    2924              :             n->setFringeType(FringeType::OUTER);
    2925            2 :             numFringe++;
    2926              :         }
    2927              :     }
    2928            2 :     return numFringe;
    2929              : }
    2930              : 
    2931              : 
    2932              : void
    2933          260 : NBNodeCont::paretoCheck(NBNode* node, NodeSet& frontier, int xSign, int ySign) {
    2934          260 :     const double x = node->getPosition().x() * xSign;
    2935          260 :     const double y = node->getPosition().y() * ySign;
    2936              :     std::vector<NBNode*> dominated;
    2937          556 :     for (NBNode* fn : frontier) {
    2938          484 :         const double x2 = fn->getPosition().x() * xSign;
    2939          484 :         const double y2 = fn->getPosition().y() * ySign;
    2940          484 :         if (x2 >= x && y2 >= y) {
    2941          188 :             return;
    2942          296 :         } else if (x2 <= x && y2 <= y) {
    2943           52 :             dominated.push_back(fn);
    2944              :         }
    2945              :     }
    2946              :     frontier.insert(node);
    2947          124 :     for (NBNode* r : dominated) {
    2948              :         frontier.erase(r);
    2949              :     }
    2950          260 : }
    2951              : 
    2952              : 
    2953              : void
    2954         2241 : NBNodeCont::applyConditionalDefaults() {
    2955        72250 :     for (const auto& item : myNodes) {
    2956        70009 :         NBNode* n = item.second;
    2957        70009 :         if (n->isTLControlled() && n->getRightOfWay() == RightOfWay::DEFAULT) {
    2958              :             bool hasNEMA = false;
    2959         6356 :             for (NBTrafficLightDefinition* tl : n->getControllingTLS()) {
    2960         3192 :                 if (tl->getType() == TrafficLightType::NEMA) {
    2961              :                     hasNEMA = true;
    2962              :                     break;
    2963              :                 }
    2964              :             }
    2965         3178 :             if (hasNEMA) {
    2966              :                 // NEMA controller defaults to allway_stop behavior when switched off
    2967              :                 n->setRightOfWay(RightOfWay::ALLWAYSTOP);
    2968              :             }
    2969              :         }
    2970              :     }
    2971         2241 : }
    2972              : 
    2973              : 
    2974              : bool
    2975         2206 : NBNodeCont::resetNodeShapes() {
    2976              :     bool hadShapes = false;
    2977        63844 :     for (const auto& item : myNodes) {
    2978        61638 :         if (item.second->getShape().size() > 0 && !item.second->hasCustomShape()) {
    2979              :             hadShapes = true;
    2980              :             item.second->resetShape();
    2981              :         }
    2982              :     }
    2983         2206 :     return hadShapes;
    2984              : }
    2985              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1