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  // do nothing.
352  } else if (farAngleDist < DEG2RAD(135) || (fabs(RAD2DEG(farAngleDist) - 180) > 1 && fabs(a2 - a1) < 10)) {
353  if (keepBothDistances) {
354  if (ccwCloser) {
355  distances2[*i] = a2;
356  } else {
357  distances[*i] = a2;
358  distances2[*i] = a1;
359  }
360  } else {
361  distances[*i] = MAX2(a1, a2);
362  }
363  }
364 #ifdef DEBUG_NODE_SHAPE
365  if (DEBUGCOND) {
366  std::cout << " a1=" << a1 << " a2=" << a2 << " keepBoth=" << keepBothDistances << " dist=" << distances[*i] << "\n";
367  }
368 #endif
369  }
370  } else {
371  if (*cwi != *ccwi && currGeom2.intersects(neighGeom2)) {
372  distances[*i] = (neigh2LargeTurn ? myRadius : smallRadius) + currGeom2.intersectsAtLengths2D(neighGeom2)[0];
373 #ifdef DEBUG_NODE_SHAPE
374  if (DEBUGCOND) {
375  std::cout << " neigh2 intersects dist=" << distances[*i] << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
376  }
377 #endif
378  } else {
379  distances[*i] = EXT + myRadius;
380 #ifdef DEBUG_NODE_SHAPE
381  if (DEBUGCOND) {
382  std::cout << " no intersects dist=" << distances[*i] << " currGeom=" << currGeom << " neighGeom=" << neighGeom << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
383  }
384 #endif
385  }
386  }
387  } else {
388  if (currGeom.intersects(neighGeom)) {
389  distances[*i] = currGeom.intersectsAtLengths2D(neighGeom)[0];
390  } else {
391  distances[*i] = (double) EXT;
392  }
393  }
394  }
395  if (useDefaultRadius && sCurveStretch > 0) {
396  double sCurveWidth = myNode.getDisplacementError();
397  if (sCurveWidth > 0) {
398  const double sCurveRadius = myRadius + sCurveWidth / SUMO_const_laneWidth * sCurveStretch * pow((*i)->getSpeed(), 2 + sCurveStretch) / 1000;
399  const double stretch = EXT + sCurveRadius - distances[*i];
400  if (stretch > 0) {
401  distances[*i] += stretch;
402  // fixate extended geometry for repeated computation
403  const double shorten = distances[*i] - EXT;
404  (*i)->shortenGeometryAtNode(&myNode, shorten);
405  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
406  (*k)->shortenGeometryAtNode(&myNode, shorten);
407  }
408 #ifdef DEBUG_NODE_SHAPE
409  if (DEBUGCOND) {
410  std::cout << " stretching junction: sCurveWidth=" << sCurveWidth << " sCurveRadius=" << sCurveRadius << " stretch=" << stretch << " dist=" << distances[*i] << "\n";
411  }
412 #endif
413  }
414  }
415  }
416  }
417 
418  for (NBEdge* const edge : newAll) {
419  if (distances.find(edge) == distances.end()) {
420  assert(false);
421  distances[edge] = EXT;
422  }
423  }
424  // because of lane spread right the crossing point may be identical to the junction center and thus the distance is exactly EXT
425  const double off = EXT - NUMERICAL_EPS;
426  // prevent inverted node shapes
427  // (may happen with near-parallel edges)
428  const double minDistSum = 2 * (EXT + myRadius);
429  for (NBEdge* const edge : newAll) {
430  if (distances[edge] < off && edge->hasDefaultGeometryEndpointAtNode(&myNode)) {
431  for (EdgeVector::const_iterator j = newAll.begin(); j != newAll.end(); ++j) {
432  if (distances[*j] > off && (*j)->hasDefaultGeometryEndpointAtNode(&myNode) && distances[edge] + distances[*j] < minDistSum) {
433  const double angleDiff = fabs(NBHelpers::relAngle(edge->getAngleAtNode(&myNode), (*j)->getAngleAtNode(&myNode)));
434  if (angleDiff > 160 || angleDiff < 20) {
435 #ifdef DEBUG_NODE_SHAPE
436  if (DEBUGCOND) {
437  std::cout << " increasing dist for i=" << edge->getID() << " because of j=" << (*j)->getID() << " jDist=" << distances[*j]
438  << " oldI=" << distances[edge] << " newI=" << minDistSum - distances[*j]
439  << " angleDiff=" << angleDiff
440  << " geomI=" << edge->getGeometry() << " geomJ=" << (*j)->getGeometry() << "\n";
441  }
442 #endif
443  distances[edge] = minDistSum - distances[*j];
444  }
445  }
446  }
447  }
448  }
449 
450 
451  // build
452  PositionVector ret;
453  for (i = newAll.begin(); i != newAll.end(); ++i) {
454  const PositionVector& ccwBound = geomsCCW[*i];
455  const PositionVector& cwBound = geomsCW[*i];
456  //double offset = MIN3(distances[*i], cwBound.length2D() - POSITION_EPS, ccwBound.length2D() - POSITION_EPS);
457  double offset = distances[*i];
458  double offset2 = distances2.count(*i) != 0 ? distances2[*i] : offset;
459  if (offset != offset2) {
460  // keep rectangular cuts if the difference is small or the roads aren't
461  // really divided by much (unless the angle is very different)
462  const double dWidth = divisionWidth(*i, same[*i],
463  ccwBound.positionAtOffset2D(offset),
464  cwBound.positionAtOffset2D(offset2));
465  const double angle = RAD2DEG(GeomHelper::angleDiff(ccwBound.angleAt2D(0), cwBound.angleAt2D(0)));
466  const double oDelta = fabs(offset - offset2);
467  //std::cout << " i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dWidth=" << dWidth << " angle=" << angle << " same=" << joinNamedToStringSorting(same[*i], ",") << "\n";
468  if ((((oDelta < 5 || dWidth < 10) && fabs(angle) < 30)) || (fabs(angle) < 5 && myNode.getType() != SumoXMLNodeType::RAIL_CROSSING)) {
469 #ifdef DEBUG_NODE_SHAPE
470  std::cout << " i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dWidth=" << dWidth << " angle=" << angle << " same=" << joinNamedToStringSorting(same[*i], ",") << "\n";
471 #endif
472  offset = MAX2(offset, offset2);
473  offset2 = offset;
474  }
475  }
476  if (!(*i)->hasDefaultGeometryEndpointAtNode(&myNode)) {
477  // for non geometry-endpoints, only shorten but never extend the geometry
478  if (advanceStopLine > 0 && offset < EXT) {
479 #ifdef DEBUG_NODE_SHAPE
480  std::cout << " i=" << (*i)->getID() << " offset=" << offset << " advanceStopLine=" << advanceStopLine << "\n";
481 #endif
482  // fixate extended geometry for repeated computation
483  (*i)->extendGeometryAtNode(&myNode, advanceStopLine);
484  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
485  (*k)->extendGeometryAtNode(&myNode, advanceStopLine);
486  }
487  }
488  offset = MAX2(EXT - advanceStopLine, offset);
489  offset2 = MAX2(EXT - advanceStopLine, offset2);
490  }
491  if (offset == -1) {
492  WRITE_WARNINGF(TL("Fixing offset for edge '%' at node '%."), (*i)->getID(), myNode.getID());
493  offset = -.1;
494  offset2 = -.1;
495  }
496  Position p = ccwBound.positionAtOffset2D(offset);
497  p.setz(myNode.getPosition().z());
498  if (i != newAll.begin()) {
499  ret.append(getSmoothCorner(geomsCW[*(i - 1)], ccwBound, ret[-1], p, cornerDetail));
500  }
501  Position p2 = cwBound.positionAtOffset2D(offset2);
502  p2.setz(myNode.getPosition().z());
503  //ret.append(getEdgeCuts(*i, geomsCCW, geomsCW, offset, offset2, same));
504  ret.push_back_noDoublePos(p);
505  ret.push_back_noDoublePos(p2);
506 #ifdef DEBUG_NODE_SHAPE
507  if (DEBUGCOND) {
508  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";
509  }
510 #endif
511  (*i)->setNodeBorder(&myNode, p, p2, rectangularCut);
512  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
513  (*k)->setNodeBorder(&myNode, p, p2, rectangularCut);
514  }
515  }
516  // final curve segment
517  ret.append(getSmoothCorner(geomsCW[*(newAll.end() - 1)], geomsCCW[*newAll.begin()], ret[-1], ret[0], cornerDetail));
518 #ifdef DEBUG_NODE_SHAPE
519  if (DEBUGCOND) {
520  std::cout << " final shape=" << ret << "\n";
521  }
522 #endif
523  return ret;
524 }
525 
526 
527 double
528 NBNodeShapeComputer::closestIntersection(const PositionVector& geom1, const PositionVector& geom2, double offset) {
529  std::vector<double> intersections = geom1.intersectsAtLengths2D(geom2);
530  double result = intersections[0];
531  for (std::vector<double>::iterator it = intersections.begin() + 1; it != intersections.end(); ++it) {
532  if (fabs(*it - offset) < fabs(result - offset)) {
533  result = *it;
534  }
535  }
536  return result;
537 }
538 
539 bool
541  std::map<NBEdge*, std::set<NBEdge*> >& same) const {
542  const SVCPermissions p1 = e1->getPermissions();
543  const SVCPermissions p2 = e2->getPermissions();
544  if ((p1 & p2 & SVC_LARGE_TURN) != 0) {
545  // note: would could also check whether there is actually a connection
546  // between those edges
547  return true;
548  }
549  // maybe edges in the same direction need a large turn
550  for (NBEdge* e2s : same[e2]) {
551  if ((p1 & e2s->getPermissions() & SVC_LARGE_TURN) != 0
552  && (e1->getToNode() == e2s->getFromNode() || e2s->getToNode() == e1->getFromNode())) {
553  return true;
554  }
555  for (NBEdge* e1s : same[e1]) {
556  if ((e2s->getPermissions() & e1s->getPermissions() & SVC_LARGE_TURN) != 0
557  && (e2s->getToNode() == e1s->getFromNode() || e1s->getToNode() == e2s->getFromNode())) {
558  return true;
559  }
560  }
561  }
562  for (NBEdge* e1s : same[e1]) {
563  if ((p2 & e1s->getPermissions() & SVC_LARGE_TURN) != 0
564  && (e2->getToNode() == e1s->getFromNode() || e1s->getToNode() == e2->getFromNode())) {
565  return true;
566  }
567  }
568  //std::cout << " e1=" << e1->getID() << " e2=" << e2->getID() << " sameE1=" << toString(same[e1]) << " sameE2=" << toString(same[e2]) << "\n";
569  return false;
570 }
571 
574  const Position& begPoint, const Position& endPoint, int cornerDetail) {
575  PositionVector ret;
576  if (cornerDetail > 0) {
577  PositionVector begShape2 = begShape.reverse().getSubpart2D(EXT2, begShape.length());
578  const double begSplit = begShape2.nearest_offset_to_point2D(begPoint, false);
579 #ifdef DEBUG_SMOOTH_CORNERS
580  if (DEBUGCOND) {
581  std::cout << " begLength=" << begShape2.length2D() << " begSplit=" << begSplit << "\n";
582  }
583 #endif
584  if (begSplit > POSITION_EPS && begSplit < begShape2.length2D() - POSITION_EPS) {
585  begShape2 = begShape2.splitAt(begSplit, true).first;
586  } else {
587  return ret;
588  }
589  PositionVector endShape2 = endShape.getSubpart(0, endShape.length() - EXT2);
590  const double endSplit = endShape2.nearest_offset_to_point2D(endPoint, false);
591 #ifdef DEBUG_SMOOTH_CORNERS
592  if (DEBUGCOND) {
593  std::cout << " endLength=" << endShape2.length2D() << " endSplit=" << endSplit << "\n";
594  }
595 #endif
596  if (endSplit > POSITION_EPS && endSplit < endShape2.length2D() - POSITION_EPS) {
597  endShape2 = endShape2.splitAt(endSplit, true).second;
598  } else {
599  return ret;
600  }
601  // flatten z to junction z level
602  begShape2 = begShape2.interpolateZ(myNode.getPosition().z(), myNode.getPosition().z());
603  endShape2 = endShape2.interpolateZ(myNode.getPosition().z(), myNode.getPosition().z());
604 #ifdef DEBUG_SMOOTH_CORNERS
605  if (DEBUGCOND) {
606  std::cout << "getSmoothCorner begPoint=" << begPoint << " endPoint=" << endPoint
607  << " begShape=" << begShape << " endShape=" << endShape
608  << " begShape2=" << begShape2 << " endShape2=" << endShape2
609  << "\n";
610  }
611 #endif
612  if (begShape2.size() < 2 || endShape2.size() < 2) {
613  return ret;
614  }
615  const double angle = GeomHelper::angleDiff(begShape2.angleAt2D(-2), endShape2.angleAt2D(0));
616  NBNode* recordError = nullptr;
617 #ifdef DEBUG_SMOOTH_CORNERS
618  if (DEBUGCOND) {
619  std::cout << " angle=" << RAD2DEG(angle) << "\n";
620  }
621  recordError = const_cast<NBNode*>(&myNode);
622 #endif
623  // fill highly acute corners
624  //if (fabs(angle) > DEG2RAD(135)) {
625  // return ret;
626  //}
627  PositionVector curve = myNode.computeSmoothShape(begShape2, endShape2, cornerDetail + 2, false, 25, 25, recordError, NBNode::AVOID_WIDE_LEFT_TURN);
628  //PositionVector curve = myNode.computeSmoothShape(begShape2, endShape2, cornerDetail + 2, false, 25, 25, recordError, 0);
629  const double curvature = curve.length2D() / MAX2(NUMERICAL_EPS, begPoint.distanceTo2D(endPoint));
630 #ifdef DEBUG_SMOOTH_CORNERS
631  if (DEBUGCOND) {
632  std::cout << " curve=" << curve << " curveLength=" << curve.length2D() << " dist=" << begPoint.distanceTo2D(endPoint) << " curvature=" << curvature << "\n";
633  }
634 #endif
635  if (curvature > 2 && angle > DEG2RAD(85)) {
636  // simplify dubious inside corner shape
637  return ret;
638  }
639  if (curve.size() > 2) {
640  curve.erase(curve.begin());
641  curve.pop_back();
642  ret = curve;
643  }
644  }
645  return ret;
646 }
647 
648 void
650  GeomsMap& geomsCCW,
651  GeomsMap& geomsCW) {
652  // compute boundary lines and extend it by EXT m
653  for (NBEdge* const edge : edges) {
654  // store current edge's boundary as current ccw/cw boundary
655  try {
656  geomsCCW[edge] = edge->getCCWBoundaryLine(myNode);
657  } catch (InvalidArgument& e) {
658  WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': " + std::string(e.what()));
659  geomsCCW[edge] = edge->getGeometry();
660  }
661  try {
662  geomsCW[edge] = edge->getCWBoundaryLine(myNode);
663  } catch (InvalidArgument& e) {
664  WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': " + std::string(e.what()));
665  geomsCW[edge] = edge->getGeometry();
666  }
667  // ensure the boundary is valid
668  if (geomsCCW[edge].length2D() < NUMERICAL_EPS) {
669  geomsCCW[edge] = edge->getGeometry();
670  }
671  if (geomsCW[edge].length2D() < NUMERICAL_EPS) {
672  geomsCW[edge] = edge->getGeometry();
673  }
674  // cut off all parts beyond EXT to avoid issues with curved-back roads
675  geomsCCW[edge] = geomsCCW[edge].getSubpart2D(0, MAX2(EXT, edge->getTotalWidth()));
676  geomsCW[edge] = geomsCW[edge].getSubpart2D(0, MAX2(EXT, edge->getTotalWidth()));
677  // extend the boundary by extrapolating it by EXT m towards the junction
678  geomsCCW[edge].extrapolate2D(EXT, true);
679  geomsCW[edge].extrapolate2D(EXT, true);
680  // ensure minimum length by extending it away from the junction
681  geomsCCW[edge].extrapolate(EXT2, false, true);
682  geomsCW[edge].extrapolate(EXT2, false, true);
683  }
684 }
685 
686 void
687 NBNodeShapeComputer::joinSameDirectionEdges(const EdgeVector& edges, std::map<NBEdge*, std::set<NBEdge*> >& same, bool useEndpoints) {
688  // compute same (edges where an intersection doesn't work well
689  // (always check an edge and its cw neighbor)
690  const double angleChangeLookahead = 35; // distance to look ahead for a misleading angle
691  const bool isXodr = OptionsCont::getOptions().exists("opendrive-files") && OptionsCont::getOptions().isSet("opendrive-files");
692  EdgeSet foundOpposite;
693  for (EdgeVector::const_iterator i = edges.begin(); i != edges.end(); i++) {
694  EdgeVector::const_iterator j;
695  if (i == edges.end() - 1) {
696  j = edges.begin();
697  } else {
698  j = i + 1;
699  }
700  if (useEndpoints
701  && !(*i)->hasDefaultGeometryEndpointAtNode(&myNode)
702  && !(*j)->hasDefaultGeometryEndpointAtNode(&myNode)) {
703  continue;
704  }
705  const bool incoming = (*i)->getToNode() == &myNode;
706  const bool incoming2 = (*j)->getToNode() == &myNode;
707  const bool differentDirs = (incoming != incoming2);
708  const bool sameGeom = (*i)->getGeometry() == (differentDirs ? (*j)->getGeometry().reverse() : (*j)->getGeometry());
709  const PositionVector g1 = incoming ? (*i)->getCCWBoundaryLine(myNode) : (*i)->getCWBoundaryLine(myNode);
710  const PositionVector g2 = incoming ? (*j)->getCCWBoundaryLine(myNode) : (*j)->getCWBoundaryLine(myNode);
711  const double angle1further = (g1.size() > 2 && g1[0].distanceTo2D(g1[1]) < angleChangeLookahead ?
712  g1.angleAt2D(1) : g1.angleAt2D(0));
713  const double angle2further = (g2.size() > 2 && g2[0].distanceTo2D(g2[1]) < angleChangeLookahead ?
714  g2.angleAt2D(1) : g2.angleAt2D(0));
715  const double angleDiff = GeomHelper::angleDiff(g1.angleAt2D(0), g2.angleAt2D(0));
716  const double angleDiffFurther = GeomHelper::angleDiff(angle1further, angle2further);
717  const bool ambiguousGeometry = ((angleDiff > 0 && angleDiffFurther < 0) || (angleDiff < 0 && angleDiffFurther > 0));
718  //if (ambiguousGeometry) {
719  // @todo: this warning would be helpful in many cases. However, if angle and angleFurther jump between 179 and -179 it is misleading
720  // WRITE_WARNINGF(TL("Ambiguous angles at junction '%' for edges '%' and '%'."), myNode.getID(), (*i)->getID(), (*j)->getID());
721  //}
722 #ifdef DEBUG_NODE_SHAPE
723  if (DEBUGCOND) {
724  std::cout << " checkSameDirection " << (*i)->getID() << " " << (*j)->getID()
725  << " diffDirs=" << differentDirs
726  << " isOpposite=" << (differentDirs && foundOpposite.count(*i) == 0)
727  << " angleDiff=" << angleDiff
728  << " ambiguousGeometry=" << ambiguousGeometry
729  << " badInsersection=" << badIntersection(*i, *j, EXT)
730  << "\n";
731 
732  }
733 #endif
734  if (sameGeom || fabs(angleDiff) < DEG2RAD(20)) {
735  const bool isOpposite = differentDirs && foundOpposite.count(*i) == 0;
736  if (isOpposite) {
737  foundOpposite.insert(*i);
738  foundOpposite.insert(*j);
739  }
740  if (isOpposite || ambiguousGeometry || (!isXodr && badIntersection(*i, *j, EXT))) {
741  // maintain equivalence relation for all members of the equivalence class
742  for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
743  if (*j != *k) {
744  same[*k].insert(*j);
745  same[*j].insert(*k);
746  }
747  }
748  for (std::set<NBEdge*>::iterator k = same[*j].begin(); k != same[*j].end(); ++k) {
749  if (*i != *k) {
750  same[*k].insert(*i);
751  same[*i].insert(*k);
752  }
753  }
754  same[*i].insert(*j);
755  same[*j].insert(*i);
756 #ifdef DEBUG_NODE_SHAPE
757  if (DEBUGCOND) {
758  std::cout << " joinedSameDirectionEdges " << (*i)->getID() << " " << (*j)->getID() << " isOpposite=" << isOpposite << " ambiguousGeometry=" << ambiguousGeometry << "\n";
759  }
760 #endif
761  }
762  }
763  }
764 }
765 
766 
767 bool
768 NBNodeShapeComputer::badIntersection(const NBEdge* e1, const NBEdge* e2, double distance) {
769  // check whether the two edges are on top of each other. In that case they should be joined
770  // also, if they never touch along their common length
771  const double commonLength = MIN3(distance, e1->getGeometry().length(), e2->getGeometry().length());
772  PositionVector geom1 = e1->getGeometry();
773  PositionVector geom2 = e2->getGeometry();
774  // shift to make geom the centerline of the edge regardless of spreadtype
776  geom1.move2side(e1->getTotalWidth() / 2);
777  }
779  geom2.move2side(e2->getTotalWidth() / 2);
780  }
781  // always let geometry start at myNode
782  if (e1->getToNode() == &myNode) {
783  geom1 = geom1.reverse();
784  }
785  if (e2->getToNode() == &myNode) {
786  geom2 = geom2.reverse();
787  }
788  geom1 = geom1.getSubpart2D(0, commonLength);
789  geom2 = geom2.getSubpart2D(0, commonLength);
790  double endAngleDiff = 0;
791  if (geom1.size() >= 2 && geom2.size() >= 2) {
792  endAngleDiff = fabs(RAD2DEG(GeomHelper::angleDiff(
793  geom1.angleAt2D((int)geom1.size() - 2),
794  geom2.angleAt2D((int)geom2.size() - 2))));
795  }
796  const double minDistanceThreshold = (e1->getTotalWidth() + e2->getTotalWidth()) / 2 + POSITION_EPS;
797  std::vector<double> distances = geom1.distances(geom2, true);
798  std::vector<double> distances2 = geom1.distances(geom2);
799  const double minDist = VectorHelper<double>::minValue(distances2);
800  const double maxDist = VectorHelper<double>::maxValue(distances);
801  const bool curvingTowards = geom1[0].distanceTo2D(geom2[0]) > minDistanceThreshold && minDist < minDistanceThreshold;
802  const bool onTop = (maxDist - POSITION_EPS < minDistanceThreshold) && endAngleDiff < 30;
803  const bool bothDefault = e1->hasDefaultGeometryEndpointAtNode(&myNode) && e2->hasDefaultGeometryEndpointAtNode(&myNode);
804  const bool neverTouch = minDist > minDistanceThreshold * 2 && !bothDefault;
805  geom1.extrapolate2D(EXT);
806  geom2.extrapolate2D(EXT);
807  Position intersect = geom1.intersectionPosition2D(geom2);
808  const bool intersects = intersect != Position::INVALID && geom1.distance2D(intersect) < POSITION_EPS;
809 #ifdef DEBUG_NODE_SHAPE
810  if (DEBUGCOND) {
811  std::cout << " badIntersect: onTop=" << onTop << " curveTo=" << curvingTowards << " intersects=" << intersects
812  << " endAngleDiff=" << endAngleDiff
813  << " geom1=" << geom1 << " geom2=" << geom2
814  << " distances=" << toString(distances) << " minDist=" << minDist << " maxDist=" << maxDist << " thresh=" << minDistanceThreshold
815  << " neverTouch=" << neverTouch
816  << " intersectPos=" << intersect
817  << "\n";
818  }
819 #endif
820  return onTop || curvingTowards || !intersects || neverTouch;
821 }
822 
823 
826  const EdgeVector& all,
827  std::map<NBEdge*, std::set<NBEdge*> >& same,
828  GeomsMap& geomsCCW,
829  GeomsMap& geomsCW) {
830  // store relationships
831  EdgeVector newAll = all;
832  for (NBEdge* e1 : all) {
833  // determine which of the edges marks the outer boundary
834  auto e2NewAll = std::find(newAll.begin(), newAll.end(), e1);
835 #ifdef DEBUG_NODE_SHAPE
836  if (DEBUGCOND) std::cout << "computeUniqueDirectionList e1=" << e1->getID()
837  << " deleted=" << (e2NewAll == newAll.end())
838  << " same=" << joinNamedToStringSorting(same[e1], ',') << "\n";
839 #endif
840  if (e2NewAll == newAll.end()) {
841  continue;
842  }
843  auto e1It = std::find(all.begin(), all.end(), e1);
844  auto bestCCW = e1It;
845  auto bestCW = e1It;
846  bool changed = true;
847  while (changed) {
848  changed = false;
849  for (NBEdge* e2 : same[e1]) {
850 #ifdef DEBUG_NODE_SHAPE
851  if (DEBUGCOND) {
852  std::cout << " e2=" << e2->getID() << "\n";
853  }
854 #endif
855  auto e2It = std::find(all.begin(), all.end(), e2);
856  if (e2It + 1 == bestCCW || (e2It == (all.end() - 1) && bestCCW == all.begin())) {
857  bestCCW = e2It;
858  changed = true;
859 #ifdef DEBUG_NODE_SHAPE
860  if (DEBUGCOND) {
861  std::cout << " bestCCW=" << e2->getID() << "\n";
862  }
863 #endif
864  } else if (bestCW + 1 == e2It || (bestCW == (all.end() - 1) && e2It == all.begin())) {
865  bestCW = e2It;
866  changed = true;
867 #ifdef DEBUG_NODE_SHAPE
868  if (DEBUGCOND) {
869  std::cout << " bestCW=" << e2->getID() << "\n";
870  }
871 #endif
872  }
873  }
874  }
875  if (bestCW != e1It) {
876  geomsCW[e1] = geomsCW[*bestCW];
877  computeSameEnd(geomsCW[e1], geomsCCW[e1]);
878  }
879  if (bestCCW != e1It) {
880  geomsCCW[e1] = geomsCCW[*bestCCW];
881  computeSameEnd(geomsCW[e1], geomsCCW[e1]);
882  }
883  // clean up
884  for (NBEdge* e2 : same[e1]) {
885  auto e2NewAllIt = std::find(newAll.begin(), newAll.end(), e2);
886  if (e2NewAllIt != newAll.end()) {
887  newAll.erase(e2NewAllIt);
888  }
889  }
890  }
891 #ifdef DEBUG_NODE_SHAPE
892  if (DEBUGCOND) {
893  std::cout << " newAll:\n";
894  for (NBEdge* e : newAll) {
895  std::cout << " " << e->getID() << " geomCCW=" << geomsCCW[e] << " geomsCW=" << geomsCW[e] << "\n";
896  }
897  }
898 #endif
899  return newAll;
900 }
901 
902 
903 void
904 NBNodeShapeComputer::initNeighbors(const EdgeVector& edges, const EdgeVector::const_iterator& current,
905  GeomsMap& geomsCW,
906  GeomsMap& geomsCCW,
907  EdgeVector::const_iterator& cwi,
908  EdgeVector::const_iterator& ccwi,
909  double& cad,
910  double& ccad) {
911  const double twoPI = (double)(2 * M_PI);
912  cwi = current;
913  cwi++;
914  if (cwi == edges.end()) {
915  std::advance(cwi, -((int)edges.size())); // set to edges.begin();
916  }
917  ccwi = current;
918  if (ccwi == edges.begin()) {
919  std::advance(ccwi, edges.size() - 1); // set to edges.end() - 1;
920  } else {
921  ccwi--;
922  }
923 
924  const double angleCurCCW = geomsCCW[*current].angleAt2D(0);
925  const double angleCurCW = geomsCW[*current].angleAt2D(0);
926  const double angleCCW = geomsCW[*ccwi].angleAt2D(0);
927  const double angleCW = geomsCCW[*cwi].angleAt2D(0);
928  ccad = angleCCW - angleCurCCW;
929  while (ccad < 0.) {
930  ccad += twoPI;
931  }
932  cad = angleCurCW - angleCW;
933  while (cad < 0.) {
934  cad += twoPI;
935  }
936 }
937 
938 
939 
940 const PositionVector
942 #ifdef DEBUG_NODE_SHAPE
943  if (DEBUGCOND) {
944  std::cout << "computeNodeShapeSmall node=" << myNode.getID() << "\n";
945  }
946 #endif
947  PositionVector ret;
948  for (NBEdge* e : myNode.getEdges()) {
949  // compute crossing with normal
950  PositionVector edgebound1 = e->getCCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
951  PositionVector edgebound2 = e->getCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
952  Position delta = edgebound1[1] - edgebound1[0];
953  delta.set(-delta.y(), delta.x()); // rotate 90 degrees
954  PositionVector cross(myNode.getPosition(), myNode.getPosition() + delta);
955  cross.extrapolate2D(500);
956  edgebound1.extrapolate2D(500);
957  edgebound2.extrapolate2D(500);
958  if (cross.intersects(edgebound1)) {
959  Position np = cross.intersectionPosition2D(edgebound1);
960  np.set(np.x(), np.y(), myNode.getPosition().z());
961  ret.push_back_noDoublePos(np);
962  }
963  if (cross.intersects(edgebound2)) {
964  Position np = cross.intersectionPosition2D(edgebound2);
965  np.set(np.x(), np.y(), myNode.getPosition().z());
966  ret.push_back_noDoublePos(np);
967  }
968  e->resetNodeBorder(&myNode);
969  }
970  return ret;
971 }
972 
973 
974 double
976  // look for incoming/outgoing edge pairs that do not go straight and allow wide vehicles
977  // (connection information is not available yet)
978  // @TODO compute the radius for each pair of neighboring edge intersections in computeNodeShapeDefault rather than use the maximum
979  const double radius = oc.getFloat("default.junctions.radius");
980  const double smallRadius = oc.getFloat("junctions.small-radius");
981  double maxRightAngle = 0; // rad
982  double extraWidthRight = 0; // m
983  double maxLeftAngle = 0; // rad
984  double extraWidthLeft = 0; // m
985  int laneDelta = 0;
986  int totalWideLanesIn = 0;
987  for (NBEdge* in : myNode.getIncomingEdges()) {
988  int wideLanesIn = 0;
989  for (int i = 0; i < in->getNumLanes(); i++) {
990  if ((in->getPermissions(i) & SVC_LARGE_TURN) != 0) {
991  wideLanesIn++;
992  }
993  }
994  totalWideLanesIn += wideLanesIn;
995  for (NBEdge* out : myNode.getOutgoingEdges()) {
996  if ((in->getPermissions() & out->getPermissions() & SVC_LARGE_TURN) != 0) {
997  if (myNode.getDirection(in, out) == LinkDirection::TURN) {
998  continue;
999  };
1000  const double angle = GeomHelper::angleDiff(
1001  in->getGeometry().angleAt2D(-2),
1002  out->getGeometry().angleAt2D(0));
1003  if (angle < 0) {
1004  if (maxRightAngle < -angle) {
1005  maxRightAngle = -angle;
1006  extraWidthRight = MAX2(getExtraWidth(in, SVC_LARGE_TURN), getExtraWidth(out, SVC_LARGE_TURN));
1007  }
1008  } else {
1009  if (maxLeftAngle < angle) {
1010  maxLeftAngle = angle;
1011  // all edges clockwise between in and out count as extra width
1012  extraWidthLeft = 0;
1013  EdgeVector::const_iterator pIn = std::find(myNode.getEdges().begin(), myNode.getEdges().end(), in);
1015  while (*pIn != out) {
1016  extraWidthLeft += (*pIn)->getTotalWidth();
1017 #ifdef DEBUG_RADIUS
1018  if (DEBUGCOND) {
1019  std::cout << " in=" << in->getID() << " out=" << out->getID() << " extra=" << (*pIn)->getID() << " extraWidthLeft=" << extraWidthLeft << "\n";
1020  }
1021 #endif
1023  }
1024  }
1025  }
1026  int wideLanesOut = 0;
1027  for (int i = 0; i < out->getNumLanes(); i++) {
1028  if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1029  wideLanesOut++;
1030  }
1031  }
1032 #ifdef DEBUG_RADIUS
1033  if (DEBUGCOND) {
1034  std::cout << " in=" << in->getID() << " out=" << out->getID() << " wideLanesIn=" << wideLanesIn << " wideLanesOut=" << wideLanesOut << "\n";
1035  }
1036 #endif
1037  laneDelta = MAX2(laneDelta, abs(wideLanesOut - wideLanesIn));
1038  }
1039  }
1040  }
1041  // special case: on/off-ramp
1042  if (myNode.getOutgoingEdges().size() == 1 || myNode.getIncomingEdges().size() == 1) {
1043  int totalWideLanesOut = 0;
1044  for (NBEdge* out : myNode.getOutgoingEdges()) {
1045  for (int i = 0; i < out->getNumLanes(); i++) {
1046  if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1047  totalWideLanesOut++;
1048  }
1049  }
1050  }
1051  if (totalWideLanesIn == totalWideLanesOut) {
1052  // use total laneDelta instead of individual edge lane delta
1053  laneDelta = 0;
1054  }
1055  }
1056  // changing the number of wide-vehicle lanes on a straight segment requires a larger junction to allow for smooth driving
1057  // otherwise we can reduce the radius according to the angle
1058  double result = radius;
1059  // left turns are assumed to cross additional edges and thus du not determine the required radius in most cases
1060  double maxTurnAngle = maxRightAngle;
1061  double extraWidth = extraWidthRight;
1062  if (maxRightAngle < DEG2RAD(5)) {
1063  maxTurnAngle = maxLeftAngle;
1064  extraWidth = extraWidthLeft;
1065  }
1066  const double minRadius = maxTurnAngle >= DEG2RAD(30) ? MIN2(smallRadius, radius) : smallRadius;
1067  if (laneDelta == 0 || maxTurnAngle >= DEG2RAD(30) || myNode.isConstantWidthTransition()) {
1068  // subtract radius gained from extra lanes
1069  // do not increase radius for turns that are sharper than a right angle
1070  result = radius * tan(0.5 * MIN2(0.5 * M_PI, maxTurnAngle)) - extraWidth;
1071  }
1072  result = MAX2(minRadius, result);
1073 #ifdef DEBUG_RADIUS
1074  if (DEBUGCOND) {
1075  std::cout << "getDefaultRadius n=" << myNode.getID()
1076  << " r=" << radius << " sr=" << smallRadius
1077  << " mr=" << minRadius
1078  << " laneDelta=" << laneDelta
1079  << " rightA=" << RAD2DEG(maxRightAngle)
1080  << " leftA=" << RAD2DEG(maxLeftAngle)
1081  << " maxA=" << RAD2DEG(maxTurnAngle)
1082  << " extraWidth=" << extraWidth
1083  << " result=" << result << "\n";
1084  }
1085 #endif
1086  return result;
1087 }
1088 
1089 
1090 bool
1091 NBNodeShapeComputer::isDivided(const NBEdge* e, std::set<NBEdge*> same, const PositionVector& ccw, const PositionVector& cw) const {
1092  if (same.size() < 2) {
1093  return false;
1094  }
1095  std::set<Position> endPoints;
1096  endPoints.insert(e->getEndpointAtNode(&myNode));
1097  for (NBEdge* s : same) {
1098  endPoints.insert(s->getEndpointAtNode(&myNode));
1099  }
1100  if (endPoints.size() > 1) {
1101  std::vector<double> distances = ccw.distances(cw, true);
1102  double width = e->getTotalWidth();
1103  for (const NBEdge* e2 : same) {
1104  width += e2->getTotalWidth();
1105  }
1106  const double maxDist = VectorHelper<double>::maxValue(distances);
1107  const double maxDivider = maxDist - width;
1108  return maxDivider >= 5;
1109  }
1110  return false;
1111 }
1112 
1113 
1114 double
1116  double result = 0;
1117  int lane = 0;
1118  while (lane < e->getNumLanes() && e->getPermissions(lane) == 0) {
1119  // ignore forbidden lanes out the outside
1120  lane++;
1121  }
1122  while (lane < e->getNumLanes() && (e->getPermissions(lane) & exclude) == 0) {
1123  result += e->getLaneWidth(lane);
1124  lane++;
1125  }
1126  return result;
1127 }
1128 
1129 
1130 double
1131 NBNodeShapeComputer::divisionWidth(const NBEdge* e, std::set<NBEdge*> same, const Position& p, const Position& p2) {
1132  double result = p.distanceTo2D(p2);
1133  result -= e->getTotalWidth();
1134  for (NBEdge* e2 : same) {
1135  result -= e2->getTotalWidth();
1136  }
1137  return MAX2(0.0, result);
1138 }
1139 
1140 /****************************************************************************/
#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 permission 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:4306
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:979
Position getEndpointAtNode(const NBNode *node) const
Definition: NBEdge.cpp:613
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:4144
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:602
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:2349
bool hasIncoming(const NBEdge *const e) const
Returns whether the given edge ends at this node.
Definition: NBNode.cpp:1847
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:2918
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
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:317
double distanceTo2D(const Position &p2) const
returns the euclidean distance in the x-y-plane
Definition: Position.h:271
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