Eclipse SUMO - Simulation of Urban MObility
Loading...
Searching...
No Matches
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-2025 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>
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
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
100void
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
123const 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
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*, ComparatorIdLess> > 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));
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
537double
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
549bool
551 std::map<NBEdge*, std::set<NBEdge*, ComparatorIdLess> >& 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
658void
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 if (geomsCCW[edge].isNAN() || geomsCW[edge].isNAN()) {
694 WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': found invalid boundary line for edge '" + edge->getID() + "'.")
695 }
696 if (geomsCCW[edge].isNAN()) {
697 geomsCCW[edge] = edge->getGeometry();
698 }
699 if (geomsCW[edge].isNAN()) {
700 geomsCW[edge] = edge->getGeometry();
701 }
702 }
703}
704
705void
706NBNodeShapeComputer::joinSameDirectionEdges(const EdgeVector& edges, std::map<NBEdge*, std::set<NBEdge*, ComparatorIdLess> >& same, bool useEndpoints) {
707 // compute same (edges where an intersection doesn't work well
708 // (always check an edge and its cw neighbor)
709 const double angleChangeLookahead = 35; // distance to look ahead for a misleading angle
710 const bool isXodr = OptionsCont::getOptions().exists("opendrive-files") && OptionsCont::getOptions().isSet("opendrive-files");
711 EdgeSet foundOpposite;
712 for (EdgeVector::const_iterator i = edges.begin(); i != edges.end(); i++) {
713 EdgeVector::const_iterator j;
714 if (i == edges.end() - 1) {
715 j = edges.begin();
716 } else {
717 j = i + 1;
718 }
719 if (useEndpoints
720 && !(*i)->hasDefaultGeometryEndpointAtNode(&myNode)
721 && !(*j)->hasDefaultGeometryEndpointAtNode(&myNode)) {
722 continue;
723 }
724 const bool incoming = (*i)->getToNode() == &myNode;
725 const bool incoming2 = (*j)->getToNode() == &myNode;
726 const bool differentDirs = (incoming != incoming2);
727 const bool sameGeom = (*i)->getGeometry() == (differentDirs ? (*j)->getGeometry().reverse() : (*j)->getGeometry());
728 const PositionVector g1 = incoming ? (*i)->getCCWBoundaryLine(myNode) : (*i)->getCWBoundaryLine(myNode);
729 const PositionVector g2 = incoming ? (*j)->getCCWBoundaryLine(myNode) : (*j)->getCWBoundaryLine(myNode);
730 const double angle1further = (g1.size() > 2 && g1[0].distanceTo2D(g1[1]) < angleChangeLookahead ?
731 g1.angleAt2D(1) : g1.angleAt2D(0));
732 const double angle2further = (g2.size() > 2 && g2[0].distanceTo2D(g2[1]) < angleChangeLookahead ?
733 g2.angleAt2D(1) : g2.angleAt2D(0));
734 const double angleDiff = GeomHelper::angleDiff(g1.angleAt2D(0), g2.angleAt2D(0));
735 const double angleDiffFurther = GeomHelper::angleDiff(angle1further, angle2further);
736 const bool ambiguousGeometry = ((angleDiff > 0 && angleDiffFurther < 0) || (angleDiff < 0 && angleDiffFurther > 0));
737 //if (ambiguousGeometry) {
738 // @todo: this warning would be helpful in many cases. However, if angle and angleFurther jump between 179 and -179 it is misleading
739 // WRITE_WARNINGF(TL("Ambiguous angles at junction '%' for edges '%' and '%'."), myNode.getID(), (*i)->getID(), (*j)->getID());
740 //}
741#ifdef DEBUG_NODE_SHAPE
742 if (DEBUGCOND) {
743 std::cout << " checkSameDirection " << (*i)->getID() << " " << (*j)->getID()
744 << " sameGeom=" << sameGeom
745 << " diffDirs=" << differentDirs
746 << " isOpposite=" << (differentDirs && foundOpposite.count(*i) == 0)
747 << " angleDiff=" << angleDiff
748 << " ambiguousGeometry=" << ambiguousGeometry
749 << " badInsersection=" << badIntersection(*i, *j, EXT)
750 << "\n";
751
752 }
753#endif
754 if (sameGeom || fabs(angleDiff) < DEG2RAD(20)) {
755 const bool isOpposite = differentDirs && foundOpposite.count(*i) == 0;
756 if (isOpposite) {
757 foundOpposite.insert(*i);
758 foundOpposite.insert(*j);
759 }
760 if (isOpposite || ambiguousGeometry || (!isXodr && badIntersection(*i, *j, EXT))) {
761 // maintain equivalence relation for all members of the equivalence class
762 for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
763 if (*j != *k) {
764 same[*k].insert(*j);
765 same[*j].insert(*k);
766 }
767 }
768 for (std::set<NBEdge*>::iterator k = same[*j].begin(); k != same[*j].end(); ++k) {
769 if (*i != *k) {
770 same[*k].insert(*i);
771 same[*i].insert(*k);
772 }
773 }
774 same[*i].insert(*j);
775 same[*j].insert(*i);
776#ifdef DEBUG_NODE_SHAPE
777 if (DEBUGCOND) {
778 std::cout << " joinedSameDirectionEdges " << (*i)->getID() << " " << (*j)->getID() << " isOpposite=" << isOpposite << " ambiguousGeometry=" << ambiguousGeometry << "\n";
779 }
780#endif
781 }
782 }
783 }
784}
785
786
787bool
788NBNodeShapeComputer::badIntersection(const NBEdge* e1, const NBEdge* e2, double distance) {
789 // check whether the two edges are on top of each other. In that case they should be joined
790 // also, if they never touch along their common length
791 const double commonLength = MIN3(distance, e1->getGeometry().length(), e2->getGeometry().length());
792 PositionVector geom1 = e1->getGeometry();
793 PositionVector geom2 = e2->getGeometry();
794 // shift to make geom the centerline of the edge regardless of spreadtype
796 geom1.move2side(e1->getTotalWidth() / 2);
797 }
799 geom2.move2side(e2->getTotalWidth() / 2);
800 }
801 // always let geometry start at myNode
802 if (e1->getToNode() == &myNode) {
803 geom1 = geom1.reverse();
804 }
805 if (e2->getToNode() == &myNode) {
806 geom2 = geom2.reverse();
807 }
808 geom1 = geom1.getSubpart2D(0, commonLength);
809 geom2 = geom2.getSubpart2D(0, commonLength);
810 double endAngleDiff = 0;
811 if (geom1.size() >= 2 && geom2.size() >= 2) {
812 endAngleDiff = fabs(RAD2DEG(GeomHelper::angleDiff(
813 geom1.angleAt2D((int)geom1.size() - 2),
814 geom2.angleAt2D((int)geom2.size() - 2))));
815 }
816 const double minDistanceThreshold = (e1->getTotalWidth() + e2->getTotalWidth()) / 2 + POSITION_EPS;
817 std::vector<double> distances = geom1.distances(geom2, true);
818 std::vector<double> distances2 = geom1.distances(geom2);
819 const double minDist = VectorHelper<double>::minValue(distances2);
820 const double maxDist = VectorHelper<double>::maxValue(distances);
821 const bool curvingTowards = geom1[0].distanceTo2D(geom2[0]) > minDistanceThreshold && minDist < minDistanceThreshold;
822 const bool onTop = (maxDist - POSITION_EPS < minDistanceThreshold) && endAngleDiff < 30;
824 const bool neverTouch = minDist > minDistanceThreshold * 2 && !bothDefault;
825 geom1.extrapolate2D(EXT);
826 geom2.extrapolate2D(EXT);
827 Position intersect = geom1.intersectionPosition2D(geom2);
828 const bool intersects = intersect != Position::INVALID && geom1.distance2D(intersect) < POSITION_EPS;
829#ifdef DEBUG_NODE_SHAPE
830 if (DEBUGCOND) {
831 std::cout << " badIntersect: onTop=" << onTop << " curveTo=" << curvingTowards << " intersects=" << intersects
832 << " endAngleDiff=" << endAngleDiff
833 << " geom1=" << geom1 << " geom2=" << geom2
834 << " distances=" << toString(distances) << " minDist=" << minDist << " maxDist=" << maxDist << " thresh=" << minDistanceThreshold
835 << " neverTouch=" << neverTouch
836 << " intersectPos=" << intersect
837 << "\n";
838 }
839#endif
840 return onTop || curvingTowards || !intersects || neverTouch;
841}
842
843
846 const EdgeVector& all,
847 std::map<NBEdge*, std::set<NBEdge*, ComparatorIdLess> >& same,
848 GeomsMap& geomsCCW,
849 GeomsMap& geomsCW) {
850 // store relationships
851 EdgeVector newAll = all;
852 for (NBEdge* e1 : all) {
853 // determine which of the edges marks the outer boundary
854 auto e2NewAll = std::find(newAll.begin(), newAll.end(), e1);
855#ifdef DEBUG_NODE_SHAPE
856 if (DEBUGCOND) std::cout << "computeUniqueDirectionList e1=" << e1->getID()
857 << " deleted=" << (e2NewAll == newAll.end())
858 << " same=" << joinNamedToStringSorting(same[e1], ',') << "\n";
859#endif
860 if (e2NewAll == newAll.end()) {
861 continue;
862 }
863 auto e1It = std::find(all.begin(), all.end(), e1);
864 auto bestCCW = e1It;
865 auto bestCW = e1It;
866 bool changed = true;
867 while (changed) {
868 changed = false;
869 for (NBEdge* e2 : same[e1]) {
870#ifdef DEBUG_NODE_SHAPE
871 if (DEBUGCOND) {
872 std::cout << " e2=" << e2->getID() << "\n";
873 }
874#endif
875 auto e2It = std::find(all.begin(), all.end(), e2);
876 if (e2It + 1 == bestCCW || (e2It == (all.end() - 1) && bestCCW == all.begin())) {
877 bestCCW = e2It;
878 changed = true;
879#ifdef DEBUG_NODE_SHAPE
880 if (DEBUGCOND) {
881 std::cout << " bestCCW=" << e2->getID() << "\n";
882 }
883#endif
884 } else if (bestCW + 1 == e2It || (bestCW == (all.end() - 1) && e2It == all.begin())) {
885 bestCW = e2It;
886 changed = true;
887#ifdef DEBUG_NODE_SHAPE
888 if (DEBUGCOND) {
889 std::cout << " bestCW=" << e2->getID() << "\n";
890 }
891#endif
892 }
893 }
894 }
895 if (bestCW != e1It) {
896 geomsCW[e1] = geomsCW[*bestCW];
897 computeSameEnd(geomsCW[e1], geomsCCW[e1]);
898 }
899 if (bestCCW != e1It) {
900 geomsCCW[e1] = geomsCCW[*bestCCW];
901 computeSameEnd(geomsCW[e1], geomsCCW[e1]);
902 }
903 // clean up
904 for (NBEdge* e2 : same[e1]) {
905 auto e2NewAllIt = std::find(newAll.begin(), newAll.end(), e2);
906 if (e2NewAllIt != newAll.end()) {
907 newAll.erase(e2NewAllIt);
908 }
909 }
910 }
911#ifdef DEBUG_NODE_SHAPE
912 if (DEBUGCOND) {
913 std::cout << " newAll:\n";
914 for (NBEdge* e : newAll) {
915 std::cout << " " << e->getID() << " geomCCW=" << geomsCCW[e] << " geomsCW=" << geomsCW[e] << "\n";
916 }
917 }
918#endif
919 return newAll;
920}
921
922
923void
924NBNodeShapeComputer::initNeighbors(const EdgeVector& edges, const EdgeVector::const_iterator& current,
925 GeomsMap& geomsCW,
926 GeomsMap& geomsCCW,
927 EdgeVector::const_iterator& cwi,
928 EdgeVector::const_iterator& ccwi,
929 double& cad,
930 double& ccad) {
931 const double twoPI = (double)(2 * M_PI);
932 cwi = current;
933 cwi++;
934 if (cwi == edges.end()) {
935 std::advance(cwi, -((int)edges.size())); // set to edges.begin();
936 }
937 ccwi = current;
938 if (ccwi == edges.begin()) {
939 std::advance(ccwi, edges.size() - 1); // set to edges.end() - 1;
940 } else {
941 ccwi--;
942 }
943
944 const double angleCurCCW = geomsCCW[*current].angleAt2D(0);
945 const double angleCurCW = geomsCW[*current].angleAt2D(0);
946 const double angleCCW = geomsCW[*ccwi].angleAt2D(0);
947 const double angleCW = geomsCCW[*cwi].angleAt2D(0);
948 ccad = angleCCW - angleCurCCW;
949 while (ccad < 0.) {
950 ccad += twoPI;
951 }
952 cad = angleCurCW - angleCW;
953 while (cad < 0.) {
954 cad += twoPI;
955 }
956}
957
958
959
960const PositionVector
962#ifdef DEBUG_NODE_SHAPE
963 if (DEBUGCOND) {
964 std::cout << "computeNodeShapeSmall node=" << myNode.getID() << "\n";
965 }
966#endif
967 PositionVector ret;
968 for (NBEdge* e : myNode.getEdges()) {
969 // compute crossing with normal
970 PositionVector edgebound1 = e->getCCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
971 PositionVector edgebound2 = e->getCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
972 Position delta = edgebound1[1] - edgebound1[0];
973 delta.set(-delta.y(), delta.x()); // rotate 90 degrees
975 cross.extrapolate2D(500);
976 edgebound1.extrapolate2D(500);
977 edgebound2.extrapolate2D(500);
978 if (cross.intersects(edgebound1)) {
979 Position np = cross.intersectionPosition2D(edgebound1);
980 np.set(np.x(), np.y(), myNode.getPosition().z());
981 ret.push_back_noDoublePos(np);
982 }
983 if (cross.intersects(edgebound2)) {
984 Position np = cross.intersectionPosition2D(edgebound2);
985 np.set(np.x(), np.y(), myNode.getPosition().z());
986 ret.push_back_noDoublePos(np);
987 }
988 e->resetNodeBorder(&myNode);
989 }
990 return ret;
991}
992
993
994double
996 // look for incoming/outgoing edge pairs that do not go straight and allow wide vehicles
997 // (connection information is not available yet)
998 // @TODO compute the radius for each pair of neighboring edge intersections in computeNodeShapeDefault rather than use the maximum
999 const double radius = oc.getFloat("default.junctions.radius");
1000 const double smallRadius = oc.getFloat("junctions.small-radius");
1001 double maxRightAngle = 0; // rad
1002 double extraWidthRight = 0; // m
1003 double maxLeftAngle = 0; // rad
1004 double extraWidthLeft = 0; // m
1005 int laneDelta = 0;
1006 int totalWideLanesIn = 0;
1007 for (NBEdge* in : myNode.getIncomingEdges()) {
1008 int wideLanesIn = 0;
1009 for (int i = 0; i < in->getNumLanes(); i++) {
1010 if ((in->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1011 wideLanesIn++;
1012 }
1013 }
1014 totalWideLanesIn += wideLanesIn;
1015 for (NBEdge* out : myNode.getOutgoingEdges()) {
1016 if ((in->getPermissions() & out->getPermissions() & SVC_LARGE_TURN) != 0) {
1017 if (myNode.getDirection(in, out) == LinkDirection::TURN) {
1018 continue;
1019 };
1020 const double angle = GeomHelper::angleDiff(
1021 in->getGeometry().angleAt2D(-2),
1022 out->getGeometry().angleAt2D(0));
1023 if (angle < 0) {
1024 if (maxRightAngle < -angle) {
1025 maxRightAngle = -angle;
1026 extraWidthRight = MAX2(getExtraWidth(in, SVC_LARGE_TURN), getExtraWidth(out, SVC_LARGE_TURN));
1027 }
1028 } else {
1029 if (maxLeftAngle < angle) {
1030 maxLeftAngle = angle;
1031 // all edges clockwise between in and out count as extra width
1032 extraWidthLeft = 0;
1033 EdgeVector::const_iterator pIn = std::find(myNode.getEdges().begin(), myNode.getEdges().end(), in);
1035 while (*pIn != out) {
1036 extraWidthLeft += (*pIn)->getTotalWidth();
1037#ifdef DEBUG_RADIUS
1038 if (DEBUGCOND) {
1039 std::cout << " in=" << in->getID() << " out=" << out->getID() << " extra=" << (*pIn)->getID() << " extraWidthLeft=" << extraWidthLeft << "\n";
1040 }
1041#endif
1043 }
1044 }
1045 }
1046 int wideLanesOut = 0;
1047 for (int i = 0; i < out->getNumLanes(); i++) {
1048 if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1049 wideLanesOut++;
1050 }
1051 }
1052#ifdef DEBUG_RADIUS
1053 if (DEBUGCOND) {
1054 std::cout << " in=" << in->getID() << " out=" << out->getID() << " wideLanesIn=" << wideLanesIn << " wideLanesOut=" << wideLanesOut << "\n";
1055 }
1056#endif
1057 laneDelta = MAX2(laneDelta, abs(wideLanesOut - wideLanesIn));
1058 }
1059 }
1060 }
1061 // special case: on/off-ramp
1062 if (myNode.getOutgoingEdges().size() == 1 || myNode.getIncomingEdges().size() == 1) {
1063 int totalWideLanesOut = 0;
1064 for (NBEdge* out : myNode.getOutgoingEdges()) {
1065 for (int i = 0; i < out->getNumLanes(); i++) {
1066 if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1067 totalWideLanesOut++;
1068 }
1069 }
1070 }
1071 if (totalWideLanesIn == totalWideLanesOut) {
1072 // use total laneDelta instead of individual edge lane delta
1073 laneDelta = 0;
1074 }
1075 }
1076 // changing the number of wide-vehicle lanes on a straight segment requires a larger junction to allow for smooth driving
1077 // otherwise we can reduce the radius according to the angle
1078 double result = radius;
1079 // left turns are assumed to cross additional edges and thus du not determine the required radius in most cases
1080 double maxTurnAngle = maxRightAngle;
1081 double extraWidth = extraWidthRight;
1082 if (maxRightAngle < DEG2RAD(5)) {
1083 maxTurnAngle = maxLeftAngle;
1084 extraWidth = extraWidthLeft;
1085 }
1086 const double minRadius = maxTurnAngle >= DEG2RAD(30) ? MIN2(smallRadius, radius) : smallRadius;
1087 if (laneDelta == 0 || maxTurnAngle >= DEG2RAD(30) || myNode.isConstantWidthTransition()) {
1088 // subtract radius gained from extra lanes
1089 // do not increase radius for turns that are sharper than a right angle
1090 result = radius * tan(0.5 * MIN2(0.5 * M_PI, maxTurnAngle)) - extraWidth;
1091 }
1092 result = MAX2(minRadius, result);
1093#ifdef DEBUG_RADIUS
1094 if (DEBUGCOND) {
1095 std::cout << "getDefaultRadius n=" << myNode.getID()
1096 << " r=" << radius << " sr=" << smallRadius
1097 << " mr=" << minRadius
1098 << " laneDelta=" << laneDelta
1099 << " rightA=" << RAD2DEG(maxRightAngle)
1100 << " leftA=" << RAD2DEG(maxLeftAngle)
1101 << " maxA=" << RAD2DEG(maxTurnAngle)
1102 << " extraWidth=" << extraWidth
1103 << " result=" << result << "\n";
1104 }
1105#endif
1106 return result;
1107}
1108
1109
1110bool
1111NBNodeShapeComputer::isDivided(const NBEdge* e, std::set<NBEdge*, ComparatorIdLess> same, const PositionVector& ccw, const PositionVector& cw) const {
1112 if (same.size() < 2) {
1113 return false;
1114 }
1115 std::set<Position> endPoints;
1116 endPoints.insert(e->getEndpointAtNode(&myNode));
1117 for (NBEdge* s : same) {
1118 endPoints.insert(s->getEndpointAtNode(&myNode));
1119 }
1120 if (endPoints.size() > 1) {
1121 std::vector<double> distances = ccw.distances(cw, true);
1122 double width = e->getTotalWidth();
1123 for (const NBEdge* e2 : same) {
1124 width += e2->getTotalWidth();
1125 }
1126 const double maxDist = VectorHelper<double>::maxValue(distances);
1127 const double maxDivider = maxDist - width;
1128 return maxDivider >= 5;
1129 }
1130 return false;
1131}
1132
1133
1134double
1136 double result = 0;
1137 int lane = 0;
1138 while (lane < e->getNumLanes() && e->getPermissions(lane) == 0) {
1139 // ignore forbidden lanes out the outside
1140 lane++;
1141 }
1142 while (lane < e->getNumLanes() && (e->getPermissions(lane) & exclude) == 0) {
1143 result += e->getLaneWidth(lane);
1144 lane++;
1145 }
1146 return result;
1147}
1148
1149
1150double
1151NBNodeShapeComputer::divisionWidth(const NBEdge* e, std::set<NBEdge*, ComparatorIdLess> same, const Position& p, const Position& p2) {
1152 double result = p.distanceTo2D(p2);
1153 result -= e->getTotalWidth();
1154 for (NBEdge* e2 : same) {
1155 result -= e2->getTotalWidth();
1156 }
1157 return MAX2(0.0, result);
1158}
1159
1160/****************************************************************************/
#define DEG2RAD(x)
Definition GeomHelper.h:35
#define RAD2DEG(x)
Definition GeomHelper.h:36
#define DEBUGCOND(PED)
#define WRITE_WARNINGF(...)
Definition MsgHandler.h:287
#define WRITE_WARNING(msg)
Definition MsgHandler.h:286
#define TL(string)
Definition MsgHandler.h:304
std::set< NBEdge * > EdgeSet
container for unique edges
Definition NBCont.h:50
std::vector< NBEdge * > EdgeVector
container for (sorted) edges
Definition NBCont.h:42
#define EXT2
const SVCPermissions SVCAll
all VClasses are allowed
bool isRailway(SVCPermissions permissions)
Returns whether an edge with the given permissions is a (exclusive) 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:52
T MIN3(T a, T b, T c)
Definition StdDefs.h:93
T MIN2(T a, T b)
Definition StdDefs.h:80
T MAX2(T a, T b)
Definition StdDefs.h:86
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.
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:4540
double getLaneWidth() const
Returns the default width of lanes of this edge.
Definition NBEdge.h:648
NBNode * getToNode() const
Returns the destination node of the edge.
Definition NBEdge.h:552
const PositionVector & getGeometry() const
Returns the geometry of the edge.
Definition NBEdge.h:789
LaneSpreadFunction getLaneSpreadFunction() const
Returns how this edge's lanes' lateral offset is computed.
Definition NBEdge.cpp:1021
Position getEndpointAtNode(const NBNode *node) const
Definition NBEdge.cpp:643
double getTotalWidth() const
Returns the combined width of all lanes of this edge.
Definition NBEdge.cpp:4378
NBNode * getFromNode() const
Returns the origin node of the edge.
Definition NBEdge.h:545
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:632
static const double UNSPECIFIED_WIDTH
unspecified lane width
Definition NBEdge.h:346
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:2496
bool hasIncoming(const NBEdge *const e) const
Returns whether the given edge ends at this node.
Definition NBNode.cpp:1992
double getDisplacementError() const
compute the displacement error during s-curve computation
Definition NBNode.h:651
bool isSimpleContinuation(bool checkLaneNumbers=true, bool checkWidth=false) const
check if node is a simple continuation
Definition NBNode.cpp:551
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 & getIncomingEdges() const
Returns this node's incoming edges (The edges which yield in this node)
Definition NBNode.h:268
const EdgeVector & getOutgoingEdges() const
Returns this node's outgoing edges (The edges which start at this node)
Definition NBNode.h:273
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:586
std::vector< Crossing * > getCrossings() const
return this junctions pedestrian crossings
Definition NBNode.cpp:3096
bool isConstantWidthTransition() const
detects whether a given junction splits or merges lanes while keeping constant road width
Definition NBNode.cpp:919
static const int AVOID_WIDE_LEFT_TURN
Definition NBNode.h:224
const Position & getPosition() const
Definition NBNode.h:260
const EdgeVector & getEdges() const
Returns all edges which participate in this node (Edges that start or end at this node)
Definition NBNode.h:278
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:4081
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
bool needsLargeTurn(NBEdge *e1, NBEdge *e2, std::map< NBEdge *, std::set< NBEdge *, ComparatorIdLess > > &same) const
whether the given edges (along with those in the same direction) requires a large turning radius
const PositionVector computeNodeShapeSmall()
Computes the node geometry using normals.
double myRadius
the computed node radius
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
void joinSameDirectionEdges(const EdgeVector &edges, std::map< NBEdge *, std::set< NBEdge *, ComparatorIdLess > > &same, bool useEndpoints)
Joins edges and computes ccw/cw boundaries.
std::map< NBEdge *, PositionVector > GeomsMap
NBNodeShapeComputer(const NBNode &node)
Constructor.
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.
bool isDivided(const NBEdge *e, std::set< NBEdge *, ComparatorIdLess > same, const PositionVector &ccw, const PositionVector &cw) const
static double divisionWidth(const NBEdge *e, std::set< NBEdge *, ComparatorIdLess > same, const Position &p, const Position &p2)
compute the width of the divider space for divided roads
const NBNode & myNode
The node to compute the geometry for.
EdgeVector computeUniqueDirectionList(const EdgeVector &all, std::map< NBEdge *, std::set< NBEdge *, ComparatorIdLess > > &same, GeomsMap &geomsCCW, GeomsMap &geomsCW)
Joins edges.
void computeSameEnd(PositionVector &l1, PositionVector &l2)
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.
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.
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:82
static const Position INVALID
used to indicate that a position is valid
Definition Position.h:323
double distanceTo2D(const Position &p2) const
returns the euclidean distance in the x-y-plane
Definition Position.h:273
double x() const
Returns the x-position.
Definition Position.h:52
void add(const Position &pos)
Adds the given position to this one.
Definition Position.h:129
void setz(double z)
set position z
Definition Position.h:77
void mul(double val)
Multiplies position with the given value.
Definition Position.h:102
double z() const
Returns the z-position.
Definition Position.h:62
double y() const
Returns the y-position.
Definition Position.h:57
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)
Position positionAtOffset2D(double pos, double lateralOffset=0, bool extrapolateBeyond=false) const
Returns the position at the given length.
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
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)
static T minValue(const std::vector< T > &v)
#define M_PI
Definition odrSpiral.cpp:45