Eclipse SUMO - Simulation of Urban MObility
NBNodeShapeComputer.cpp
Go to the documentation of this file.
1 /****************************************************************************/
2 // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 // Copyright (C) 2001-2024 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 /****************************************************************************/
20 // This class computes shapes of junctions
21 /****************************************************************************/
22 #include <config.h>
23 
24 #include <algorithm>
25 #include <iterator>
28 #include <utils/geom/GeomHelper.h>
29 #include <utils/common/StdDefs.h>
32 #include <utils/common/ToString.h>
34 #include "NBNode.h"
35 #include "NBAlgorithms.h"
36 #include "NBNodeShapeComputer.h"
37 
38 //#define DEBUG_NODE_SHAPE
39 //#define DEBUG_SMOOTH_CORNERS
40 //#define DEBUG_RADIUS
41 #define DEBUGCOND (myNode.getID() == "C")
42 
43 
44 #define EXT2 10.0
45 
46 // foot and bicycle paths as well as pure service roads should not get large junctions
47 // railways also do have have junctions with sharp turns so can be excluded
50 
51 // ===========================================================================
52 // method definitions
53 // ===========================================================================
55  myNode(node),
56  myRadius(node.getRadius()) {
57  if (node.getEdges().size() > 4 && !NBNodeTypeComputer::isRailwayNode(&node)) {
58  EXT = 50;
59  } else {
60  EXT = 100;
61  }
62 }
63 
64 
66 
67 
68 const PositionVector
69 NBNodeShapeComputer::compute(bool forceSmall) {
70 #ifdef DEBUG_NODE_SHAPE
71  if (DEBUGCOND) {
72  // annotate edges edges to make their ordering visible
73  int i = 0;
74  for (NBEdge* e : myNode.getEdges()) {
75  e->setStreetName(toString(i));
76  i++;
77  }
78  }
79 #endif
80  // check whether the node is a dead end node or a node where only turning is possible
81  // in this case, we will use "computeNodeShapeSmall"
82  if (myNode.getEdges().size() == 1 || forceSmall) {
83  return computeNodeShapeSmall();
84  }
85  if (myNode.getEdges().size() == 2 && myNode.getIncomingEdges().size() == 1) {
86  if (myNode.getIncomingEdges()[0]->isTurningDirectionAt(myNode.getOutgoingEdges()[0])) {
87  return computeNodeShapeSmall();
88  }
89  }
90  const bool geometryLike = myNode.isSimpleContinuation(true, true);
91  const PositionVector& ret = computeNodeShapeDefault(geometryLike);
92  // fail fall-back: use "computeNodeShapeSmall"
93  if (ret.size() < 3) {
94  return computeNodeShapeSmall();
95  }
96  return ret;
97 }
98 
99 
100 void
102  assert(l1[0].distanceTo2D(l1[1]) >= EXT);
103  assert(l2[0].distanceTo2D(l2[1]) >= EXT);
104  PositionVector tmp;
105  tmp.push_back(PositionVector::positionAtOffset2D(l1[0], l1[1], EXT));
106  tmp.push_back(l1[1]);
107  tmp[1].sub(tmp[0]);
108  tmp[1].set(-tmp[1].y(), tmp[1].x());
109  tmp[1].add(tmp[0]);
110  tmp.extrapolate2D(EXT);
111  if (l2.intersects(tmp[0], tmp[1])) {
112  const double offset = l2.intersectsAtLengths2D(tmp)[0];
113  if (l2.length2D() - offset > POSITION_EPS) {
114  PositionVector tl2 = l2.getSubpart2D(offset, l2.length2D());
115  tl2.extrapolate2D(EXT);
116  l2.erase(l2.begin(), l2.begin() + (l2.size() - tl2.size()));
117  l2[0] = tl2[0];
118  }
119  }
120 }
121 
122 
123 const PositionVector
125  // if we have less than two edges, we can not compute the node's shape this way
126  if (myNode.getEdges().size() < 2) {
127  return PositionVector();
128  }
129  // magic values
130  const OptionsCont& oc = OptionsCont::getOptions();
131  const double defaultRadius = getDefaultRadius(oc);
132  const bool useDefaultRadius = myNode.getRadius() == NBNode::UNSPECIFIED_RADIUS || myNode.getRadius() == defaultRadius;
133  myRadius = (useDefaultRadius ? defaultRadius : myNode.getRadius());
134  double smallRadius = useDefaultRadius ? oc.getFloat("junctions.small-radius") : myRadius;
135  const int cornerDetail = oc.getInt("junctions.corner-detail");
136  const double sCurveStretch = oc.getFloat("junctions.scurve-stretch");
137  const bool useEndpoints = oc.getBool("junctions.endpoint-shape");
138  const bool rectangularCut = oc.getBool("rectangular-lane-cut");
139  const bool openDriveOutput = oc.isSet("opendrive-output");
140 
141  // Extend geometries to move the stop line forward.
142  // In OpenDrive the junction starts whenever the geometry changes. Stop
143  // line information is not given or ambiguous (sign positions at most)
144  // In SUMO, stop lines are where the junction starts. This is computed
145  // heuristically from intersecting the junctions roads geometries.
146  const double advanceStopLine = oc.exists("opendrive-files") && oc.isSet("opendrive-files") ? oc.getFloat("opendrive.advance-stopline") : 0;
147 
148 
149 #ifdef DEBUG_NODE_SHAPE
150  if (DEBUGCOND) {
151  std::cout << "\ncomputeNodeShapeDefault node " << myNode.getID() << " simple=" << simpleContinuation << " useDefaultRadius=" << useDefaultRadius << " radius=" << myRadius << "\n";
152  }
153 #endif
154 
155  // initialise
156  EdgeVector::const_iterator i;
157  // edges located in the value-vector have the same direction as the key edge
158  std::map<NBEdge*, std::set<NBEdge*> > same;
159  // the counter-clockwise boundary of the edge regarding possible same-direction edges
160  GeomsMap geomsCCW;
161  // the clockwise boundary of the edge regarding possible same-direction edges
162  GeomsMap geomsCW;
163  EdgeVector usedEdges = myNode.getEdges();
164  computeEdgeBoundaries(usedEdges, geomsCCW, geomsCW);
165 
166  // check which edges are parallel
167  joinSameDirectionEdges(usedEdges, same, useEndpoints);
168  // compute unique direction list
169  EdgeVector newAll = computeUniqueDirectionList(usedEdges, same, geomsCCW, geomsCW);
170  // if we have only two "directions", let's not compute the geometry using this method
171  if (newAll.size() < 2) {
172  return PositionVector();
173  }
174 
175  // All geoms are outgoing from myNode.
176  // for every direction in newAll we compute the offset at which the
177  // intersection ends and the edge starts. This value is saved in 'distances'
178  // If the geometries need to be extended to get an intersection, this is
179  // recorded in 'myExtended'
180  std::map<NBEdge*, double> distances;
181  std::map<NBEdge*, double> distances2;
182  std::map<NBEdge*, bool> myExtended;
183 
184  for (i = newAll.begin(); i != newAll.end(); ++i) {
185  EdgeVector::const_iterator cwi = i;
186  EdgeVector::const_iterator ccwi = i;
187  double ccad;
188  double cad;
189  initNeighbors(newAll, i, geomsCW, geomsCCW, cwi, ccwi, cad, ccad);
190  assert(geomsCCW.find(*i) != geomsCCW.end());
191  assert(geomsCW.find(*ccwi) != geomsCW.end());
192  assert(geomsCW.find(*cwi) != geomsCW.end());
193 
194  // there are only 2 directions and they are almost parallel
195  if (*cwi == *ccwi &&
196  (
197  // no change in lane numbers, even low angles still give a good intersection
198  (simpleContinuation && fabs(ccad - cad) < (double) 0.1)
199  // lane numbers change, a direct intersection could be far away from the node position
200  // so we use a larger threshold
201  || (!simpleContinuation && fabs(ccad - cad) < DEG2RAD(22.5)))
202  ) {
203  // compute the mean position between both edges ends ...
204  Position p;
205  if (myExtended.find(*ccwi) != myExtended.end()) {
206  p = geomsCCW[*ccwi][0];
207  p.add(geomsCW[*ccwi][0]);
208  p.mul(0.5);
209 #ifdef DEBUG_NODE_SHAPE
210  if (DEBUGCOND) {
211  std::cout << " extended: p=" << p << " angle=" << (ccad - cad) << "\n";
212  }
213 #endif
214  } else {
215  p = geomsCCW[*ccwi][0];
216  p.add(geomsCW[*ccwi][0]);
217  p.add(geomsCCW[*i][0]);
218  p.add(geomsCW[*i][0]);
219  p.mul(0.25);
220 #ifdef DEBUG_NODE_SHAPE
221  if (DEBUGCOND) {
222  std::cout << " unextended: p=" << p << " angle=" << (ccad - cad) << "\n";
223  }
224 #endif
225  }
226  // ... compute the distance to this point ...
227  double dist = MAX2(
228  geomsCCW[*i].nearest_offset_to_point2D(p),
229  geomsCW[*i].nearest_offset_to_point2D(p));
230  if (dist < 0) {
231  if (isRailway((*i)->getPermissions())) {
232  // better not mess up bidi geometries
233  return PositionVector();
234  }
235  // ok, we have the problem that even the extrapolated geometry
236  // does not reach the point
237  // in this case, the geometry has to be extenden... too bad ...
238  // ... let's append the mean position to the geometry
239  PositionVector g = (*i)->getGeometry();
240  if (myNode.hasIncoming(*i)) {
242  } else {
244  }
245  (*i)->setGeometry(g);
246  // and rebuild previous information
247  geomsCCW[*i] = (*i)->getCCWBoundaryLine(myNode);
248  geomsCCW[*i].extrapolate(EXT);
249  geomsCW[*i] = (*i)->getCWBoundaryLine(myNode);
250  geomsCW[*i].extrapolate(EXT);
251  // the distance is now = zero (the point we have appended)
252  distances[*i] = EXT;
253  myExtended[*i] = true;
254 #ifdef DEBUG_NODE_SHAPE
255  if (DEBUGCOND) {
256  std::cout << " extending (dist=" << dist << ")\n";
257  }
258 #endif
259  } else {
260  if (!simpleContinuation) {
261  dist += myRadius;
262  } else {
263  // if the angles change, junction should have some size to avoid degenerate shape
264  double radius2 = fabs(ccad - cad) * (*i)->getNumLanes();
265  if (radius2 > NUMERICAL_EPS || openDriveOutput) {
266  radius2 = MAX2(0.15, radius2);
267  }
268  if (myNode.getCrossings().size() > 0) {
269  double width = myNode.getCrossings()[0]->customWidth;
270  if (width == NBEdge::UNSPECIFIED_WIDTH) {
271  width = OptionsCont::getOptions().getFloat("default.crossing-width");
272  }
273  radius2 = MAX2(radius2, width / 2);
274  }
275  if (!useDefaultRadius) {
276  radius2 = MAX2(radius2, myRadius);
277  }
278  dist += radius2;
279 #ifdef DEBUG_NODE_SHAPE
280  if (DEBUGCOND) {
281  std::cout << " using radius=" << radius2 << " ccad=" << ccad << " cad=" << cad << "\n";
282  }
283 #endif
284  }
285  distances[*i] = dist;
286  }
287 
288  } else {
289  // the angles are different enough to compute the intersection of
290  // the outer boundaries directly (or there are more than 2 directions). The "nearer" neighbor causes the furthest distance
291  const bool ccwCloser = ccad < cad;
292  const bool cwLargeTurn = needsLargeTurn(*i, *cwi, same);
293  const bool ccwLargeTurn = needsLargeTurn(*i, *ccwi, same);
294  const bool neighLargeTurn = ccwCloser ? ccwLargeTurn : cwLargeTurn;
295  const bool neigh2LargeTurn = ccwCloser ? cwLargeTurn : ccwLargeTurn;
296  // the border facing the closer neighbor
297  const PositionVector& currGeom = ccwCloser ? geomsCCW[*i] : geomsCW[*i];
298  // the border facing the far neighbor
299  const PositionVector& currGeom2 = ccwCloser ? geomsCW[*i] : geomsCCW[*i];
300  // the border of the closer neighbor
301  const PositionVector& neighGeom = ccwCloser ? geomsCW[*ccwi] : geomsCCW[*cwi];
302  // the border of the far neighbor
303  const PositionVector& neighGeom2 = ccwCloser ? geomsCCW[*cwi] : geomsCW[*ccwi];
304  // whether the current edge/direction spans a divided road
305  const bool keepBothDistances = isDivided(*i, same[*i], geomsCCW[*i], geomsCW[*i]);
306 #ifdef DEBUG_NODE_SHAPE
307  if (DEBUGCOND) {
308  std::cout << " i=" << (*i)->getID() << " neigh=" << (*ccwi)->getID() << " neigh2=" << (*cwi)->getID() << "\n";
309  std::cout << " ccwCloser=" << ccwCloser << " divided=" << keepBothDistances
310  << "\n currGeom=" << currGeom << " neighGeom=" << neighGeom
311  << "\n currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2
312  << "\n";
313  }
314 #endif
315  if (!simpleContinuation) {
316  if (useEndpoints && !(*i)->hasDefaultGeometryEndpointAtNode(&myNode)) {
317  distances[*i] = EXT;
318  } else if (currGeom.intersects(neighGeom)) {
319  distances[*i] = (neighLargeTurn ? myRadius : smallRadius) + closestIntersection(currGeom, neighGeom, EXT);
320 #ifdef DEBUG_NODE_SHAPE
321  if (DEBUGCOND) {
322  std::cout << " neigh intersects dist=" << distances[*i] << " currGeom=" << currGeom << " neighGeom=" << neighGeom << "\n";
323  }
324 #endif
325  if (*cwi != *ccwi && currGeom2.intersects(neighGeom2)) {
326  // also use the second intersection point
327  // but prevent very large node shapes
328  const double farAngleDist = ccwCloser ? cad : ccad;
329  double a1 = distances[*i];
330  double a2 = (neigh2LargeTurn ? myRadius : smallRadius) + closestIntersection(currGeom2, neighGeom2, EXT);
331 #ifdef DEBUG_NODE_SHAPE
332  if (DEBUGCOND) {
333  std::cout << " neigh2 also intersects a1=" << a1 << " a2=" << a2 << " ccad=" << RAD2DEG(ccad) << " cad=" << RAD2DEG(cad) << " dist[cwi]=" << distances[*cwi] << " dist[ccwi]=" << distances[*ccwi] << " farAngleDist=" << RAD2DEG(farAngleDist) << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
334  }
335 #endif
336  //if (RAD2DEG(farAngleDist) < 175) {
337  // distances[*i] = MAX2(a1, MIN2(a2, a1 + 180 - RAD2DEG(farAngleDist)));
338  //}
339  if (a2 <= EXT) {
340  if (keepBothDistances) {
341  if (ccwCloser) {
342  distances2[*i] = a2;
343  } else {
344  distances[*i] = a2;
345  distances2[*i] = a1;
346  }
347  } else {
348  distances[*i] = MAX2(a1, a2);
349  }
350  } else if (ccad > DEG2RAD(90. + 45.) && cad > DEG2RAD(90. + 45.)
351  && (fabs(ccad - cad) > DEG2RAD(10)
352  || MAX2(ccad, cad) > DEG2RAD(160)
353  || (a2 - a1) > 7
354  // keep roundabouts nodes small
355  || myNode.isRoundabout())) {
356 #ifdef DEBUG_NODE_SHAPE
357  if (DEBUGCOND) {
358  std::cout << " ignore a2\n";
359  }
360 #endif
361  // do nothing.
362  } else if (farAngleDist < DEG2RAD(135) || (fabs(RAD2DEG(farAngleDist) - 180) > 1 && fabs(a2 - a1) < 10)) {
363  if (keepBothDistances) {
364  if (ccwCloser) {
365  distances2[*i] = a2;
366  } else {
367  distances[*i] = a2;
368  distances2[*i] = a1;
369  }
370  } else {
371  distances[*i] = MAX2(a1, a2);
372  }
373  }
374 #ifdef DEBUG_NODE_SHAPE
375  if (DEBUGCOND) {
376  std::cout << " a1=" << a1 << " a2=" << a2 << " keepBoth=" << keepBothDistances << " dist=" << distances[*i] << "\n";
377  }
378 #endif
379  }
380  } else {
381  if (*cwi != *ccwi && currGeom2.intersects(neighGeom2)) {
382  distances[*i] = (neigh2LargeTurn ? myRadius : smallRadius) + currGeom2.intersectsAtLengths2D(neighGeom2)[0];
383 #ifdef DEBUG_NODE_SHAPE
384  if (DEBUGCOND) {
385  std::cout << " neigh2 intersects dist=" << distances[*i] << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
386  }
387 #endif
388  } else {
389  distances[*i] = EXT + myRadius;
390 #ifdef DEBUG_NODE_SHAPE
391  if (DEBUGCOND) {
392  std::cout << " no intersects dist=" << distances[*i] << " currGeom=" << currGeom << " neighGeom=" << neighGeom << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
393  }
394 #endif
395  }
396  }
397  } else {
398  if (currGeom.intersects(neighGeom)) {
399  distances[*i] = currGeom.intersectsAtLengths2D(neighGeom)[0];
400  } else {
401  distances[*i] = (double) EXT;
402  }
403  }
404  }
405  if (useDefaultRadius && sCurveStretch > 0) {
406  double sCurveWidth = myNode.getDisplacementError();
407  if (sCurveWidth > 0) {
408  const double sCurveRadius = myRadius + sCurveWidth / SUMO_const_laneWidth * sCurveStretch * pow((*i)->getSpeed(), 2 + sCurveStretch) / 1000;
409  const double stretch = EXT + sCurveRadius - distances[*i];
410  if (stretch > 0) {
411  distances[*i] += stretch;
412  // fixate extended geometry for repeated computation
413  const double shorten = distances[*i] - EXT;
414  (*i)->shortenGeometryAtNode(&myNode, shorten);
415  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
416  (*k)->shortenGeometryAtNode(&myNode, shorten);
417  }
418 #ifdef DEBUG_NODE_SHAPE
419  if (DEBUGCOND) {
420  std::cout << " stretching junction: sCurveWidth=" << sCurveWidth << " sCurveRadius=" << sCurveRadius << " stretch=" << stretch << " dist=" << distances[*i] << "\n";
421  }
422 #endif
423  }
424  }
425  }
426  }
427 
428  for (NBEdge* const edge : newAll) {
429  if (distances.find(edge) == distances.end()) {
430  assert(false);
431  distances[edge] = EXT;
432  }
433  }
434  // because of lane spread right the crossing point may be identical to the junction center and thus the distance is exactly EXT
435  const double off = EXT - NUMERICAL_EPS;
436  // prevent inverted node shapes
437  // (may happen with near-parallel edges)
438  const double minDistSum = 2 * (EXT + myRadius);
439  for (NBEdge* const edge : newAll) {
440  if (distances[edge] < off && edge->hasDefaultGeometryEndpointAtNode(&myNode)) {
441  for (EdgeVector::const_iterator j = newAll.begin(); j != newAll.end(); ++j) {
442  if (distances[*j] > off && (*j)->hasDefaultGeometryEndpointAtNode(&myNode) && distances[edge] + distances[*j] < minDistSum) {
443  const double angleDiff = fabs(NBHelpers::relAngle(edge->getAngleAtNode(&myNode), (*j)->getAngleAtNode(&myNode)));
444  if (angleDiff > 160 || angleDiff < 20) {
445 #ifdef DEBUG_NODE_SHAPE
446  if (DEBUGCOND) {
447  std::cout << " increasing dist for i=" << edge->getID() << " because of j=" << (*j)->getID() << " jDist=" << distances[*j]
448  << " oldI=" << distances[edge] << " newI=" << minDistSum - distances[*j]
449  << " angleDiff=" << angleDiff
450  << " geomI=" << edge->getGeometry() << " geomJ=" << (*j)->getGeometry() << "\n";
451  }
452 #endif
453  distances[edge] = minDistSum - distances[*j];
454  }
455  }
456  }
457  }
458  }
459 
460 
461  // build
462  PositionVector ret;
463  for (i = newAll.begin(); i != newAll.end(); ++i) {
464  const PositionVector& ccwBound = geomsCCW[*i];
465  const PositionVector& cwBound = geomsCW[*i];
466  //double offset = MIN3(distances[*i], cwBound.length2D() - POSITION_EPS, ccwBound.length2D() - POSITION_EPS);
467  double offset = distances[*i];
468  double offset2 = distances2.count(*i) != 0 ? distances2[*i] : offset;
469  if (offset != offset2) {
470  // keep rectangular cuts if the difference is small or the roads aren't
471  // really divided by much (unless the angle is very different)
472  const double dWidth = divisionWidth(*i, same[*i],
473  ccwBound.positionAtOffset2D(offset),
474  cwBound.positionAtOffset2D(offset2));
475  const double angle = RAD2DEG(GeomHelper::angleDiff(ccwBound.angleAt2D(0), cwBound.angleAt2D(0)));
476  const double oDelta = fabs(offset - offset2);
477  //std::cout << " i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dWidth=" << dWidth << " angle=" << angle << " same=" << joinNamedToStringSorting(same[*i], ",") << "\n";
478  if ((((oDelta < 5 || dWidth < 10) && fabs(angle) < 30)) || (fabs(angle) < 5 && myNode.getType() != SumoXMLNodeType::RAIL_CROSSING)) {
479 #ifdef DEBUG_NODE_SHAPE
480  std::cout << " i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dWidth=" << dWidth << " angle=" << angle << " same=" << joinNamedToStringSorting(same[*i], ",") << "\n";
481 #endif
482  offset = MAX2(offset, offset2);
483  offset2 = offset;
484  }
485  }
486  if (!(*i)->hasDefaultGeometryEndpointAtNode(&myNode)) {
487  // for non geometry-endpoints, only shorten but never extend the geometry
488  if (advanceStopLine > 0 && offset < EXT) {
489 #ifdef DEBUG_NODE_SHAPE
490  std::cout << " i=" << (*i)->getID() << " offset=" << offset << " advanceStopLine=" << advanceStopLine << "\n";
491 #endif
492  // fixate extended geometry for repeated computation
493  (*i)->extendGeometryAtNode(&myNode, advanceStopLine);
494  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
495  (*k)->extendGeometryAtNode(&myNode, advanceStopLine);
496  }
497  }
498  offset = MAX2(EXT - advanceStopLine, offset);
499  offset2 = MAX2(EXT - advanceStopLine, offset2);
500  }
501  if (offset == -1) {
502  WRITE_WARNINGF(TL("Fixing offset for edge '%' at node '%."), (*i)->getID(), myNode.getID());
503  offset = -.1;
504  offset2 = -.1;
505  }
506  Position p = ccwBound.positionAtOffset2D(offset);
507  p.setz(myNode.getPosition().z());
508  if (i != newAll.begin()) {
509  ret.append(getSmoothCorner(geomsCW[*(i - 1)], ccwBound, ret[-1], p, cornerDetail));
510  }
511  Position p2 = cwBound.positionAtOffset2D(offset2);
512  p2.setz(myNode.getPosition().z());
513  //ret.append(getEdgeCuts(*i, geomsCCW, geomsCW, offset, offset2, same));
514  ret.push_back_noDoublePos(p);
515  ret.push_back_noDoublePos(p2);
516 #ifdef DEBUG_NODE_SHAPE
517  if (DEBUGCOND) {
518  std::cout << " build stopLine for i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dist=" << distances[*i] << " cwLength=" << cwBound.length2D() << " ccwLength=" << ccwBound.length2D() << " p=" << p << " p2=" << p2 << " ccwBound=" << ccwBound << " cwBound=" << cwBound << "\n";
519  }
520 #endif
521  (*i)->setNodeBorder(&myNode, p, p2, rectangularCut);
522  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
523  (*k)->setNodeBorder(&myNode, p, p2, rectangularCut);
524  }
525  }
526  // final curve segment
527  ret.append(getSmoothCorner(geomsCW[*(newAll.end() - 1)], geomsCCW[*newAll.begin()], ret[-1], ret[0], cornerDetail));
528 #ifdef DEBUG_NODE_SHAPE
529  if (DEBUGCOND) {
530  std::cout << " final shape=" << ret << "\n";
531  }
532 #endif
533  return ret;
534 }
535 
536 
537 double
538 NBNodeShapeComputer::closestIntersection(const PositionVector& geom1, const PositionVector& geom2, double offset) {
539  std::vector<double> intersections = geom1.intersectsAtLengths2D(geom2);
540  double result = intersections[0];
541  for (std::vector<double>::iterator it = intersections.begin() + 1; it != intersections.end(); ++it) {
542  if (fabs(*it - offset) < fabs(result - offset)) {
543  result = *it;
544  }
545  }
546  return result;
547 }
548 
549 bool
551  std::map<NBEdge*, std::set<NBEdge*> >& same) const {
552  const SVCPermissions p1 = e1->getPermissions();
553  const SVCPermissions p2 = e2->getPermissions();
554  if ((p1 & p2 & SVC_LARGE_TURN) != 0) {
555  // note: would could also check whether there is actually a connection
556  // between those edges
557  return true;
558  }
559  // maybe edges in the same direction need a large turn
560  for (NBEdge* e2s : same[e2]) {
561  if ((p1 & e2s->getPermissions() & SVC_LARGE_TURN) != 0
562  && (e1->getToNode() == e2s->getFromNode() || e2s->getToNode() == e1->getFromNode())) {
563  return true;
564  }
565  for (NBEdge* e1s : same[e1]) {
566  if ((e2s->getPermissions() & e1s->getPermissions() & SVC_LARGE_TURN) != 0
567  && (e2s->getToNode() == e1s->getFromNode() || e1s->getToNode() == e2s->getFromNode())) {
568  return true;
569  }
570  }
571  }
572  for (NBEdge* e1s : same[e1]) {
573  if ((p2 & e1s->getPermissions() & SVC_LARGE_TURN) != 0
574  && (e2->getToNode() == e1s->getFromNode() || e1s->getToNode() == e2->getFromNode())) {
575  return true;
576  }
577  }
578  //std::cout << " e1=" << e1->getID() << " e2=" << e2->getID() << " sameE1=" << toString(same[e1]) << " sameE2=" << toString(same[e2]) << "\n";
579  return false;
580 }
581 
584  const Position& begPoint, const Position& endPoint, int cornerDetail) {
585  PositionVector ret;
586  if (cornerDetail > 0) {
587  PositionVector begShape2 = begShape.reverse().getSubpart2D(EXT2, begShape.length());
588  const double begSplit = begShape2.nearest_offset_to_point2D(begPoint, false);
589 #ifdef DEBUG_SMOOTH_CORNERS
590  if (DEBUGCOND) {
591  std::cout << " begLength=" << begShape2.length2D() << " begSplit=" << begSplit << "\n";
592  }
593 #endif
594  if (begSplit > POSITION_EPS && begSplit < begShape2.length2D() - POSITION_EPS) {
595  begShape2 = begShape2.splitAt(begSplit, true).first;
596  } else {
597  return ret;
598  }
599  PositionVector endShape2 = endShape.getSubpart(0, endShape.length() - EXT2);
600  const double endSplit = endShape2.nearest_offset_to_point2D(endPoint, false);
601 #ifdef DEBUG_SMOOTH_CORNERS
602  if (DEBUGCOND) {
603  std::cout << " endLength=" << endShape2.length2D() << " endSplit=" << endSplit << "\n";
604  }
605 #endif
606  if (endSplit > POSITION_EPS && endSplit < endShape2.length2D() - POSITION_EPS) {
607  endShape2 = endShape2.splitAt(endSplit, true).second;
608  } else {
609  return ret;
610  }
611  // flatten z to junction z level
612  begShape2 = begShape2.interpolateZ(myNode.getPosition().z(), myNode.getPosition().z());
613  endShape2 = endShape2.interpolateZ(myNode.getPosition().z(), myNode.getPosition().z());
614 #ifdef DEBUG_SMOOTH_CORNERS
615  if (DEBUGCOND) {
616  std::cout << "getSmoothCorner begPoint=" << begPoint << " endPoint=" << endPoint
617  << " begShape=" << begShape << " endShape=" << endShape
618  << " begShape2=" << begShape2 << " endShape2=" << endShape2
619  << "\n";
620  }
621 #endif
622  if (begShape2.size() < 2 || endShape2.size() < 2) {
623  return ret;
624  }
625  const double angle = GeomHelper::angleDiff(begShape2.angleAt2D(-2), endShape2.angleAt2D(0));
626  NBNode* recordError = nullptr;
627 #ifdef DEBUG_SMOOTH_CORNERS
628  if (DEBUGCOND) {
629  std::cout << " angle=" << RAD2DEG(angle) << "\n";
630  }
631  recordError = const_cast<NBNode*>(&myNode);
632 #endif
633  // fill highly acute corners
634  //if (fabs(angle) > DEG2RAD(135)) {
635  // return ret;
636  //}
637  PositionVector curve = myNode.computeSmoothShape(begShape2, endShape2, cornerDetail + 2, false, 25, 25, recordError, NBNode::AVOID_WIDE_LEFT_TURN);
638  //PositionVector curve = myNode.computeSmoothShape(begShape2, endShape2, cornerDetail + 2, false, 25, 25, recordError, 0);
639  const double curvature = curve.length2D() / MAX2(NUMERICAL_EPS, begPoint.distanceTo2D(endPoint));
640 #ifdef DEBUG_SMOOTH_CORNERS
641  if (DEBUGCOND) {
642  std::cout << " curve=" << curve << " curveLength=" << curve.length2D() << " dist=" << begPoint.distanceTo2D(endPoint) << " curvature=" << curvature << "\n";
643  }
644 #endif
645  if (curvature > 2 && angle > DEG2RAD(85)) {
646  // simplify dubious inside corner shape
647  return ret;
648  }
649  if (curve.size() > 2) {
650  curve.erase(curve.begin());
651  curve.pop_back();
652  ret = curve;
653  }
654  }
655  return ret;
656 }
657 
658 void
660  GeomsMap& geomsCCW,
661  GeomsMap& geomsCW) {
662  // compute boundary lines and extend it by EXT m
663  for (NBEdge* const edge : edges) {
664  // store current edge's boundary as current ccw/cw boundary
665  try {
666  geomsCCW[edge] = edge->getCCWBoundaryLine(myNode);
667  } catch (InvalidArgument& e) {
668  WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': " + std::string(e.what()));
669  geomsCCW[edge] = edge->getGeometry();
670  }
671  try {
672  geomsCW[edge] = edge->getCWBoundaryLine(myNode);
673  } catch (InvalidArgument& e) {
674  WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': " + std::string(e.what()));
675  geomsCW[edge] = edge->getGeometry();
676  }
677  // ensure the boundary is valid
678  if (geomsCCW[edge].length2D() < NUMERICAL_EPS) {
679  geomsCCW[edge] = edge->getGeometry();
680  }
681  if (geomsCW[edge].length2D() < NUMERICAL_EPS) {
682  geomsCW[edge] = edge->getGeometry();
683  }
684  // cut off all parts beyond EXT to avoid issues with curved-back roads
685  geomsCCW[edge] = geomsCCW[edge].getSubpart2D(0, MAX2(EXT, edge->getTotalWidth()));
686  geomsCW[edge] = geomsCW[edge].getSubpart2D(0, MAX2(EXT, edge->getTotalWidth()));
687  // extend the boundary by extrapolating it by EXT m towards the junction
688  geomsCCW[edge].extrapolate2D(EXT, true);
689  geomsCW[edge].extrapolate2D(EXT, true);
690  // ensure minimum length by extending it away from the junction
691  geomsCCW[edge].extrapolate(EXT2, false, true);
692  geomsCW[edge].extrapolate(EXT2, false, true);
693  }
694 }
695 
696 void
697 NBNodeShapeComputer::joinSameDirectionEdges(const EdgeVector& edges, std::map<NBEdge*, std::set<NBEdge*> >& same, bool useEndpoints) {
698  // compute same (edges where an intersection doesn't work well
699  // (always check an edge and its cw neighbor)
700  const double angleChangeLookahead = 35; // distance to look ahead for a misleading angle
701  const bool isXodr = OptionsCont::getOptions().exists("opendrive-files") && OptionsCont::getOptions().isSet("opendrive-files");
702  EdgeSet foundOpposite;
703  for (EdgeVector::const_iterator i = edges.begin(); i != edges.end(); i++) {
704  EdgeVector::const_iterator j;
705  if (i == edges.end() - 1) {
706  j = edges.begin();
707  } else {
708  j = i + 1;
709  }
710  if (useEndpoints
711  && !(*i)->hasDefaultGeometryEndpointAtNode(&myNode)
712  && !(*j)->hasDefaultGeometryEndpointAtNode(&myNode)) {
713  continue;
714  }
715  const bool incoming = (*i)->getToNode() == &myNode;
716  const bool incoming2 = (*j)->getToNode() == &myNode;
717  const bool differentDirs = (incoming != incoming2);
718  const bool sameGeom = (*i)->getGeometry() == (differentDirs ? (*j)->getGeometry().reverse() : (*j)->getGeometry());
719  const PositionVector g1 = incoming ? (*i)->getCCWBoundaryLine(myNode) : (*i)->getCWBoundaryLine(myNode);
720  const PositionVector g2 = incoming ? (*j)->getCCWBoundaryLine(myNode) : (*j)->getCWBoundaryLine(myNode);
721  const double angle1further = (g1.size() > 2 && g1[0].distanceTo2D(g1[1]) < angleChangeLookahead ?
722  g1.angleAt2D(1) : g1.angleAt2D(0));
723  const double angle2further = (g2.size() > 2 && g2[0].distanceTo2D(g2[1]) < angleChangeLookahead ?
724  g2.angleAt2D(1) : g2.angleAt2D(0));
725  const double angleDiff = GeomHelper::angleDiff(g1.angleAt2D(0), g2.angleAt2D(0));
726  const double angleDiffFurther = GeomHelper::angleDiff(angle1further, angle2further);
727  const bool ambiguousGeometry = ((angleDiff > 0 && angleDiffFurther < 0) || (angleDiff < 0 && angleDiffFurther > 0));
728  //if (ambiguousGeometry) {
729  // @todo: this warning would be helpful in many cases. However, if angle and angleFurther jump between 179 and -179 it is misleading
730  // WRITE_WARNINGF(TL("Ambiguous angles at junction '%' for edges '%' and '%'."), myNode.getID(), (*i)->getID(), (*j)->getID());
731  //}
732 #ifdef DEBUG_NODE_SHAPE
733  if (DEBUGCOND) {
734  std::cout << " checkSameDirection " << (*i)->getID() << " " << (*j)->getID()
735  << " diffDirs=" << differentDirs
736  << " isOpposite=" << (differentDirs && foundOpposite.count(*i) == 0)
737  << " angleDiff=" << angleDiff
738  << " ambiguousGeometry=" << ambiguousGeometry
739  << " badInsersection=" << badIntersection(*i, *j, EXT)
740  << "\n";
741 
742  }
743 #endif
744  if (sameGeom || fabs(angleDiff) < DEG2RAD(20)) {
745  const bool isOpposite = differentDirs && foundOpposite.count(*i) == 0;
746  if (isOpposite) {
747  foundOpposite.insert(*i);
748  foundOpposite.insert(*j);
749  }
750  if (isOpposite || ambiguousGeometry || (!isXodr && badIntersection(*i, *j, EXT))) {
751  // maintain equivalence relation for all members of the equivalence class
752  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
753  if (*j != *k) {
754  same[*k].insert(*j);
755  same[*j].insert(*k);
756  }
757  }
758  for (std::set<NBEdge*>::iterator k = same[*j].begin(); k != same[*j].end(); ++k) {
759  if (*i != *k) {
760  same[*k].insert(*i);
761  same[*i].insert(*k);
762  }
763  }
764  same[*i].insert(*j);
765  same[*j].insert(*i);
766 #ifdef DEBUG_NODE_SHAPE
767  if (DEBUGCOND) {
768  std::cout << " joinedSameDirectionEdges " << (*i)->getID() << " " << (*j)->getID() << " isOpposite=" << isOpposite << " ambiguousGeometry=" << ambiguousGeometry << "\n";
769  }
770 #endif
771  }
772  }
773  }
774 }
775 
776 
777 bool
778 NBNodeShapeComputer::badIntersection(const NBEdge* e1, const NBEdge* e2, double distance) {
779  // check whether the two edges are on top of each other. In that case they should be joined
780  // also, if they never touch along their common length
781  const double commonLength = MIN3(distance, e1->getGeometry().length(), e2->getGeometry().length());
782  PositionVector geom1 = e1->getGeometry();
783  PositionVector geom2 = e2->getGeometry();
784  // shift to make geom the centerline of the edge regardless of spreadtype
786  geom1.move2side(e1->getTotalWidth() / 2);
787  }
789  geom2.move2side(e2->getTotalWidth() / 2);
790  }
791  // always let geometry start at myNode
792  if (e1->getToNode() == &myNode) {
793  geom1 = geom1.reverse();
794  }
795  if (e2->getToNode() == &myNode) {
796  geom2 = geom2.reverse();
797  }
798  geom1 = geom1.getSubpart2D(0, commonLength);
799  geom2 = geom2.getSubpart2D(0, commonLength);
800  double endAngleDiff = 0;
801  if (geom1.size() >= 2 && geom2.size() >= 2) {
802  endAngleDiff = fabs(RAD2DEG(GeomHelper::angleDiff(
803  geom1.angleAt2D((int)geom1.size() - 2),
804  geom2.angleAt2D((int)geom2.size() - 2))));
805  }
806  const double minDistanceThreshold = (e1->getTotalWidth() + e2->getTotalWidth()) / 2 + POSITION_EPS;
807  std::vector<double> distances = geom1.distances(geom2, true);
808  std::vector<double> distances2 = geom1.distances(geom2);
809  const double minDist = VectorHelper<double>::minValue(distances2);
810  const double maxDist = VectorHelper<double>::maxValue(distances);
811  const bool curvingTowards = geom1[0].distanceTo2D(geom2[0]) > minDistanceThreshold && minDist < minDistanceThreshold;
812  const bool onTop = (maxDist - POSITION_EPS < minDistanceThreshold) && endAngleDiff < 30;
813  const bool bothDefault = e1->hasDefaultGeometryEndpointAtNode(&myNode) && e2->hasDefaultGeometryEndpointAtNode(&myNode);
814  const bool neverTouch = minDist > minDistanceThreshold * 2 && !bothDefault;
815  geom1.extrapolate2D(EXT);
816  geom2.extrapolate2D(EXT);
817  Position intersect = geom1.intersectionPosition2D(geom2);
818  const bool intersects = intersect != Position::INVALID && geom1.distance2D(intersect) < POSITION_EPS;
819 #ifdef DEBUG_NODE_SHAPE
820  if (DEBUGCOND) {
821  std::cout << " badIntersect: onTop=" << onTop << " curveTo=" << curvingTowards << " intersects=" << intersects
822  << " endAngleDiff=" << endAngleDiff
823  << " geom1=" << geom1 << " geom2=" << geom2
824  << " distances=" << toString(distances) << " minDist=" << minDist << " maxDist=" << maxDist << " thresh=" << minDistanceThreshold
825  << " neverTouch=" << neverTouch
826  << " intersectPos=" << intersect
827  << "\n";
828  }
829 #endif
830  return onTop || curvingTowards || !intersects || neverTouch;
831 }
832 
833 
836  const EdgeVector& all,
837  std::map<NBEdge*, std::set<NBEdge*> >& same,
838  GeomsMap& geomsCCW,
839  GeomsMap& geomsCW) {
840  // store relationships
841  EdgeVector newAll = all;
842  for (NBEdge* e1 : all) {
843  // determine which of the edges marks the outer boundary
844  auto e2NewAll = std::find(newAll.begin(), newAll.end(), e1);
845 #ifdef DEBUG_NODE_SHAPE
846  if (DEBUGCOND) std::cout << "computeUniqueDirectionList e1=" << e1->getID()
847  << " deleted=" << (e2NewAll == newAll.end())
848  << " same=" << joinNamedToStringSorting(same[e1], ',') << "\n";
849 #endif
850  if (e2NewAll == newAll.end()) {
851  continue;
852  }
853  auto e1It = std::find(all.begin(), all.end(), e1);
854  auto bestCCW = e1It;
855  auto bestCW = e1It;
856  bool changed = true;
857  while (changed) {
858  changed = false;
859  for (NBEdge* e2 : same[e1]) {
860 #ifdef DEBUG_NODE_SHAPE
861  if (DEBUGCOND) {
862  std::cout << " e2=" << e2->getID() << "\n";
863  }
864 #endif
865  auto e2It = std::find(all.begin(), all.end(), e2);
866  if (e2It + 1 == bestCCW || (e2It == (all.end() - 1) && bestCCW == all.begin())) {
867  bestCCW = e2It;
868  changed = true;
869 #ifdef DEBUG_NODE_SHAPE
870  if (DEBUGCOND) {
871  std::cout << " bestCCW=" << e2->getID() << "\n";
872  }
873 #endif
874  } else if (bestCW + 1 == e2It || (bestCW == (all.end() - 1) && e2It == all.begin())) {
875  bestCW = e2It;
876  changed = true;
877 #ifdef DEBUG_NODE_SHAPE
878  if (DEBUGCOND) {
879  std::cout << " bestCW=" << e2->getID() << "\n";
880  }
881 #endif
882  }
883  }
884  }
885  if (bestCW != e1It) {
886  geomsCW[e1] = geomsCW[*bestCW];
887  computeSameEnd(geomsCW[e1], geomsCCW[e1]);
888  }
889  if (bestCCW != e1It) {
890  geomsCCW[e1] = geomsCCW[*bestCCW];
891  computeSameEnd(geomsCW[e1], geomsCCW[e1]);
892  }
893  // clean up
894  for (NBEdge* e2 : same[e1]) {
895  auto e2NewAllIt = std::find(newAll.begin(), newAll.end(), e2);
896  if (e2NewAllIt != newAll.end()) {
897  newAll.erase(e2NewAllIt);
898  }
899  }
900  }
901 #ifdef DEBUG_NODE_SHAPE
902  if (DEBUGCOND) {
903  std::cout << " newAll:\n";
904  for (NBEdge* e : newAll) {
905  std::cout << " " << e->getID() << " geomCCW=" << geomsCCW[e] << " geomsCW=" << geomsCW[e] << "\n";
906  }
907  }
908 #endif
909  return newAll;
910 }
911 
912 
913 void
914 NBNodeShapeComputer::initNeighbors(const EdgeVector& edges, const EdgeVector::const_iterator& current,
915  GeomsMap& geomsCW,
916  GeomsMap& geomsCCW,
917  EdgeVector::const_iterator& cwi,
918  EdgeVector::const_iterator& ccwi,
919  double& cad,
920  double& ccad) {
921  const double twoPI = (double)(2 * M_PI);
922  cwi = current;
923  cwi++;
924  if (cwi == edges.end()) {
925  std::advance(cwi, -((int)edges.size())); // set to edges.begin();
926  }
927  ccwi = current;
928  if (ccwi == edges.begin()) {
929  std::advance(ccwi, edges.size() - 1); // set to edges.end() - 1;
930  } else {
931  ccwi--;
932  }
933 
934  const double angleCurCCW = geomsCCW[*current].angleAt2D(0);
935  const double angleCurCW = geomsCW[*current].angleAt2D(0);
936  const double angleCCW = geomsCW[*ccwi].angleAt2D(0);
937  const double angleCW = geomsCCW[*cwi].angleAt2D(0);
938  ccad = angleCCW - angleCurCCW;
939  while (ccad < 0.) {
940  ccad += twoPI;
941  }
942  cad = angleCurCW - angleCW;
943  while (cad < 0.) {
944  cad += twoPI;
945  }
946 }
947 
948 
949 
950 const PositionVector
952 #ifdef DEBUG_NODE_SHAPE
953  if (DEBUGCOND) {
954  std::cout << "computeNodeShapeSmall node=" << myNode.getID() << "\n";
955  }
956 #endif
957  PositionVector ret;
958  for (NBEdge* e : myNode.getEdges()) {
959  // compute crossing with normal
960  PositionVector edgebound1 = e->getCCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
961  PositionVector edgebound2 = e->getCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
962  Position delta = edgebound1[1] - edgebound1[0];
963  delta.set(-delta.y(), delta.x()); // rotate 90 degrees
964  PositionVector cross(myNode.getPosition(), myNode.getPosition() + delta);
965  cross.extrapolate2D(500);
966  edgebound1.extrapolate2D(500);
967  edgebound2.extrapolate2D(500);
968  if (cross.intersects(edgebound1)) {
969  Position np = cross.intersectionPosition2D(edgebound1);
970  np.set(np.x(), np.y(), myNode.getPosition().z());
971  ret.push_back_noDoublePos(np);
972  }
973  if (cross.intersects(edgebound2)) {
974  Position np = cross.intersectionPosition2D(edgebound2);
975  np.set(np.x(), np.y(), myNode.getPosition().z());
976  ret.push_back_noDoublePos(np);
977  }
978  e->resetNodeBorder(&myNode);
979  }
980  return ret;
981 }
982 
983 
984 double
986  // look for incoming/outgoing edge pairs that do not go straight and allow wide vehicles
987  // (connection information is not available yet)
988  // @TODO compute the radius for each pair of neighboring edge intersections in computeNodeShapeDefault rather than use the maximum
989  const double radius = oc.getFloat("default.junctions.radius");
990  const double smallRadius = oc.getFloat("junctions.small-radius");
991  double maxRightAngle = 0; // rad
992  double extraWidthRight = 0; // m
993  double maxLeftAngle = 0; // rad
994  double extraWidthLeft = 0; // m
995  int laneDelta = 0;
996  int totalWideLanesIn = 0;
997  for (NBEdge* in : myNode.getIncomingEdges()) {
998  int wideLanesIn = 0;
999  for (int i = 0; i < in->getNumLanes(); i++) {
1000  if ((in->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1001  wideLanesIn++;
1002  }
1003  }
1004  totalWideLanesIn += wideLanesIn;
1005  for (NBEdge* out : myNode.getOutgoingEdges()) {
1006  if ((in->getPermissions() & out->getPermissions() & SVC_LARGE_TURN) != 0) {
1007  if (myNode.getDirection(in, out) == LinkDirection::TURN) {
1008  continue;
1009  };
1010  const double angle = GeomHelper::angleDiff(
1011  in->getGeometry().angleAt2D(-2),
1012  out->getGeometry().angleAt2D(0));
1013  if (angle < 0) {
1014  if (maxRightAngle < -angle) {
1015  maxRightAngle = -angle;
1016  extraWidthRight = MAX2(getExtraWidth(in, SVC_LARGE_TURN), getExtraWidth(out, SVC_LARGE_TURN));
1017  }
1018  } else {
1019  if (maxLeftAngle < angle) {
1020  maxLeftAngle = angle;
1021  // all edges clockwise between in and out count as extra width
1022  extraWidthLeft = 0;
1023  EdgeVector::const_iterator pIn = std::find(myNode.getEdges().begin(), myNode.getEdges().end(), in);
1025  while (*pIn != out) {
1026  extraWidthLeft += (*pIn)->getTotalWidth();
1027 #ifdef DEBUG_RADIUS
1028  if (DEBUGCOND) {
1029  std::cout << " in=" << in->getID() << " out=" << out->getID() << " extra=" << (*pIn)->getID() << " extraWidthLeft=" << extraWidthLeft << "\n";
1030  }
1031 #endif
1033  }
1034  }
1035  }
1036  int wideLanesOut = 0;
1037  for (int i = 0; i < out->getNumLanes(); i++) {
1038  if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1039  wideLanesOut++;
1040  }
1041  }
1042 #ifdef DEBUG_RADIUS
1043  if (DEBUGCOND) {
1044  std::cout << " in=" << in->getID() << " out=" << out->getID() << " wideLanesIn=" << wideLanesIn << " wideLanesOut=" << wideLanesOut << "\n";
1045  }
1046 #endif
1047  laneDelta = MAX2(laneDelta, abs(wideLanesOut - wideLanesIn));
1048  }
1049  }
1050  }
1051  // special case: on/off-ramp
1052  if (myNode.getOutgoingEdges().size() == 1 || myNode.getIncomingEdges().size() == 1) {
1053  int totalWideLanesOut = 0;
1054  for (NBEdge* out : myNode.getOutgoingEdges()) {
1055  for (int i = 0; i < out->getNumLanes(); i++) {
1056  if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1057  totalWideLanesOut++;
1058  }
1059  }
1060  }
1061  if (totalWideLanesIn == totalWideLanesOut) {
1062  // use total laneDelta instead of individual edge lane delta
1063  laneDelta = 0;
1064  }
1065  }
1066  // changing the number of wide-vehicle lanes on a straight segment requires a larger junction to allow for smooth driving
1067  // otherwise we can reduce the radius according to the angle
1068  double result = radius;
1069  // left turns are assumed to cross additional edges and thus du not determine the required radius in most cases
1070  double maxTurnAngle = maxRightAngle;
1071  double extraWidth = extraWidthRight;
1072  if (maxRightAngle < DEG2RAD(5)) {
1073  maxTurnAngle = maxLeftAngle;
1074  extraWidth = extraWidthLeft;
1075  }
1076  const double minRadius = maxTurnAngle >= DEG2RAD(30) ? MIN2(smallRadius, radius) : smallRadius;
1077  if (laneDelta == 0 || maxTurnAngle >= DEG2RAD(30) || myNode.isConstantWidthTransition()) {
1078  // subtract radius gained from extra lanes
1079  // do not increase radius for turns that are sharper than a right angle
1080  result = radius * tan(0.5 * MIN2(0.5 * M_PI, maxTurnAngle)) - extraWidth;
1081  }
1082  result = MAX2(minRadius, result);
1083 #ifdef DEBUG_RADIUS
1084  if (DEBUGCOND) {
1085  std::cout << "getDefaultRadius n=" << myNode.getID()
1086  << " r=" << radius << " sr=" << smallRadius
1087  << " mr=" << minRadius
1088  << " laneDelta=" << laneDelta
1089  << " rightA=" << RAD2DEG(maxRightAngle)
1090  << " leftA=" << RAD2DEG(maxLeftAngle)
1091  << " maxA=" << RAD2DEG(maxTurnAngle)
1092  << " extraWidth=" << extraWidth
1093  << " result=" << result << "\n";
1094  }
1095 #endif
1096  return result;
1097 }
1098 
1099 
1100 bool
1101 NBNodeShapeComputer::isDivided(const NBEdge* e, std::set<NBEdge*> same, const PositionVector& ccw, const PositionVector& cw) const {
1102  if (same.size() < 2) {
1103  return false;
1104  }
1105  std::set<Position> endPoints;
1106  endPoints.insert(e->getEndpointAtNode(&myNode));
1107  for (NBEdge* s : same) {
1108  endPoints.insert(s->getEndpointAtNode(&myNode));
1109  }
1110  if (endPoints.size() > 1) {
1111  std::vector<double> distances = ccw.distances(cw, true);
1112  double width = e->getTotalWidth();
1113  for (const NBEdge* e2 : same) {
1114  width += e2->getTotalWidth();
1115  }
1116  const double maxDist = VectorHelper<double>::maxValue(distances);
1117  const double maxDivider = maxDist - width;
1118  return maxDivider >= 5;
1119  }
1120  return false;
1121 }
1122 
1123 
1124 double
1126  double result = 0;
1127  int lane = 0;
1128  while (lane < e->getNumLanes() && e->getPermissions(lane) == 0) {
1129  // ignore forbidden lanes out the outside
1130  lane++;
1131  }
1132  while (lane < e->getNumLanes() && (e->getPermissions(lane) & exclude) == 0) {
1133  result += e->getLaneWidth(lane);
1134  lane++;
1135  }
1136  return result;
1137 }
1138 
1139 
1140 double
1141 NBNodeShapeComputer::divisionWidth(const NBEdge* e, std::set<NBEdge*> same, const Position& p, const Position& p2) {
1142  double result = p.distanceTo2D(p2);
1143  result -= e->getTotalWidth();
1144  for (NBEdge* e2 : same) {
1145  result -= e2->getTotalWidth();
1146  }
1147  return MAX2(0.0, result);
1148 }
1149 
1150 /****************************************************************************/
#define DEG2RAD(x)
Definition: GeomHelper.h:35
#define RAD2DEG(x)
Definition: GeomHelper.h:36
#define WRITE_WARNINGF(...)
Definition: MsgHandler.h:296
#define WRITE_WARNING(msg)
Definition: MsgHandler.h:295
#define TL(string)
Definition: MsgHandler.h:315
std::set< NBEdge * > EdgeSet
container for unique edges
Definition: NBCont.h:50
std::vector< NBEdge * > EdgeVector
container for (sorted) edges
Definition: NBCont.h:35
#define DEBUGCOND
#define EXT2
const SVCPermissions SVCAll
all VClasses are allowed
bool isRailway(SVCPermissions permissions)
Returns whether an edge with the given permissions is a railway edge.
long long int SVCPermissions
bitset where each bit declares whether a certain SVC may use this edge/lane
@ SVC_RAIL_CLASSES
classes which drive on tracks
@ SVC_BICYCLE
vehicle is a bicycle
@ SVC_DELIVERY
vehicle is a small delivery vehicle
@ SVC_PEDESTRIAN
pedestrian
@ TURN
The link is a 180 degree turn.
const double SUMO_const_laneWidth
Definition: StdDefs.h:48
T MIN3(T a, T b, T c)
Definition: StdDefs.h:89
T MIN2(T a, T b)
Definition: StdDefs.h:76
T MAX2(T a, T b)
Definition: StdDefs.h:82
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
Definition: ToString.h:46
std::string joinNamedToStringSorting(const std::set< T * > &ns, const T_BETWEEN &between)
Definition: ToString.h:307
static double angleDiff(const double angle1, const double angle2)
Returns the difference of the second angle to the first angle in radiants.
Definition: GeomHelper.cpp:178
static void nextCW(const EdgeVector &edges, EdgeVector::const_iterator &from)
The representation of a single edge during network building.
Definition: NBEdge.h:92
SVCPermissions getPermissions(int lane=-1) const
get the union of allowed classes over all lanes or for a specific lane
Definition: NBEdge.cpp:4368
double getLaneWidth() const
Returns the default width of lanes of this edge.
Definition: NBEdge.h:638
LaneSpreadFunction getLaneSpreadFunction() const
Returns how this edge's lanes' lateral offset is computed.
Definition: NBEdge.cpp:992
Position getEndpointAtNode(const NBNode *node) const
Definition: NBEdge.cpp:614
NBNode * getToNode() const
Returns the destination node of the edge.
Definition: NBEdge.h:542
const PositionVector & getGeometry() const
Returns the geometry of the edge.
Definition: NBEdge.h:779
double getTotalWidth() const
Returns the combined width of all lanes of this edge.
Definition: NBEdge.cpp:4206
bool hasDefaultGeometryEndpointAtNode(const NBNode *node) const
Returns whether the geometry is terminated by the node positions This default may be violated by init...
Definition: NBEdge.cpp:603
static const double UNSPECIFIED_WIDTH
unspecified lane width
Definition: NBEdge.h:342
NBNode * getFromNode() const
Returns the origin node of the edge.
Definition: NBEdge.h:535
static double relAngle(double angle1, double angle2)
computes the relative angle between the two angles
Definition: NBHelpers.cpp:45
Represents a single node (junction) during network building.
Definition: NBNode.h:66
LinkDirection getDirection(const NBEdge *const incoming, const NBEdge *const outgoing, bool leftHand=false) const
Returns the representation of the described stream's direction.
Definition: NBNode.cpp:2358
bool hasIncoming(const NBEdge *const e) const
Returns whether the given edge ends at this node.
Definition: NBNode.cpp:1856
double getDisplacementError() const
compute the displacement error during s-curve computation
Definition: NBNode.h:644
bool isSimpleContinuation(bool checkLaneNumbers=true, bool checkWidth=false) const
check if node is a simple continuation
Definition: NBNode.cpp:510
static const double UNSPECIFIED_RADIUS
unspecified lane width
Definition: NBNode.h:220
SumoXMLNodeType getType() const
Returns the type of this node.
Definition: NBNode.h:285
const EdgeVector & getOutgoingEdges() const
Returns this node's outgoing edges (The edges which start at this node)
Definition: NBNode.h:273
const EdgeVector & getEdges() const
Returns all edges which participate in this node (Edges that start or end at this node)
Definition: NBNode.h:278
PositionVector computeSmoothShape(const PositionVector &begShape, const PositionVector &endShape, int numPoints, bool isTurnaround, double extrapolateBeg, double extrapolateEnd, NBNode *recordError=0, int shapeFlag=0) const
Compute a smooth curve between the given geometries.
Definition: NBNode.cpp:545
const EdgeVector & getIncomingEdges() const
Returns this node's incoming edges (The edges which yield in this node)
Definition: NBNode.h:268
std::vector< Crossing * > getCrossings() const
return this junctions pedestrian crossings
Definition: NBNode.cpp:2927
bool isConstantWidthTransition() const
detects whether a given junction splits or merges lanes while keeping constant road width
Definition: NBNode.cpp:878
static const int AVOID_WIDE_LEFT_TURN
Definition: NBNode.h:224
const Position & getPosition() const
Definition: NBNode.h:260
double getRadius() const
Returns the turning radius of this node.
Definition: NBNode.h:290
bool isRoundabout() const
return whether this node is part of a roundabout
Definition: NBNode.cpp:3827
PositionVector getSmoothCorner(PositionVector begShape, PositionVector endShape, const Position &begPoint, const Position &endPoint, int cornerDetail)
Compute smoothed corner shape.
double closestIntersection(const PositionVector &geom1, const PositionVector &geom2, double offset)
return the intersection point closest to the given offset
const PositionVector computeNodeShapeSmall()
Computes the node geometry using normals.
double myRadius
the computed node radius
EdgeVector computeUniqueDirectionList(const EdgeVector &all, std::map< NBEdge *, std::set< NBEdge * > > &same, GeomsMap &geomsCCW, GeomsMap &geomsCW)
Joins edges.
bool isDivided(const NBEdge *e, std::set< NBEdge * > same, const PositionVector &ccw, const PositionVector &cw) const
double EXT
the maximum distance to search for a place where neighboring edges intersect and do not overlap
void computeEdgeBoundaries(const EdgeVector &edges, GeomsMap &geomsCCW, GeomsMap &geomsCW)
compute clockwise/counter-clockwise edge boundaries
std::map< NBEdge *, PositionVector > GeomsMap
NBNodeShapeComputer(const NBNode &node)
Constructor.
~NBNodeShapeComputer()
Destructor.
bool badIntersection(const NBEdge *e1, const NBEdge *e2, double distance)
const PositionVector computeNodeShapeDefault(bool simpleContinuation)
Computes the node geometry Edges with the same direction are grouped. Then the node geometry is built...
const PositionVector compute(bool forceSmall)
Computes the shape of the assigned junction.
const NBNode & myNode
The node to compute the geometry for.
void joinSameDirectionEdges(const EdgeVector &edges, std::map< NBEdge *, std::set< NBEdge * > > &same, bool useEndpoints)
Joins edges and computes ccw/cw boundaries.
void computeSameEnd(PositionVector &l1, PositionVector &l2)
static double divisionWidth(const NBEdge *e, std::set< NBEdge * > same, const Position &p, const Position &p2)
compute the width of the divider space for divided roads
double getDefaultRadius(const OptionsCont &oc)
determine the default radius appropriate for the current junction
static void initNeighbors(const EdgeVector &edges, const EdgeVector::const_iterator &current, GeomsMap &geomsCW, GeomsMap &geomsCCW, EdgeVector::const_iterator &cwi, EdgeVector::const_iterator &ccwi, double &cad, double &ccad)
Initialize neighbors and angles.
bool needsLargeTurn(NBEdge *e1, NBEdge *e2, std::map< NBEdge *, std::set< NBEdge * > > &same) const
whether the given edges (along with those in the same direction) requires a large turning radius
static const SVCPermissions SVC_LARGE_TURN
static double getExtraWidth(const NBEdge *e, SVCPermissions exclude)
compute with of rightmost lanes that exlude the given permissions
static bool isRailwayNode(const NBNode *n)
whether the given node only has rail edges
const std::string & getID() const
Returns the id.
Definition: Named.h:74
A storage for options typed value containers)
Definition: OptionsCont.h:89
bool isSet(const std::string &name, bool failOnNonExistant=true) const
Returns the information whether the named option is set.
double getFloat(const std::string &name) const
Returns the double-value of the named option (only for Option_Float)
int getInt(const std::string &name) const
Returns the int-value of the named option (only for Option_Integer)
bool exists(const std::string &name) const
Returns the information whether the named option is known.
bool getBool(const std::string &name) const
Returns the boolean-value of the named option (only for Option_Bool)
static OptionsCont & getOptions()
Retrieves the options.
Definition: OptionsCont.cpp:60
A point in 2D or 3D with translation and scaling methods.
Definition: Position.h:37
void set(double x, double y)
set positions x and y
Definition: Position.h:85
static const Position INVALID
used to indicate that a position is valid
Definition: Position.h:322
double distanceTo2D(const Position &p2) const
returns the euclidean distance in the x-y-plane
Definition: Position.h:276
double x() const
Returns the x-position.
Definition: Position.h:55
void add(const Position &pos)
Adds the given position to this one.
Definition: Position.h:132
void setz(double z)
set position z
Definition: Position.h:80
void mul(double val)
Multiplies position with the given value.
Definition: Position.h:105
double z() const
Returns the z-position.
Definition: Position.h:65
double y() const
Returns the y-position.
Definition: Position.h:60
A list of positions.
double length2D() const
Returns the length.
void append(const PositionVector &v, double sameThreshold=2.0)
double length() const
Returns the length.
Position intersectionPosition2D(const Position &p1, const Position &p2, const double withinDist=0.) const
Returns the position of the intersection.
void push_front_noDoublePos(const Position &p)
insert in front a non double position
void add(double xoff, double yoff, double zoff)
std::vector< double > intersectsAtLengths2D(const PositionVector &other) const
For all intersections between this vector and other, return the 2D-length of the subvector from this ...
double distance2D(const Position &p, bool perpendicular=false) const
closest 2D-distance to point p (or -1 if perpendicular is true and the point is beyond this vector)
double nearest_offset_to_point2D(const Position &p, bool perpendicular=true) const
return the nearest offest to point 2D
std::vector< double > distances(const PositionVector &s, bool perpendicular=false) const
distances of all my points to s and all of s points to myself
std::pair< PositionVector, PositionVector > splitAt(double where, bool use2D=false) const
Returns the two lists made when this list vector is splitted at the given point.
void move2side(double amount, double maxExtension=100)
move position vector to side using certain amount
PositionVector getSubpart2D(double beginOffset, double endOffset) const
get subpart of a position vector in two dimensions (Z is ignored)
PositionVector interpolateZ(double zStart, double zEnd) const
returned vector that varies z smoothly over its length
double angleAt2D(int pos) const
get angle in certain position of position vector (in radians between -M_PI and M_PI)
void extrapolate2D(const double val, const bool onlyFirst=false)
extrapolate position vector in two dimensions (Z is ignored)
void push_back_noDoublePos(const Position &p)
insert in back a non double position
bool intersects(const Position &p1, const Position &p2) const
Returns the information whether this list of points interesects the given line.
PositionVector reverse() const
reverse position vector
PositionVector getSubpartByIndex(int beginIndex, int count) const
get subpart of a position vector using index and a cout
Position positionAtOffset2D(double pos, double lateralOffset=0) const
Returns the position at the given length.
void sub(const Position &offset)
PositionVector getSubpart(double beginOffset, double endOffset) const
get subpart of a position vector
static T maxValue(const std::vector< T > &v)
Definition: VectorHelper.h:87
static T minValue(const std::vector< T > &v)
Definition: VectorHelper.h:97
#define M_PI
Definition: odrSpiral.cpp:45