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-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>
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*> > 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*> >& 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 }
694}
695
696void
697NBNodeShapeComputer::joinSameDirectionEdges(const EdgeVector& edges, std::map<NBEdge*, std::set<NBEdge*> >& same, bool useEndpoints) {
698 // compute same (edges where an intersection doesn't work well
699 // (always check an edge and its cw neighbor)
700 const double angleChangeLookahead = 35; // distance to look ahead for a misleading angle
701 const bool isXodr = OptionsCont::getOptions().exists("opendrive-files") && OptionsCont::getOptions().isSet("opendrive-files");
702 EdgeSet foundOpposite;
703 for (EdgeVector::const_iterator i = edges.begin(); i != edges.end(); i++) {
704 EdgeVector::const_iterator j;
705 if (i == edges.end() - 1) {
706 j = edges.begin();
707 } else {
708 j = i + 1;
709 }
710 if (useEndpoints
711 && !(*i)->hasDefaultGeometryEndpointAtNode(&myNode)
712 && !(*j)->hasDefaultGeometryEndpointAtNode(&myNode)) {
713 continue;
714 }
715 const bool incoming = (*i)->getToNode() == &myNode;
716 const bool incoming2 = (*j)->getToNode() == &myNode;
717 const bool differentDirs = (incoming != incoming2);
718 const bool sameGeom = (*i)->getGeometry() == (differentDirs ? (*j)->getGeometry().reverse() : (*j)->getGeometry());
719 const PositionVector g1 = incoming ? (*i)->getCCWBoundaryLine(myNode) : (*i)->getCWBoundaryLine(myNode);
720 const PositionVector g2 = incoming ? (*j)->getCCWBoundaryLine(myNode) : (*j)->getCWBoundaryLine(myNode);
721 const double angle1further = (g1.size() > 2 && g1[0].distanceTo2D(g1[1]) < angleChangeLookahead ?
722 g1.angleAt2D(1) : g1.angleAt2D(0));
723 const double angle2further = (g2.size() > 2 && g2[0].distanceTo2D(g2[1]) < angleChangeLookahead ?
724 g2.angleAt2D(1) : g2.angleAt2D(0));
725 const double angleDiff = GeomHelper::angleDiff(g1.angleAt2D(0), g2.angleAt2D(0));
726 const double angleDiffFurther = GeomHelper::angleDiff(angle1further, angle2further);
727 const bool ambiguousGeometry = ((angleDiff > 0 && angleDiffFurther < 0) || (angleDiff < 0 && angleDiffFurther > 0));
728 //if (ambiguousGeometry) {
729 // @todo: this warning would be helpful in many cases. However, if angle and angleFurther jump between 179 and -179 it is misleading
730 // WRITE_WARNINGF(TL("Ambiguous angles at junction '%' for edges '%' and '%'."), myNode.getID(), (*i)->getID(), (*j)->getID());
731 //}
732#ifdef DEBUG_NODE_SHAPE
733 if (DEBUGCOND) {
734 std::cout << " checkSameDirection " << (*i)->getID() << " " << (*j)->getID()
735 << " diffDirs=" << differentDirs
736 << " isOpposite=" << (differentDirs && foundOpposite.count(*i) == 0)
737 << " angleDiff=" << angleDiff
738 << " ambiguousGeometry=" << ambiguousGeometry
739 << " badInsersection=" << badIntersection(*i, *j, EXT)
740 << "\n";
741
742 }
743#endif
744 if (sameGeom || fabs(angleDiff) < DEG2RAD(20)) {
745 const bool isOpposite = differentDirs && foundOpposite.count(*i) == 0;
746 if (isOpposite) {
747 foundOpposite.insert(*i);
748 foundOpposite.insert(*j);
749 }
750 if (isOpposite || ambiguousGeometry || (!isXodr && badIntersection(*i, *j, EXT))) {
751 // maintain equivalence relation for all members of the equivalence class
752 for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
753 if (*j != *k) {
754 same[*k].insert(*j);
755 same[*j].insert(*k);
756 }
757 }
758 for (std::set<NBEdge*>::iterator k = same[*j].begin(); k != same[*j].end(); ++k) {
759 if (*i != *k) {
760 same[*k].insert(*i);
761 same[*i].insert(*k);
762 }
763 }
764 same[*i].insert(*j);
765 same[*j].insert(*i);
766#ifdef DEBUG_NODE_SHAPE
767 if (DEBUGCOND) {
768 std::cout << " joinedSameDirectionEdges " << (*i)->getID() << " " << (*j)->getID() << " isOpposite=" << isOpposite << " ambiguousGeometry=" << ambiguousGeometry << "\n";
769 }
770#endif
771 }
772 }
773 }
774}
775
776
777bool
778NBNodeShapeComputer::badIntersection(const NBEdge* e1, const NBEdge* e2, double distance) {
779 // check whether the two edges are on top of each other. In that case they should be joined
780 // also, if they never touch along their common length
781 const double commonLength = MIN3(distance, e1->getGeometry().length(), e2->getGeometry().length());
782 PositionVector geom1 = e1->getGeometry();
783 PositionVector geom2 = e2->getGeometry();
784 // shift to make geom the centerline of the edge regardless of spreadtype
786 geom1.move2side(e1->getTotalWidth() / 2);
787 }
789 geom2.move2side(e2->getTotalWidth() / 2);
790 }
791 // always let geometry start at myNode
792 if (e1->getToNode() == &myNode) {
793 geom1 = geom1.reverse();
794 }
795 if (e2->getToNode() == &myNode) {
796 geom2 = geom2.reverse();
797 }
798 geom1 = geom1.getSubpart2D(0, commonLength);
799 geom2 = geom2.getSubpart2D(0, commonLength);
800 double endAngleDiff = 0;
801 if (geom1.size() >= 2 && geom2.size() >= 2) {
802 endAngleDiff = fabs(RAD2DEG(GeomHelper::angleDiff(
803 geom1.angleAt2D((int)geom1.size() - 2),
804 geom2.angleAt2D((int)geom2.size() - 2))));
805 }
806 const double minDistanceThreshold = (e1->getTotalWidth() + e2->getTotalWidth()) / 2 + POSITION_EPS;
807 std::vector<double> distances = geom1.distances(geom2, true);
808 std::vector<double> distances2 = geom1.distances(geom2);
809 const double minDist = VectorHelper<double>::minValue(distances2);
810 const double maxDist = VectorHelper<double>::maxValue(distances);
811 const bool curvingTowards = geom1[0].distanceTo2D(geom2[0]) > minDistanceThreshold && minDist < minDistanceThreshold;
812 const bool onTop = (maxDist - POSITION_EPS < minDistanceThreshold) && endAngleDiff < 30;
814 const bool neverTouch = minDist > minDistanceThreshold * 2 && !bothDefault;
815 geom1.extrapolate2D(EXT);
816 geom2.extrapolate2D(EXT);
817 Position intersect = geom1.intersectionPosition2D(geom2);
818 const bool intersects = intersect != Position::INVALID && geom1.distance2D(intersect) < POSITION_EPS;
819#ifdef DEBUG_NODE_SHAPE
820 if (DEBUGCOND) {
821 std::cout << " badIntersect: onTop=" << onTop << " curveTo=" << curvingTowards << " intersects=" << intersects
822 << " endAngleDiff=" << endAngleDiff
823 << " geom1=" << geom1 << " geom2=" << geom2
824 << " distances=" << toString(distances) << " minDist=" << minDist << " maxDist=" << maxDist << " thresh=" << minDistanceThreshold
825 << " neverTouch=" << neverTouch
826 << " intersectPos=" << intersect
827 << "\n";
828 }
829#endif
830 return onTop || curvingTowards || !intersects || neverTouch;
831}
832
833
836 const EdgeVector& all,
837 std::map<NBEdge*, std::set<NBEdge*> >& same,
838 GeomsMap& geomsCCW,
839 GeomsMap& geomsCW) {
840 // store relationships
841 EdgeVector newAll = all;
842 for (NBEdge* e1 : all) {
843 // determine which of the edges marks the outer boundary
844 auto e2NewAll = std::find(newAll.begin(), newAll.end(), e1);
845#ifdef DEBUG_NODE_SHAPE
846 if (DEBUGCOND) std::cout << "computeUniqueDirectionList e1=" << e1->getID()
847 << " deleted=" << (e2NewAll == newAll.end())
848 << " same=" << joinNamedToStringSorting(same[e1], ',') << "\n";
849#endif
850 if (e2NewAll == newAll.end()) {
851 continue;
852 }
853 auto e1It = std::find(all.begin(), all.end(), e1);
854 auto bestCCW = e1It;
855 auto bestCW = e1It;
856 bool changed = true;
857 while (changed) {
858 changed = false;
859 for (NBEdge* e2 : same[e1]) {
860#ifdef DEBUG_NODE_SHAPE
861 if (DEBUGCOND) {
862 std::cout << " e2=" << e2->getID() << "\n";
863 }
864#endif
865 auto e2It = std::find(all.begin(), all.end(), e2);
866 if (e2It + 1 == bestCCW || (e2It == (all.end() - 1) && bestCCW == all.begin())) {
867 bestCCW = e2It;
868 changed = true;
869#ifdef DEBUG_NODE_SHAPE
870 if (DEBUGCOND) {
871 std::cout << " bestCCW=" << e2->getID() << "\n";
872 }
873#endif
874 } else if (bestCW + 1 == e2It || (bestCW == (all.end() - 1) && e2It == all.begin())) {
875 bestCW = e2It;
876 changed = true;
877#ifdef DEBUG_NODE_SHAPE
878 if (DEBUGCOND) {
879 std::cout << " bestCW=" << e2->getID() << "\n";
880 }
881#endif
882 }
883 }
884 }
885 if (bestCW != e1It) {
886 geomsCW[e1] = geomsCW[*bestCW];
887 computeSameEnd(geomsCW[e1], geomsCCW[e1]);
888 }
889 if (bestCCW != e1It) {
890 geomsCCW[e1] = geomsCCW[*bestCCW];
891 computeSameEnd(geomsCW[e1], geomsCCW[e1]);
892 }
893 // clean up
894 for (NBEdge* e2 : same[e1]) {
895 auto e2NewAllIt = std::find(newAll.begin(), newAll.end(), e2);
896 if (e2NewAllIt != newAll.end()) {
897 newAll.erase(e2NewAllIt);
898 }
899 }
900 }
901#ifdef DEBUG_NODE_SHAPE
902 if (DEBUGCOND) {
903 std::cout << " newAll:\n";
904 for (NBEdge* e : newAll) {
905 std::cout << " " << e->getID() << " geomCCW=" << geomsCCW[e] << " geomsCW=" << geomsCW[e] << "\n";
906 }
907 }
908#endif
909 return newAll;
910}
911
912
913void
914NBNodeShapeComputer::initNeighbors(const EdgeVector& edges, const EdgeVector::const_iterator& current,
915 GeomsMap& geomsCW,
916 GeomsMap& geomsCCW,
917 EdgeVector::const_iterator& cwi,
918 EdgeVector::const_iterator& ccwi,
919 double& cad,
920 double& ccad) {
921 const double twoPI = (double)(2 * M_PI);
922 cwi = current;
923 cwi++;
924 if (cwi == edges.end()) {
925 std::advance(cwi, -((int)edges.size())); // set to edges.begin();
926 }
927 ccwi = current;
928 if (ccwi == edges.begin()) {
929 std::advance(ccwi, edges.size() - 1); // set to edges.end() - 1;
930 } else {
931 ccwi--;
932 }
933
934 const double angleCurCCW = geomsCCW[*current].angleAt2D(0);
935 const double angleCurCW = geomsCW[*current].angleAt2D(0);
936 const double angleCCW = geomsCW[*ccwi].angleAt2D(0);
937 const double angleCW = geomsCCW[*cwi].angleAt2D(0);
938 ccad = angleCCW - angleCurCCW;
939 while (ccad < 0.) {
940 ccad += twoPI;
941 }
942 cad = angleCurCW - angleCW;
943 while (cad < 0.) {
944 cad += twoPI;
945 }
946}
947
948
949
950const PositionVector
952#ifdef DEBUG_NODE_SHAPE
953 if (DEBUGCOND) {
954 std::cout << "computeNodeShapeSmall node=" << myNode.getID() << "\n";
955 }
956#endif
957 PositionVector ret;
958 for (NBEdge* e : myNode.getEdges()) {
959 // compute crossing with normal
960 PositionVector edgebound1 = e->getCCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
961 PositionVector edgebound2 = e->getCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
962 Position delta = edgebound1[1] - edgebound1[0];
963 delta.set(-delta.y(), delta.x()); // rotate 90 degrees
965 cross.extrapolate2D(500);
966 edgebound1.extrapolate2D(500);
967 edgebound2.extrapolate2D(500);
968 if (cross.intersects(edgebound1)) {
969 Position np = cross.intersectionPosition2D(edgebound1);
970 np.set(np.x(), np.y(), myNode.getPosition().z());
971 ret.push_back_noDoublePos(np);
972 }
973 if (cross.intersects(edgebound2)) {
974 Position np = cross.intersectionPosition2D(edgebound2);
975 np.set(np.x(), np.y(), myNode.getPosition().z());
976 ret.push_back_noDoublePos(np);
977 }
978 e->resetNodeBorder(&myNode);
979 }
980 return ret;
981}
982
983
984double
986 // look for incoming/outgoing edge pairs that do not go straight and allow wide vehicles
987 // (connection information is not available yet)
988 // @TODO compute the radius for each pair of neighboring edge intersections in computeNodeShapeDefault rather than use the maximum
989 const double radius = oc.getFloat("default.junctions.radius");
990 const double smallRadius = oc.getFloat("junctions.small-radius");
991 double maxRightAngle = 0; // rad
992 double extraWidthRight = 0; // m
993 double maxLeftAngle = 0; // rad
994 double extraWidthLeft = 0; // m
995 int laneDelta = 0;
996 int totalWideLanesIn = 0;
997 for (NBEdge* in : myNode.getIncomingEdges()) {
998 int wideLanesIn = 0;
999 for (int i = 0; i < in->getNumLanes(); i++) {
1000 if ((in->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1001 wideLanesIn++;
1002 }
1003 }
1004 totalWideLanesIn += wideLanesIn;
1005 for (NBEdge* out : myNode.getOutgoingEdges()) {
1006 if ((in->getPermissions() & out->getPermissions() & SVC_LARGE_TURN) != 0) {
1007 if (myNode.getDirection(in, out) == LinkDirection::TURN) {
1008 continue;
1009 };
1010 const double angle = GeomHelper::angleDiff(
1011 in->getGeometry().angleAt2D(-2),
1012 out->getGeometry().angleAt2D(0));
1013 if (angle < 0) {
1014 if (maxRightAngle < -angle) {
1015 maxRightAngle = -angle;
1016 extraWidthRight = MAX2(getExtraWidth(in, SVC_LARGE_TURN), getExtraWidth(out, SVC_LARGE_TURN));
1017 }
1018 } else {
1019 if (maxLeftAngle < angle) {
1020 maxLeftAngle = angle;
1021 // all edges clockwise between in and out count as extra width
1022 extraWidthLeft = 0;
1023 EdgeVector::const_iterator pIn = std::find(myNode.getEdges().begin(), myNode.getEdges().end(), in);
1025 while (*pIn != out) {
1026 extraWidthLeft += (*pIn)->getTotalWidth();
1027#ifdef DEBUG_RADIUS
1028 if (DEBUGCOND) {
1029 std::cout << " in=" << in->getID() << " out=" << out->getID() << " extra=" << (*pIn)->getID() << " extraWidthLeft=" << extraWidthLeft << "\n";
1030 }
1031#endif
1033 }
1034 }
1035 }
1036 int wideLanesOut = 0;
1037 for (int i = 0; i < out->getNumLanes(); i++) {
1038 if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1039 wideLanesOut++;
1040 }
1041 }
1042#ifdef DEBUG_RADIUS
1043 if (DEBUGCOND) {
1044 std::cout << " in=" << in->getID() << " out=" << out->getID() << " wideLanesIn=" << wideLanesIn << " wideLanesOut=" << wideLanesOut << "\n";
1045 }
1046#endif
1047 laneDelta = MAX2(laneDelta, abs(wideLanesOut - wideLanesIn));
1048 }
1049 }
1050 }
1051 // special case: on/off-ramp
1052 if (myNode.getOutgoingEdges().size() == 1 || myNode.getIncomingEdges().size() == 1) {
1053 int totalWideLanesOut = 0;
1054 for (NBEdge* out : myNode.getOutgoingEdges()) {
1055 for (int i = 0; i < out->getNumLanes(); i++) {
1056 if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1057 totalWideLanesOut++;
1058 }
1059 }
1060 }
1061 if (totalWideLanesIn == totalWideLanesOut) {
1062 // use total laneDelta instead of individual edge lane delta
1063 laneDelta = 0;
1064 }
1065 }
1066 // changing the number of wide-vehicle lanes on a straight segment requires a larger junction to allow for smooth driving
1067 // otherwise we can reduce the radius according to the angle
1068 double result = radius;
1069 // left turns are assumed to cross additional edges and thus du not determine the required radius in most cases
1070 double maxTurnAngle = maxRightAngle;
1071 double extraWidth = extraWidthRight;
1072 if (maxRightAngle < DEG2RAD(5)) {
1073 maxTurnAngle = maxLeftAngle;
1074 extraWidth = extraWidthLeft;
1075 }
1076 const double minRadius = maxTurnAngle >= DEG2RAD(30) ? MIN2(smallRadius, radius) : smallRadius;
1077 if (laneDelta == 0 || maxTurnAngle >= DEG2RAD(30) || myNode.isConstantWidthTransition()) {
1078 // subtract radius gained from extra lanes
1079 // do not increase radius for turns that are sharper than a right angle
1080 result = radius * tan(0.5 * MIN2(0.5 * M_PI, maxTurnAngle)) - extraWidth;
1081 }
1082 result = MAX2(minRadius, result);
1083#ifdef DEBUG_RADIUS
1084 if (DEBUGCOND) {
1085 std::cout << "getDefaultRadius n=" << myNode.getID()
1086 << " r=" << radius << " sr=" << smallRadius
1087 << " mr=" << minRadius
1088 << " laneDelta=" << laneDelta
1089 << " rightA=" << RAD2DEG(maxRightAngle)
1090 << " leftA=" << RAD2DEG(maxLeftAngle)
1091 << " maxA=" << RAD2DEG(maxTurnAngle)
1092 << " extraWidth=" << extraWidth
1093 << " result=" << result << "\n";
1094 }
1095#endif
1096 return result;
1097}
1098
1099
1100bool
1101NBNodeShapeComputer::isDivided(const NBEdge* e, std::set<NBEdge*> same, const PositionVector& ccw, const PositionVector& cw) const {
1102 if (same.size() < 2) {
1103 return false;
1104 }
1105 std::set<Position> endPoints;
1106 endPoints.insert(e->getEndpointAtNode(&myNode));
1107 for (NBEdge* s : same) {
1108 endPoints.insert(s->getEndpointAtNode(&myNode));
1109 }
1110 if (endPoints.size() > 1) {
1111 std::vector<double> distances = ccw.distances(cw, true);
1112 double width = e->getTotalWidth();
1113 for (const NBEdge* e2 : same) {
1114 width += e2->getTotalWidth();
1115 }
1116 const double maxDist = VectorHelper<double>::maxValue(distances);
1117 const double maxDivider = maxDist - width;
1118 return maxDivider >= 5;
1119 }
1120 return false;
1121}
1122
1123
1124double
1126 double result = 0;
1127 int lane = 0;
1128 while (lane < e->getNumLanes() && e->getPermissions(lane) == 0) {
1129 // ignore forbidden lanes out the outside
1130 lane++;
1131 }
1132 while (lane < e->getNumLanes() && (e->getPermissions(lane) & exclude) == 0) {
1133 result += e->getLaneWidth(lane);
1134 lane++;
1135 }
1136 return result;
1137}
1138
1139
1140double
1141NBNodeShapeComputer::divisionWidth(const NBEdge* e, std::set<NBEdge*> same, const Position& p, const Position& p2) {
1142 double result = p.distanceTo2D(p2);
1143 result -= e->getTotalWidth();
1144 for (NBEdge* e2 : same) {
1145 result -= e2->getTotalWidth();
1146 }
1147 return MAX2(0.0, result);
1148}
1149
1150/****************************************************************************/
#define DEG2RAD(x)
Definition GeomHelper.h:35
#define RAD2DEG(x)
Definition GeomHelper.h:36
#define DEBUGCOND(PED)
#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:42
#define EXT2
const SVCPermissions SVCAll
all VClasses are allowed
bool isRailway(SVCPermissions permissions)
Returns whether an edge with the given permissions is a railway edge.
long long int SVCPermissions
bitset where each bit declares whether a certain SVC may use this edge/lane
@ SVC_RAIL_CLASSES
classes which drive on tracks
@ SVC_BICYCLE
vehicle is a bicycle
@ SVC_DELIVERY
vehicle is a small delivery vehicle
@ SVC_PEDESTRIAN
pedestrian
@ TURN
The link is a 180 degree turn.
const double SUMO_const_laneWidth
Definition StdDefs.h:48
T MIN3(T a, T b, T c)
Definition StdDefs.h:89
T MIN2(T a, T b)
Definition StdDefs.h:76
T MAX2(T a, T b)
Definition StdDefs.h:82
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
Definition ToString.h:46
std::string joinNamedToStringSorting(const std::set< T * > &ns, const T_BETWEEN &between)
Definition ToString.h:307
static double angleDiff(const double angle1, const double angle2)
Returns the difference of the second angle to the first angle in radiants.
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:4379
double getLaneWidth() const
Returns the default width of lanes of this edge.
Definition NBEdge.h:642
NBNode * getToNode() const
Returns the destination node of the edge.
Definition NBEdge.h:546
const PositionVector & getGeometry() const
Returns the geometry of the edge.
Definition NBEdge.h:783
LaneSpreadFunction getLaneSpreadFunction() const
Returns how this edge's lanes' lateral offset is computed.
Definition NBEdge.cpp:998
Position getEndpointAtNode(const NBNode *node) const
Definition NBEdge.cpp:620
double getTotalWidth() const
Returns the combined width of all lanes of this edge.
Definition NBEdge.cpp:4217
NBNode * getFromNode() const
Returns the origin node of the edge.
Definition NBEdge.h:539
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:609
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:2409
bool hasIncoming(const NBEdge *const e) const
Returns whether the given edge ends at this node.
Definition NBNode.cpp:1907
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 & 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:545
std::vector< Crossing * > getCrossings() const
return this junctions pedestrian crossings
Definition NBNode.cpp:2982
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
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:3882
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.
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.
A point in 2D or 3D with translation and scaling methods.
Definition Position.h:37
void set(double x, double y)
set positions x and y
Definition Position.h:85
static const Position INVALID
used to indicate that a position is valid
Definition Position.h:322
double distanceTo2D(const Position &p2) const
returns the euclidean distance in the x-y-plane
Definition Position.h:276
double x() const
Returns the x-position.
Definition Position.h:55
void add(const Position &pos)
Adds the given position to this one.
Definition Position.h:132
void setz(double z)
set position z
Definition Position.h:80
void mul(double val)
Multiplies position with the given value.
Definition Position.h:105
double z() const
Returns the z-position.
Definition Position.h:65
double y() const
Returns the y-position.
Definition Position.h:60
A list of positions.
double length2D() const
Returns the length.
void append(const PositionVector &v, double sameThreshold=2.0)
double length() const
Returns the length.
Position intersectionPosition2D(const Position &p1, const Position &p2, const double withinDist=0.) const
Returns the position of the intersection.
void push_front_noDoublePos(const Position &p)
insert in front a non double position
void add(double xoff, double yoff, double zoff)
std::vector< double > intersectsAtLengths2D(const PositionVector &other) const
For all intersections between this vector and other, return the 2D-length of the subvector from this ...
double distance2D(const Position &p, bool perpendicular=false) const
closest 2D-distance to point p (or -1 if perpendicular is true and the point is beyond this vector)
double nearest_offset_to_point2D(const Position &p, bool perpendicular=true) const
return the nearest offest to point 2D
std::vector< double > distances(const PositionVector &s, bool perpendicular=false) const
distances of all my points to s and all of s points to myself
std::pair< PositionVector, PositionVector > splitAt(double where, bool use2D=false) const
Returns the two lists made when this list vector is splitted at the given point.
void move2side(double amount, double maxExtension=100)
move position vector to side using certain amount
PositionVector getSubpart2D(double beginOffset, double endOffset) const
get subpart of a position vector in two dimensions (Z is ignored)
PositionVector interpolateZ(double zStart, double zEnd) const
returned vector that varies z smoothly over its length
double angleAt2D(int pos) const
get angle in certain position of position vector (in radians between -M_PI and M_PI)
void extrapolate2D(const double val, const bool onlyFirst=false)
extrapolate position vector in two dimensions (Z is ignored)
void push_back_noDoublePos(const Position &p)
insert in back a non double position
bool intersects(const Position &p1, const Position &p2) const
Returns the information whether this list of points interesects the given line.
PositionVector reverse() const
reverse position vector
PositionVector getSubpartByIndex(int beginIndex, int count) const
get subpart of a position vector using index and a cout
Position positionAtOffset2D(double pos, double lateralOffset=0) const
Returns the position at the given length.
void sub(const Position &offset)
PositionVector getSubpart(double beginOffset, double endOffset) const
get subpart of a position vector
static T maxValue(const std::vector< T > &v)
static T minValue(const std::vector< T > &v)
#define M_PI
Definition odrSpiral.cpp:45