Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netbuild/NBNodeShapeComputer.cpp
169665 views
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
/****************************************************************************/
14
/// @file NBNodeShapeComputer.cpp
15
/// @author Daniel Krajzewicz
16
/// @author Jakob Erdmann
17
/// @author Michael Behrisch
18
/// @date Sept 2002
19
///
20
// This class computes shapes of junctions
21
/****************************************************************************/
22
#include <config.h>
23
24
#include <algorithm>
25
#include <iterator>
26
#include <utils/geom/PositionVector.h>
27
#include <utils/options/OptionsCont.h>
28
#include <utils/geom/GeomHelper.h>
29
#include <utils/common/StdDefs.h>
30
#include <utils/common/MsgHandler.h>
31
#include <utils/common/UtilExceptions.h>
32
#include <utils/common/ToString.h>
33
#include <utils/iodevices/OutputDevice.h>
34
#include "NBNode.h"
35
#include "NBAlgorithms.h"
36
#include "NBNodeShapeComputer.h"
37
38
//#define DEBUG_NODE_SHAPE
39
//#define DEBUG_SMOOTH_CORNERS
40
//#define DEBUG_RADIUS
41
#define DEBUGCOND (myNode.getID() == "C")
42
43
44
#define EXT2 10.0
45
46
// foot and bicycle paths as well as pure service roads should not get large junctions
47
// railways also do have have junctions with sharp turns so can be excluded
48
const SVCPermissions NBNodeShapeComputer::SVC_LARGE_TURN(
49
SVCAll & ~(SVC_BICYCLE | SVC_PEDESTRIAN | SVC_DELIVERY | SVC_RAIL_CLASSES));
50
51
// ===========================================================================
52
// method definitions
53
// ===========================================================================
54
NBNodeShapeComputer::NBNodeShapeComputer(const NBNode& node) :
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
65
NBNodeShapeComputer::~NBNodeShapeComputer() {}
66
67
68
const PositionVector
69
NBNodeShapeComputer::compute(bool forceSmall) {
70
#ifdef DEBUG_NODE_SHAPE
71
if (DEBUGCOND) {
72
// annotate edges edges to make their ordering visible
73
int i = 0;
74
for (NBEdge* e : myNode.getEdges()) {
75
e->setStreetName(toString(i));
76
i++;
77
}
78
}
79
#endif
80
// check whether the node is a dead end node or a node where only turning is possible
81
// in this case, we will use "computeNodeShapeSmall"
82
if (myNode.getEdges().size() == 1 || forceSmall) {
83
return computeNodeShapeSmall();
84
}
85
if (myNode.getEdges().size() == 2 && myNode.getIncomingEdges().size() == 1) {
86
if (myNode.getIncomingEdges()[0]->isTurningDirectionAt(myNode.getOutgoingEdges()[0])) {
87
return computeNodeShapeSmall();
88
}
89
}
90
const bool geometryLike = myNode.isSimpleContinuation(true, true);
91
const PositionVector& ret = computeNodeShapeDefault(geometryLike);
92
// fail fall-back: use "computeNodeShapeSmall"
93
if (ret.size() < 3) {
94
return computeNodeShapeSmall();
95
}
96
return ret;
97
}
98
99
100
void
101
NBNodeShapeComputer::computeSameEnd(PositionVector& l1, PositionVector& l2) {
102
assert(l1[0].distanceTo2D(l1[1]) >= EXT);
103
assert(l2[0].distanceTo2D(l2[1]) >= EXT);
104
PositionVector tmp;
105
tmp.push_back(PositionVector::positionAtOffset2D(l1[0], l1[1], EXT));
106
tmp.push_back(l1[1]);
107
tmp[1].sub(tmp[0]);
108
tmp[1].set(-tmp[1].y(), tmp[1].x());
109
tmp[1].add(tmp[0]);
110
tmp.extrapolate2D(EXT);
111
if (l2.intersects(tmp[0], tmp[1])) {
112
const double offset = l2.intersectsAtLengths2D(tmp)[0];
113
if (l2.length2D() - offset > POSITION_EPS) {
114
PositionVector tl2 = l2.getSubpart2D(offset, l2.length2D());
115
tl2.extrapolate2D(EXT);
116
l2.erase(l2.begin(), l2.begin() + (l2.size() - tl2.size()));
117
l2[0] = tl2[0];
118
}
119
}
120
}
121
122
123
const PositionVector
124
NBNodeShapeComputer::computeNodeShapeDefault(bool simpleContinuation) {
125
// if we have less than two edges, we can not compute the node's shape this way
126
if (myNode.getEdges().size() < 2) {
127
return PositionVector();
128
}
129
// magic values
130
const OptionsCont& oc = OptionsCont::getOptions();
131
const double defaultRadius = getDefaultRadius(oc);
132
const bool useDefaultRadius = myNode.getRadius() == NBNode::UNSPECIFIED_RADIUS || myNode.getRadius() == defaultRadius;
133
myRadius = (useDefaultRadius ? defaultRadius : myNode.getRadius());
134
double smallRadius = useDefaultRadius ? oc.getFloat("junctions.small-radius") : myRadius;
135
const int cornerDetail = oc.getInt("junctions.corner-detail");
136
const double sCurveStretch = oc.getFloat("junctions.scurve-stretch");
137
const bool useEndpoints = oc.getBool("junctions.endpoint-shape");
138
const bool rectangularCut = oc.getBool("rectangular-lane-cut");
139
const bool openDriveOutput = oc.isSet("opendrive-output");
140
141
// Extend geometries to move the stop line forward.
142
// In OpenDrive the junction starts whenever the geometry changes. Stop
143
// line information is not given or ambiguous (sign positions at most)
144
// In SUMO, stop lines are where the junction starts. This is computed
145
// heuristically from intersecting the junctions roads geometries.
146
const double advanceStopLine = oc.exists("opendrive-files") && oc.isSet("opendrive-files") ? oc.getFloat("opendrive.advance-stopline") : 0;
147
148
149
#ifdef DEBUG_NODE_SHAPE
150
if (DEBUGCOND) {
151
std::cout << "\ncomputeNodeShapeDefault node " << myNode.getID() << " simple=" << simpleContinuation << " useDefaultRadius=" << useDefaultRadius << " radius=" << myRadius << "\n";
152
}
153
#endif
154
155
// initialise
156
EdgeVector::const_iterator i;
157
// edges located in the value-vector have the same direction as the key edge
158
std::map<NBEdge*, std::set<NBEdge*, 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)) {
241
g.push_back_noDoublePos(p);
242
} else {
243
g.push_front_noDoublePos(p);
244
}
245
(*i)->setGeometry(g);
246
// and rebuild previous information
247
geomsCCW[*i] = (*i)->getCCWBoundaryLine(myNode);
248
geomsCCW[*i].extrapolate(EXT);
249
geomsCW[*i] = (*i)->getCWBoundaryLine(myNode);
250
geomsCW[*i].extrapolate(EXT);
251
// the distance is now = zero (the point we have appended)
252
distances[*i] = EXT;
253
myExtended[*i] = true;
254
#ifdef DEBUG_NODE_SHAPE
255
if (DEBUGCOND) {
256
std::cout << " extending (dist=" << dist << ")\n";
257
}
258
#endif
259
} else {
260
if (!simpleContinuation) {
261
dist += myRadius;
262
} else {
263
// if the angles change, junction should have some size to avoid degenerate shape
264
double radius2 = fabs(ccad - cad) * (*i)->getNumLanes();
265
if (radius2 > NUMERICAL_EPS || openDriveOutput) {
266
radius2 = MAX2(0.15, radius2);
267
}
268
if (myNode.getCrossings().size() > 0) {
269
double width = myNode.getCrossings()[0]->customWidth;
270
if (width == NBEdge::UNSPECIFIED_WIDTH) {
271
width = OptionsCont::getOptions().getFloat("default.crossing-width");
272
}
273
radius2 = MAX2(radius2, width / 2);
274
}
275
if (!useDefaultRadius) {
276
radius2 = MAX2(radius2, myRadius);
277
}
278
dist += radius2;
279
#ifdef DEBUG_NODE_SHAPE
280
if (DEBUGCOND) {
281
std::cout << " using radius=" << radius2 << " ccad=" << ccad << " cad=" << cad << "\n";
282
}
283
#endif
284
}
285
distances[*i] = dist;
286
}
287
288
} else {
289
// the angles are different enough to compute the intersection of
290
// the outer boundaries directly (or there are more than 2 directions). The "nearer" neighbor causes the furthest distance
291
const bool ccwCloser = ccad < cad;
292
const bool cwLargeTurn = needsLargeTurn(*i, *cwi, same);
293
const bool ccwLargeTurn = needsLargeTurn(*i, *ccwi, same);
294
const bool neighLargeTurn = ccwCloser ? ccwLargeTurn : cwLargeTurn;
295
const bool neigh2LargeTurn = ccwCloser ? cwLargeTurn : ccwLargeTurn;
296
// the border facing the closer neighbor
297
const PositionVector& currGeom = ccwCloser ? geomsCCW[*i] : geomsCW[*i];
298
// the border facing the far neighbor
299
const PositionVector& currGeom2 = ccwCloser ? geomsCW[*i] : geomsCCW[*i];
300
// the border of the closer neighbor
301
const PositionVector& neighGeom = ccwCloser ? geomsCW[*ccwi] : geomsCCW[*cwi];
302
// the border of the far neighbor
303
const PositionVector& neighGeom2 = ccwCloser ? geomsCCW[*cwi] : geomsCW[*ccwi];
304
// whether the current edge/direction spans a divided road
305
const bool keepBothDistances = isDivided(*i, same[*i], geomsCCW[*i], geomsCW[*i]);
306
#ifdef DEBUG_NODE_SHAPE
307
if (DEBUGCOND) {
308
std::cout << " i=" << (*i)->getID() << " neigh=" << (*ccwi)->getID() << " neigh2=" << (*cwi)->getID() << "\n";
309
std::cout << " ccwCloser=" << ccwCloser << " divided=" << keepBothDistances
310
<< "\n currGeom=" << currGeom << " neighGeom=" << neighGeom
311
<< "\n currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2
312
<< "\n";
313
}
314
#endif
315
if (!simpleContinuation) {
316
if (useEndpoints && !(*i)->hasDefaultGeometryEndpointAtNode(&myNode)) {
317
distances[*i] = EXT;
318
} else if (currGeom.intersects(neighGeom)) {
319
distances[*i] = (neighLargeTurn ? myRadius : smallRadius) + closestIntersection(currGeom, neighGeom, EXT);
320
#ifdef DEBUG_NODE_SHAPE
321
if (DEBUGCOND) {
322
std::cout << " neigh intersects dist=" << distances[*i] << " currGeom=" << currGeom << " neighGeom=" << neighGeom << "\n";
323
}
324
#endif
325
if (*cwi != *ccwi && currGeom2.intersects(neighGeom2)) {
326
// also use the second intersection point
327
// but prevent very large node shapes
328
const double farAngleDist = ccwCloser ? cad : ccad;
329
double a1 = distances[*i];
330
double a2 = (neigh2LargeTurn ? myRadius : smallRadius) + closestIntersection(currGeom2, neighGeom2, EXT);
331
#ifdef DEBUG_NODE_SHAPE
332
if (DEBUGCOND) {
333
std::cout << " neigh2 also intersects a1=" << a1 << " a2=" << a2 << " ccad=" << RAD2DEG(ccad) << " cad=" << RAD2DEG(cad) << " dist[cwi]=" << distances[*cwi] << " dist[ccwi]=" << distances[*ccwi] << " farAngleDist=" << RAD2DEG(farAngleDist) << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
334
}
335
#endif
336
//if (RAD2DEG(farAngleDist) < 175) {
337
// distances[*i] = MAX2(a1, MIN2(a2, a1 + 180 - RAD2DEG(farAngleDist)));
338
//}
339
if (a2 <= EXT) {
340
if (keepBothDistances) {
341
if (ccwCloser) {
342
distances2[*i] = a2;
343
} else {
344
distances[*i] = a2;
345
distances2[*i] = a1;
346
}
347
} else {
348
distances[*i] = MAX2(a1, a2);
349
}
350
} else if (ccad > DEG2RAD(90. + 45.) && cad > DEG2RAD(90. + 45.)
351
&& (fabs(ccad - cad) > DEG2RAD(10)
352
|| MAX2(ccad, cad) > DEG2RAD(160)
353
|| (a2 - a1) > 7
354
// keep roundabouts nodes small
355
|| myNode.isRoundabout())) {
356
#ifdef DEBUG_NODE_SHAPE
357
if (DEBUGCOND) {
358
std::cout << " ignore a2\n";
359
}
360
#endif
361
// do nothing.
362
} else if (farAngleDist < DEG2RAD(135) || (fabs(RAD2DEG(farAngleDist) - 180) > 1 && fabs(a2 - a1) < 10)) {
363
if (keepBothDistances) {
364
if (ccwCloser) {
365
distances2[*i] = a2;
366
} else {
367
distances[*i] = a2;
368
distances2[*i] = a1;
369
}
370
} else {
371
distances[*i] = MAX2(a1, a2);
372
}
373
}
374
#ifdef DEBUG_NODE_SHAPE
375
if (DEBUGCOND) {
376
std::cout << " a1=" << a1 << " a2=" << a2 << " keepBoth=" << keepBothDistances << " dist=" << distances[*i] << "\n";
377
}
378
#endif
379
}
380
} else {
381
if (*cwi != *ccwi && currGeom2.intersects(neighGeom2)) {
382
distances[*i] = (neigh2LargeTurn ? myRadius : smallRadius) + currGeom2.intersectsAtLengths2D(neighGeom2)[0];
383
#ifdef DEBUG_NODE_SHAPE
384
if (DEBUGCOND) {
385
std::cout << " neigh2 intersects dist=" << distances[*i] << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
386
}
387
#endif
388
} else {
389
distances[*i] = EXT + myRadius;
390
#ifdef DEBUG_NODE_SHAPE
391
if (DEBUGCOND) {
392
std::cout << " no intersects dist=" << distances[*i] << " currGeom=" << currGeom << " neighGeom=" << neighGeom << " currGeom2=" << currGeom2 << " neighGeom2=" << neighGeom2 << "\n";
393
}
394
#endif
395
}
396
}
397
} else {
398
if (currGeom.intersects(neighGeom)) {
399
distances[*i] = currGeom.intersectsAtLengths2D(neighGeom)[0];
400
} else {
401
distances[*i] = (double) EXT;
402
}
403
}
404
}
405
if (useDefaultRadius && sCurveStretch > 0) {
406
double sCurveWidth = myNode.getDisplacementError();
407
if (sCurveWidth > 0) {
408
const double sCurveRadius = myRadius + sCurveWidth / SUMO_const_laneWidth * sCurveStretch * pow((*i)->getSpeed(), 2 + sCurveStretch) / 1000;
409
const double stretch = EXT + sCurveRadius - distances[*i];
410
if (stretch > 0) {
411
distances[*i] += stretch;
412
// fixate extended geometry for repeated computation
413
const double shorten = distances[*i] - EXT;
414
(*i)->shortenGeometryAtNode(&myNode, shorten);
415
for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
416
(*k)->shortenGeometryAtNode(&myNode, shorten);
417
}
418
#ifdef DEBUG_NODE_SHAPE
419
if (DEBUGCOND) {
420
std::cout << " stretching junction: sCurveWidth=" << sCurveWidth << " sCurveRadius=" << sCurveRadius << " stretch=" << stretch << " dist=" << distances[*i] << "\n";
421
}
422
#endif
423
}
424
}
425
}
426
}
427
428
for (NBEdge* const edge : newAll) {
429
if (distances.find(edge) == distances.end()) {
430
assert(false);
431
distances[edge] = EXT;
432
}
433
}
434
// because of lane spread right the crossing point may be identical to the junction center and thus the distance is exactly EXT
435
const double off = EXT - NUMERICAL_EPS;
436
// prevent inverted node shapes
437
// (may happen with near-parallel edges)
438
const double minDistSum = 2 * (EXT + myRadius);
439
for (NBEdge* const edge : newAll) {
440
if (distances[edge] < off && edge->hasDefaultGeometryEndpointAtNode(&myNode)) {
441
for (EdgeVector::const_iterator j = newAll.begin(); j != newAll.end(); ++j) {
442
if (distances[*j] > off && (*j)->hasDefaultGeometryEndpointAtNode(&myNode) && distances[edge] + distances[*j] < minDistSum) {
443
const double angleDiff = fabs(NBHelpers::relAngle(edge->getAngleAtNode(&myNode), (*j)->getAngleAtNode(&myNode)));
444
if (angleDiff > 160 || angleDiff < 20) {
445
#ifdef DEBUG_NODE_SHAPE
446
if (DEBUGCOND) {
447
std::cout << " increasing dist for i=" << edge->getID() << " because of j=" << (*j)->getID() << " jDist=" << distances[*j]
448
<< " oldI=" << distances[edge] << " newI=" << minDistSum - distances[*j]
449
<< " angleDiff=" << angleDiff
450
<< " geomI=" << edge->getGeometry() << " geomJ=" << (*j)->getGeometry() << "\n";
451
}
452
#endif
453
distances[edge] = minDistSum - distances[*j];
454
}
455
}
456
}
457
}
458
}
459
460
461
// build
462
PositionVector ret;
463
for (i = newAll.begin(); i != newAll.end(); ++i) {
464
const PositionVector& ccwBound = geomsCCW[*i];
465
const PositionVector& cwBound = geomsCW[*i];
466
//double offset = MIN3(distances[*i], cwBound.length2D() - POSITION_EPS, ccwBound.length2D() - POSITION_EPS);
467
double offset = distances[*i];
468
double offset2 = distances2.count(*i) != 0 ? distances2[*i] : offset;
469
if (offset != offset2) {
470
// keep rectangular cuts if the difference is small or the roads aren't
471
// really divided by much (unless the angle is very different)
472
const double dWidth = divisionWidth(*i, same[*i],
473
ccwBound.positionAtOffset2D(offset),
474
cwBound.positionAtOffset2D(offset2));
475
const double angle = RAD2DEG(GeomHelper::angleDiff(ccwBound.angleAt2D(0), cwBound.angleAt2D(0)));
476
const double oDelta = fabs(offset - offset2);
477
//std::cout << " i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dWidth=" << dWidth << " angle=" << angle << " same=" << joinNamedToStringSorting(same[*i], ",") << "\n";
478
if ((((oDelta < 5 || dWidth < 10) && fabs(angle) < 30)) || (fabs(angle) < 5 && myNode.getType() != SumoXMLNodeType::RAIL_CROSSING)) {
479
#ifdef DEBUG_NODE_SHAPE
480
std::cout << " i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dWidth=" << dWidth << " angle=" << angle << " same=" << joinNamedToStringSorting(same[*i], ",") << "\n";
481
#endif
482
offset = MAX2(offset, offset2);
483
offset2 = offset;
484
}
485
}
486
if (!(*i)->hasDefaultGeometryEndpointAtNode(&myNode)) {
487
// for non geometry-endpoints, only shorten but never extend the geometry
488
if (advanceStopLine > 0 && offset < EXT) {
489
#ifdef DEBUG_NODE_SHAPE
490
std::cout << " i=" << (*i)->getID() << " offset=" << offset << " advanceStopLine=" << advanceStopLine << "\n";
491
#endif
492
// fixate extended geometry for repeated computation
493
(*i)->extendGeometryAtNode(&myNode, advanceStopLine);
494
for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
495
(*k)->extendGeometryAtNode(&myNode, advanceStopLine);
496
}
497
}
498
offset = MAX2(EXT - advanceStopLine, offset);
499
offset2 = MAX2(EXT - advanceStopLine, offset2);
500
}
501
if (offset == -1) {
502
WRITE_WARNINGF(TL("Fixing offset for edge '%' at node '%."), (*i)->getID(), myNode.getID());
503
offset = -.1;
504
offset2 = -.1;
505
}
506
Position p = ccwBound.positionAtOffset2D(offset);
507
p.setz(myNode.getPosition().z());
508
if (i != newAll.begin()) {
509
ret.append(getSmoothCorner(geomsCW[*(i - 1)], ccwBound, ret[-1], p, cornerDetail));
510
}
511
Position p2 = cwBound.positionAtOffset2D(offset2);
512
p2.setz(myNode.getPosition().z());
513
//ret.append(getEdgeCuts(*i, geomsCCW, geomsCW, offset, offset2, same));
514
ret.push_back_noDoublePos(p);
515
ret.push_back_noDoublePos(p2);
516
#ifdef DEBUG_NODE_SHAPE
517
if (DEBUGCOND) {
518
std::cout << " build stopLine for i=" << (*i)->getID() << " offset=" << offset << " offset2=" << offset2 << " dist=" << distances[*i] << " cwLength=" << cwBound.length2D() << " ccwLength=" << ccwBound.length2D() << " p=" << p << " p2=" << p2 << " ccwBound=" << ccwBound << " cwBound=" << cwBound << "\n";
519
}
520
#endif
521
(*i)->setNodeBorder(&myNode, p, p2, rectangularCut);
522
for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
523
(*k)->setNodeBorder(&myNode, p, p2, rectangularCut);
524
}
525
}
526
// final curve segment
527
ret.append(getSmoothCorner(geomsCW[*(newAll.end() - 1)], geomsCCW[*newAll.begin()], ret[-1], ret[0], cornerDetail));
528
#ifdef DEBUG_NODE_SHAPE
529
if (DEBUGCOND) {
530
std::cout << " final shape=" << ret << "\n";
531
}
532
#endif
533
return ret;
534
}
535
536
537
double
538
NBNodeShapeComputer::closestIntersection(const PositionVector& geom1, const PositionVector& geom2, double offset) {
539
std::vector<double> intersections = geom1.intersectsAtLengths2D(geom2);
540
double result = intersections[0];
541
for (std::vector<double>::iterator it = intersections.begin() + 1; it != intersections.end(); ++it) {
542
if (fabs(*it - offset) < fabs(result - offset)) {
543
result = *it;
544
}
545
}
546
return result;
547
}
548
549
bool
550
NBNodeShapeComputer::needsLargeTurn(NBEdge* e1, NBEdge* e2,
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
582
PositionVector
583
NBNodeShapeComputer::getSmoothCorner(PositionVector begShape, PositionVector endShape,
584
const Position& begPoint, const Position& endPoint, int cornerDetail) {
585
PositionVector ret;
586
if (cornerDetail > 0) {
587
PositionVector begShape2 = begShape.reverse().getSubpart2D(EXT2, begShape.length());
588
const double begSplit = begShape2.nearest_offset_to_point2D(begPoint, false);
589
#ifdef DEBUG_SMOOTH_CORNERS
590
if (DEBUGCOND) {
591
std::cout << " begLength=" << begShape2.length2D() << " begSplit=" << begSplit << "\n";
592
}
593
#endif
594
if (begSplit > POSITION_EPS && begSplit < begShape2.length2D() - POSITION_EPS) {
595
begShape2 = begShape2.splitAt(begSplit, true).first;
596
} else {
597
return ret;
598
}
599
PositionVector endShape2 = endShape.getSubpart(0, endShape.length() - EXT2);
600
const double endSplit = endShape2.nearest_offset_to_point2D(endPoint, false);
601
#ifdef DEBUG_SMOOTH_CORNERS
602
if (DEBUGCOND) {
603
std::cout << " endLength=" << endShape2.length2D() << " endSplit=" << endSplit << "\n";
604
}
605
#endif
606
if (endSplit > POSITION_EPS && endSplit < endShape2.length2D() - POSITION_EPS) {
607
endShape2 = endShape2.splitAt(endSplit, true).second;
608
} else {
609
return ret;
610
}
611
// flatten z to junction z level
612
begShape2 = begShape2.interpolateZ(myNode.getPosition().z(), myNode.getPosition().z());
613
endShape2 = endShape2.interpolateZ(myNode.getPosition().z(), myNode.getPosition().z());
614
#ifdef DEBUG_SMOOTH_CORNERS
615
if (DEBUGCOND) {
616
std::cout << "getSmoothCorner begPoint=" << begPoint << " endPoint=" << endPoint
617
<< " begShape=" << begShape << " endShape=" << endShape
618
<< " begShape2=" << begShape2 << " endShape2=" << endShape2
619
<< "\n";
620
}
621
#endif
622
if (begShape2.size() < 2 || endShape2.size() < 2) {
623
return ret;
624
}
625
const double angle = GeomHelper::angleDiff(begShape2.angleAt2D(-2), endShape2.angleAt2D(0));
626
NBNode* recordError = nullptr;
627
#ifdef DEBUG_SMOOTH_CORNERS
628
if (DEBUGCOND) {
629
std::cout << " angle=" << RAD2DEG(angle) << "\n";
630
}
631
recordError = const_cast<NBNode*>(&myNode);
632
#endif
633
// fill highly acute corners
634
//if (fabs(angle) > DEG2RAD(135)) {
635
// return ret;
636
//}
637
PositionVector curve = myNode.computeSmoothShape(begShape2, endShape2, cornerDetail + 2, false, 25, 25, recordError, NBNode::AVOID_WIDE_LEFT_TURN);
638
//PositionVector curve = myNode.computeSmoothShape(begShape2, endShape2, cornerDetail + 2, false, 25, 25, recordError, 0);
639
const double curvature = curve.length2D() / MAX2(NUMERICAL_EPS, begPoint.distanceTo2D(endPoint));
640
#ifdef DEBUG_SMOOTH_CORNERS
641
if (DEBUGCOND) {
642
std::cout << " curve=" << curve << " curveLength=" << curve.length2D() << " dist=" << begPoint.distanceTo2D(endPoint) << " curvature=" << curvature << "\n";
643
}
644
#endif
645
if (curvature > 2 && angle > DEG2RAD(85)) {
646
// simplify dubious inside corner shape
647
return ret;
648
}
649
if (curve.size() > 2) {
650
curve.erase(curve.begin());
651
curve.pop_back();
652
ret = curve;
653
}
654
}
655
return ret;
656
}
657
658
void
659
NBNodeShapeComputer::computeEdgeBoundaries(const EdgeVector& edges,
660
GeomsMap& geomsCCW,
661
GeomsMap& geomsCW) {
662
// compute boundary lines and extend it by EXT m
663
for (NBEdge* const edge : edges) {
664
// store current edge's boundary as current ccw/cw boundary
665
try {
666
geomsCCW[edge] = edge->getCCWBoundaryLine(myNode);
667
} catch (InvalidArgument& e) {
668
WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': " + std::string(e.what()));
669
geomsCCW[edge] = edge->getGeometry();
670
}
671
try {
672
geomsCW[edge] = edge->getCWBoundaryLine(myNode);
673
} catch (InvalidArgument& e) {
674
WRITE_WARNING("While computing intersection geometry at junction '" + myNode.getID() + "': " + std::string(e.what()));
675
geomsCW[edge] = edge->getGeometry();
676
}
677
// ensure the boundary is valid
678
if (geomsCCW[edge].length2D() < NUMERICAL_EPS) {
679
geomsCCW[edge] = edge->getGeometry();
680
}
681
if (geomsCW[edge].length2D() < NUMERICAL_EPS) {
682
geomsCW[edge] = edge->getGeometry();
683
}
684
// cut off all parts beyond EXT to avoid issues with curved-back roads
685
geomsCCW[edge] = geomsCCW[edge].getSubpart2D(0, MAX2(EXT, edge->getTotalWidth()));
686
geomsCW[edge] = geomsCW[edge].getSubpart2D(0, MAX2(EXT, edge->getTotalWidth()));
687
// extend the boundary by extrapolating it by EXT m towards the junction
688
geomsCCW[edge].extrapolate2D(EXT, true);
689
geomsCW[edge].extrapolate2D(EXT, true);
690
// ensure minimum length by extending it away from the junction
691
geomsCCW[edge].extrapolate(EXT2, false, true);
692
geomsCW[edge].extrapolate(EXT2, false, true);
693
}
694
}
695
696
void
697
NBNodeShapeComputer::joinSameDirectionEdges(const EdgeVector& edges, std::map<NBEdge*, std::set<NBEdge*, ComparatorIdLess> >& 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
<< " sameGeom=" << sameGeom
736
<< " diffDirs=" << differentDirs
737
<< " isOpposite=" << (differentDirs && foundOpposite.count(*i) == 0)
738
<< " angleDiff=" << angleDiff
739
<< " ambiguousGeometry=" << ambiguousGeometry
740
<< " badInsersection=" << badIntersection(*i, *j, EXT)
741
<< "\n";
742
743
}
744
#endif
745
if (sameGeom || fabs(angleDiff) < DEG2RAD(20)) {
746
const bool isOpposite = differentDirs && foundOpposite.count(*i) == 0;
747
if (isOpposite) {
748
foundOpposite.insert(*i);
749
foundOpposite.insert(*j);
750
}
751
if (isOpposite || ambiguousGeometry || (!isXodr && badIntersection(*i, *j, EXT))) {
752
// maintain equivalence relation for all members of the equivalence class
753
for (std::set<NBEdge*>::iterator k = same[*i].begin(); k != same[*i].end(); ++k) {
754
if (*j != *k) {
755
same[*k].insert(*j);
756
same[*j].insert(*k);
757
}
758
}
759
for (std::set<NBEdge*>::iterator k = same[*j].begin(); k != same[*j].end(); ++k) {
760
if (*i != *k) {
761
same[*k].insert(*i);
762
same[*i].insert(*k);
763
}
764
}
765
same[*i].insert(*j);
766
same[*j].insert(*i);
767
#ifdef DEBUG_NODE_SHAPE
768
if (DEBUGCOND) {
769
std::cout << " joinedSameDirectionEdges " << (*i)->getID() << " " << (*j)->getID() << " isOpposite=" << isOpposite << " ambiguousGeometry=" << ambiguousGeometry << "\n";
770
}
771
#endif
772
}
773
}
774
}
775
}
776
777
778
bool
779
NBNodeShapeComputer::badIntersection(const NBEdge* e1, const NBEdge* e2, double distance) {
780
// check whether the two edges are on top of each other. In that case they should be joined
781
// also, if they never touch along their common length
782
const double commonLength = MIN3(distance, e1->getGeometry().length(), e2->getGeometry().length());
783
PositionVector geom1 = e1->getGeometry();
784
PositionVector geom2 = e2->getGeometry();
785
// shift to make geom the centerline of the edge regardless of spreadtype
786
if (e1->getLaneSpreadFunction() == LaneSpreadFunction::RIGHT) {
787
geom1.move2side(e1->getTotalWidth() / 2);
788
}
789
if (e2->getLaneSpreadFunction() == LaneSpreadFunction::RIGHT) {
790
geom2.move2side(e2->getTotalWidth() / 2);
791
}
792
// always let geometry start at myNode
793
if (e1->getToNode() == &myNode) {
794
geom1 = geom1.reverse();
795
}
796
if (e2->getToNode() == &myNode) {
797
geom2 = geom2.reverse();
798
}
799
geom1 = geom1.getSubpart2D(0, commonLength);
800
geom2 = geom2.getSubpart2D(0, commonLength);
801
double endAngleDiff = 0;
802
if (geom1.size() >= 2 && geom2.size() >= 2) {
803
endAngleDiff = fabs(RAD2DEG(GeomHelper::angleDiff(
804
geom1.angleAt2D((int)geom1.size() - 2),
805
geom2.angleAt2D((int)geom2.size() - 2))));
806
}
807
const double minDistanceThreshold = (e1->getTotalWidth() + e2->getTotalWidth()) / 2 + POSITION_EPS;
808
std::vector<double> distances = geom1.distances(geom2, true);
809
std::vector<double> distances2 = geom1.distances(geom2);
810
const double minDist = VectorHelper<double>::minValue(distances2);
811
const double maxDist = VectorHelper<double>::maxValue(distances);
812
const bool curvingTowards = geom1[0].distanceTo2D(geom2[0]) > minDistanceThreshold && minDist < minDistanceThreshold;
813
const bool onTop = (maxDist - POSITION_EPS < minDistanceThreshold) && endAngleDiff < 30;
814
const bool bothDefault = e1->hasDefaultGeometryEndpointAtNode(&myNode) && e2->hasDefaultGeometryEndpointAtNode(&myNode);
815
const bool neverTouch = minDist > minDistanceThreshold * 2 && !bothDefault;
816
geom1.extrapolate2D(EXT);
817
geom2.extrapolate2D(EXT);
818
Position intersect = geom1.intersectionPosition2D(geom2);
819
const bool intersects = intersect != Position::INVALID && geom1.distance2D(intersect) < POSITION_EPS;
820
#ifdef DEBUG_NODE_SHAPE
821
if (DEBUGCOND) {
822
std::cout << " badIntersect: onTop=" << onTop << " curveTo=" << curvingTowards << " intersects=" << intersects
823
<< " endAngleDiff=" << endAngleDiff
824
<< " geom1=" << geom1 << " geom2=" << geom2
825
<< " distances=" << toString(distances) << " minDist=" << minDist << " maxDist=" << maxDist << " thresh=" << minDistanceThreshold
826
<< " neverTouch=" << neverTouch
827
<< " intersectPos=" << intersect
828
<< "\n";
829
}
830
#endif
831
return onTop || curvingTowards || !intersects || neverTouch;
832
}
833
834
835
EdgeVector
836
NBNodeShapeComputer::computeUniqueDirectionList(
837
const EdgeVector& all,
838
std::map<NBEdge*, std::set<NBEdge*, ComparatorIdLess> >& same,
839
GeomsMap& geomsCCW,
840
GeomsMap& geomsCW) {
841
// store relationships
842
EdgeVector newAll = all;
843
for (NBEdge* e1 : all) {
844
// determine which of the edges marks the outer boundary
845
auto e2NewAll = std::find(newAll.begin(), newAll.end(), e1);
846
#ifdef DEBUG_NODE_SHAPE
847
if (DEBUGCOND) std::cout << "computeUniqueDirectionList e1=" << e1->getID()
848
<< " deleted=" << (e2NewAll == newAll.end())
849
<< " same=" << joinNamedToStringSorting(same[e1], ',') << "\n";
850
#endif
851
if (e2NewAll == newAll.end()) {
852
continue;
853
}
854
auto e1It = std::find(all.begin(), all.end(), e1);
855
auto bestCCW = e1It;
856
auto bestCW = e1It;
857
bool changed = true;
858
while (changed) {
859
changed = false;
860
for (NBEdge* e2 : same[e1]) {
861
#ifdef DEBUG_NODE_SHAPE
862
if (DEBUGCOND) {
863
std::cout << " e2=" << e2->getID() << "\n";
864
}
865
#endif
866
auto e2It = std::find(all.begin(), all.end(), e2);
867
if (e2It + 1 == bestCCW || (e2It == (all.end() - 1) && bestCCW == all.begin())) {
868
bestCCW = e2It;
869
changed = true;
870
#ifdef DEBUG_NODE_SHAPE
871
if (DEBUGCOND) {
872
std::cout << " bestCCW=" << e2->getID() << "\n";
873
}
874
#endif
875
} else if (bestCW + 1 == e2It || (bestCW == (all.end() - 1) && e2It == all.begin())) {
876
bestCW = e2It;
877
changed = true;
878
#ifdef DEBUG_NODE_SHAPE
879
if (DEBUGCOND) {
880
std::cout << " bestCW=" << e2->getID() << "\n";
881
}
882
#endif
883
}
884
}
885
}
886
if (bestCW != e1It) {
887
geomsCW[e1] = geomsCW[*bestCW];
888
computeSameEnd(geomsCW[e1], geomsCCW[e1]);
889
}
890
if (bestCCW != e1It) {
891
geomsCCW[e1] = geomsCCW[*bestCCW];
892
computeSameEnd(geomsCW[e1], geomsCCW[e1]);
893
}
894
// clean up
895
for (NBEdge* e2 : same[e1]) {
896
auto e2NewAllIt = std::find(newAll.begin(), newAll.end(), e2);
897
if (e2NewAllIt != newAll.end()) {
898
newAll.erase(e2NewAllIt);
899
}
900
}
901
}
902
#ifdef DEBUG_NODE_SHAPE
903
if (DEBUGCOND) {
904
std::cout << " newAll:\n";
905
for (NBEdge* e : newAll) {
906
std::cout << " " << e->getID() << " geomCCW=" << geomsCCW[e] << " geomsCW=" << geomsCW[e] << "\n";
907
}
908
}
909
#endif
910
return newAll;
911
}
912
913
914
void
915
NBNodeShapeComputer::initNeighbors(const EdgeVector& edges, const EdgeVector::const_iterator& current,
916
GeomsMap& geomsCW,
917
GeomsMap& geomsCCW,
918
EdgeVector::const_iterator& cwi,
919
EdgeVector::const_iterator& ccwi,
920
double& cad,
921
double& ccad) {
922
const double twoPI = (double)(2 * M_PI);
923
cwi = current;
924
cwi++;
925
if (cwi == edges.end()) {
926
std::advance(cwi, -((int)edges.size())); // set to edges.begin();
927
}
928
ccwi = current;
929
if (ccwi == edges.begin()) {
930
std::advance(ccwi, edges.size() - 1); // set to edges.end() - 1;
931
} else {
932
ccwi--;
933
}
934
935
const double angleCurCCW = geomsCCW[*current].angleAt2D(0);
936
const double angleCurCW = geomsCW[*current].angleAt2D(0);
937
const double angleCCW = geomsCW[*ccwi].angleAt2D(0);
938
const double angleCW = geomsCCW[*cwi].angleAt2D(0);
939
ccad = angleCCW - angleCurCCW;
940
while (ccad < 0.) {
941
ccad += twoPI;
942
}
943
cad = angleCurCW - angleCW;
944
while (cad < 0.) {
945
cad += twoPI;
946
}
947
}
948
949
950
951
const PositionVector
952
NBNodeShapeComputer::computeNodeShapeSmall() {
953
#ifdef DEBUG_NODE_SHAPE
954
if (DEBUGCOND) {
955
std::cout << "computeNodeShapeSmall node=" << myNode.getID() << "\n";
956
}
957
#endif
958
PositionVector ret;
959
for (NBEdge* e : myNode.getEdges()) {
960
// compute crossing with normal
961
PositionVector edgebound1 = e->getCCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
962
PositionVector edgebound2 = e->getCWBoundaryLine(myNode).getSubpartByIndex(0, 2);
963
Position delta = edgebound1[1] - edgebound1[0];
964
delta.set(-delta.y(), delta.x()); // rotate 90 degrees
965
PositionVector cross(myNode.getPosition(), myNode.getPosition() + delta);
966
cross.extrapolate2D(500);
967
edgebound1.extrapolate2D(500);
968
edgebound2.extrapolate2D(500);
969
if (cross.intersects(edgebound1)) {
970
Position np = cross.intersectionPosition2D(edgebound1);
971
np.set(np.x(), np.y(), myNode.getPosition().z());
972
ret.push_back_noDoublePos(np);
973
}
974
if (cross.intersects(edgebound2)) {
975
Position np = cross.intersectionPosition2D(edgebound2);
976
np.set(np.x(), np.y(), myNode.getPosition().z());
977
ret.push_back_noDoublePos(np);
978
}
979
e->resetNodeBorder(&myNode);
980
}
981
return ret;
982
}
983
984
985
double
986
NBNodeShapeComputer::getDefaultRadius(const OptionsCont& oc) {
987
// look for incoming/outgoing edge pairs that do not go straight and allow wide vehicles
988
// (connection information is not available yet)
989
// @TODO compute the radius for each pair of neighboring edge intersections in computeNodeShapeDefault rather than use the maximum
990
const double radius = oc.getFloat("default.junctions.radius");
991
const double smallRadius = oc.getFloat("junctions.small-radius");
992
double maxRightAngle = 0; // rad
993
double extraWidthRight = 0; // m
994
double maxLeftAngle = 0; // rad
995
double extraWidthLeft = 0; // m
996
int laneDelta = 0;
997
int totalWideLanesIn = 0;
998
for (NBEdge* in : myNode.getIncomingEdges()) {
999
int wideLanesIn = 0;
1000
for (int i = 0; i < in->getNumLanes(); i++) {
1001
if ((in->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1002
wideLanesIn++;
1003
}
1004
}
1005
totalWideLanesIn += wideLanesIn;
1006
for (NBEdge* out : myNode.getOutgoingEdges()) {
1007
if ((in->getPermissions() & out->getPermissions() & SVC_LARGE_TURN) != 0) {
1008
if (myNode.getDirection(in, out) == LinkDirection::TURN) {
1009
continue;
1010
};
1011
const double angle = GeomHelper::angleDiff(
1012
in->getGeometry().angleAt2D(-2),
1013
out->getGeometry().angleAt2D(0));
1014
if (angle < 0) {
1015
if (maxRightAngle < -angle) {
1016
maxRightAngle = -angle;
1017
extraWidthRight = MAX2(getExtraWidth(in, SVC_LARGE_TURN), getExtraWidth(out, SVC_LARGE_TURN));
1018
}
1019
} else {
1020
if (maxLeftAngle < angle) {
1021
maxLeftAngle = angle;
1022
// all edges clockwise between in and out count as extra width
1023
extraWidthLeft = 0;
1024
EdgeVector::const_iterator pIn = std::find(myNode.getEdges().begin(), myNode.getEdges().end(), in);
1025
NBContHelper::nextCW(myNode.getEdges(), pIn);
1026
while (*pIn != out) {
1027
extraWidthLeft += (*pIn)->getTotalWidth();
1028
#ifdef DEBUG_RADIUS
1029
if (DEBUGCOND) {
1030
std::cout << " in=" << in->getID() << " out=" << out->getID() << " extra=" << (*pIn)->getID() << " extraWidthLeft=" << extraWidthLeft << "\n";
1031
}
1032
#endif
1033
NBContHelper::nextCW(myNode.getEdges(), pIn);
1034
}
1035
}
1036
}
1037
int wideLanesOut = 0;
1038
for (int i = 0; i < out->getNumLanes(); i++) {
1039
if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1040
wideLanesOut++;
1041
}
1042
}
1043
#ifdef DEBUG_RADIUS
1044
if (DEBUGCOND) {
1045
std::cout << " in=" << in->getID() << " out=" << out->getID() << " wideLanesIn=" << wideLanesIn << " wideLanesOut=" << wideLanesOut << "\n";
1046
}
1047
#endif
1048
laneDelta = MAX2(laneDelta, abs(wideLanesOut - wideLanesIn));
1049
}
1050
}
1051
}
1052
// special case: on/off-ramp
1053
if (myNode.getOutgoingEdges().size() == 1 || myNode.getIncomingEdges().size() == 1) {
1054
int totalWideLanesOut = 0;
1055
for (NBEdge* out : myNode.getOutgoingEdges()) {
1056
for (int i = 0; i < out->getNumLanes(); i++) {
1057
if ((out->getPermissions(i) & SVC_LARGE_TURN) != 0) {
1058
totalWideLanesOut++;
1059
}
1060
}
1061
}
1062
if (totalWideLanesIn == totalWideLanesOut) {
1063
// use total laneDelta instead of individual edge lane delta
1064
laneDelta = 0;
1065
}
1066
}
1067
// changing the number of wide-vehicle lanes on a straight segment requires a larger junction to allow for smooth driving
1068
// otherwise we can reduce the radius according to the angle
1069
double result = radius;
1070
// left turns are assumed to cross additional edges and thus du not determine the required radius in most cases
1071
double maxTurnAngle = maxRightAngle;
1072
double extraWidth = extraWidthRight;
1073
if (maxRightAngle < DEG2RAD(5)) {
1074
maxTurnAngle = maxLeftAngle;
1075
extraWidth = extraWidthLeft;
1076
}
1077
const double minRadius = maxTurnAngle >= DEG2RAD(30) ? MIN2(smallRadius, radius) : smallRadius;
1078
if (laneDelta == 0 || maxTurnAngle >= DEG2RAD(30) || myNode.isConstantWidthTransition()) {
1079
// subtract radius gained from extra lanes
1080
// do not increase radius for turns that are sharper than a right angle
1081
result = radius * tan(0.5 * MIN2(0.5 * M_PI, maxTurnAngle)) - extraWidth;
1082
}
1083
result = MAX2(minRadius, result);
1084
#ifdef DEBUG_RADIUS
1085
if (DEBUGCOND) {
1086
std::cout << "getDefaultRadius n=" << myNode.getID()
1087
<< " r=" << radius << " sr=" << smallRadius
1088
<< " mr=" << minRadius
1089
<< " laneDelta=" << laneDelta
1090
<< " rightA=" << RAD2DEG(maxRightAngle)
1091
<< " leftA=" << RAD2DEG(maxLeftAngle)
1092
<< " maxA=" << RAD2DEG(maxTurnAngle)
1093
<< " extraWidth=" << extraWidth
1094
<< " result=" << result << "\n";
1095
}
1096
#endif
1097
return result;
1098
}
1099
1100
1101
bool
1102
NBNodeShapeComputer::isDivided(const NBEdge* e, std::set<NBEdge*, ComparatorIdLess> same, const PositionVector& ccw, const PositionVector& cw) const {
1103
if (same.size() < 2) {
1104
return false;
1105
}
1106
std::set<Position> endPoints;
1107
endPoints.insert(e->getEndpointAtNode(&myNode));
1108
for (NBEdge* s : same) {
1109
endPoints.insert(s->getEndpointAtNode(&myNode));
1110
}
1111
if (endPoints.size() > 1) {
1112
std::vector<double> distances = ccw.distances(cw, true);
1113
double width = e->getTotalWidth();
1114
for (const NBEdge* e2 : same) {
1115
width += e2->getTotalWidth();
1116
}
1117
const double maxDist = VectorHelper<double>::maxValue(distances);
1118
const double maxDivider = maxDist - width;
1119
return maxDivider >= 5;
1120
}
1121
return false;
1122
}
1123
1124
1125
double
1126
NBNodeShapeComputer::getExtraWidth(const NBEdge* e, SVCPermissions exclude) {
1127
double result = 0;
1128
int lane = 0;
1129
while (lane < e->getNumLanes() && e->getPermissions(lane) == 0) {
1130
// ignore forbidden lanes out the outside
1131
lane++;
1132
}
1133
while (lane < e->getNumLanes() && (e->getPermissions(lane) & exclude) == 0) {
1134
result += e->getLaneWidth(lane);
1135
lane++;
1136
}
1137
return result;
1138
}
1139
1140
1141
double
1142
NBNodeShapeComputer::divisionWidth(const NBEdge* e, std::set<NBEdge*, ComparatorIdLess> same, const Position& p, const Position& p2) {
1143
double result = p.distanceTo2D(p2);
1144
result -= e->getTotalWidth();
1145
for (NBEdge* e2 : same) {
1146
result -= e2->getTotalWidth();
1147
}
1148
return MAX2(0.0, result);
1149
}
1150
1151
/****************************************************************************/
1152
1153