Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
eclipse
GitHub Repository: eclipse/sumo
Path: blob/main/src/netwrite/NWWriter_OpenDrive.cpp
169665 views
1
/****************************************************************************/
2
// Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3
// Copyright (C) 2011-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 NWWriter_OpenDrive.cpp
15
/// @author Daniel Krajzewicz
16
/// @author Jakob Erdmann
17
/// @date Tue, 04.05.2011
18
///
19
// Exporter writing networks using the openDRIVE format
20
/****************************************************************************/
21
#include <config.h>
22
23
#include <ctime>
24
#include "NWWriter_OpenDrive.h"
25
#include <utils/iodevices/OutputDevice_String.h>
26
#include <utils/common/MsgHandler.h>
27
#include <netbuild/NBEdgeCont.h>
28
#include <netbuild/NBNode.h>
29
#include <netbuild/NBNodeCont.h>
30
#include <netbuild/NBNetBuilder.h>
31
#include <utils/options/OptionsCont.h>
32
#include <utils/iodevices/OutputDevice.h>
33
#include <utils/common/MsgHandler.h>
34
#include <utils/common/StdDefs.h>
35
#include <utils/common/StringUtils.h>
36
#include <utils/common/StringTokenizer.h>
37
#include <utils/geom/GeoConvHelper.h>
38
#include <regex>
39
40
#define INVALID_ID -1
41
42
//#define DEBUG_SMOOTH_GEOM
43
#define DEBUGCOND true
44
45
#define MIN_TURN_DIAMETER 2.0
46
47
#define ROAD_OBJECTS "roadObjects"
48
49
// ===========================================================================
50
// static members
51
// ===========================================================================
52
bool NWWriter_OpenDrive::lefthand(false);
53
bool NWWriter_OpenDrive::LHLL(false);
54
bool NWWriter_OpenDrive::LHRL(false);
55
56
// ===========================================================================
57
// method definitions
58
// ===========================================================================
59
// ---------------------------------------------------------------------------
60
// static methods
61
// ---------------------------------------------------------------------------
62
void
63
NWWriter_OpenDrive::writeNetwork(const OptionsCont& oc, NBNetBuilder& nb) {
64
// check whether an opendrive-file shall be generated
65
if (!oc.isSet("opendrive-output")) {
66
return;
67
}
68
const NBNodeCont& nc = nb.getNodeCont();
69
const NBEdgeCont& ec = nb.getEdgeCont();
70
const bool origNames = oc.getBool("output.original-names");
71
lefthand = oc.getBool("lefthand");
72
LHLL = lefthand && oc.getBool("opendrive-output.lefthand-left");
73
LHRL = lefthand && !LHLL;
74
const double straightThresh = DEG2RAD(oc.getFloat("opendrive-output.straight-threshold"));
75
// some internal mapping containers
76
int nodeID = 1;
77
int edgeID = nc.size() * 10; // distinct from node ids
78
StringBijection<int> edgeMap;
79
StringBijection<int> nodeMap;
80
//
81
OutputDevice& device = OutputDevice::getDevice(oc.getString("opendrive-output"));
82
OutputDevice::createDeviceByOption("opendrive-output", "OpenDRIVE");
83
time_t now = time(nullptr);
84
std::string dstr(ctime(&now));
85
const Boundary& b = GeoConvHelper::getFinal().getConvBoundary();
86
// write header
87
device.openTag("header");
88
device.writeAttr("revMajor", "1");
89
device.writeAttr("revMinor", "4");
90
device.writeAttr("name", "");
91
device.writeAttr("version", "1.00");
92
device.writeAttr("date", dstr.substr(0, dstr.length() - 1));
93
device.writeAttr("north", b.ymax());
94
device.writeAttr("south", b.ymin());
95
device.writeAttr("east", b.xmax());
96
device.writeAttr("west", b.xmin());
97
/* @note obsolete in 1.4
98
device.writeAttr("maxRoad", ec.size());
99
device.writeAttr("maxJunc", nc.size());
100
device.writeAttr("maxPrg", 0);
101
*/
102
// write optional geo reference
103
const GeoConvHelper& gch = GeoConvHelper::getFinal();
104
if (gch.usingGeoProjection()) {
105
device.openTag("geoReference");
106
device.writePreformattedTag(" <![CDATA[\n "
107
+ gch.getProjString()
108
+ "\n]]>\n");
109
device.closeTag();
110
if (gch.getOffsetBase() != Position(0, 0)) {
111
device.openTag("offset");
112
device.writeAttr("x", -gch.getOffsetBase().x());
113
device.writeAttr("y", -gch.getOffsetBase().y());
114
device.writeAttr("z", -gch.getOffsetBase().z());
115
device.writeAttr("hdg", 0);
116
device.closeTag();
117
}
118
}
119
device.closeTag();
120
121
SignalLanes signalLanes;
122
123
mapmatchRoadObjects(nb.getShapeCont(), ec);
124
125
PositionVector crosswalk_shape;
126
std::map<std::string, std::vector<std::string>> crosswalksByEdge;
127
for (auto it = nc.begin(); it != nc.end(); ++it) {
128
NBNode* n = it->second;
129
auto crosswalks = n->getCrossings();
130
for (const auto& cw : n->getCrossings()) {
131
// getting from crosswalk line to a full shape
132
crosswalk_shape = cw->shape;
133
PositionVector rightside = cw->shape;
134
try {
135
crosswalk_shape.move2side(cw->width / 2);
136
rightside.move2side(cw->width / -2);
137
} catch (InvalidArgument&) { }
138
rightside = rightside.reverse();
139
crosswalk_shape.append(rightside);
140
auto crosswalkId = cw->id;
141
nb.getShapeCont().addPolygon(crosswalkId, "crosswalk", RGBColor::DEFAULT_COLOR, 0,
142
Shape::DEFAULT_ANGLE, Shape::DEFAULT_IMG_FILE,
143
crosswalk_shape, false, true, 1, false, crosswalkId);
144
SUMOPolygon* cwp = nb.getShapeCont().getPolygons().get(crosswalkId);
145
cwp->setParameter("length", toString(cw->width));
146
cwp->setParameter("width", toString(cw->shape.length2D()));
147
cwp->setParameter("hdg", "0");
148
crosswalksByEdge[cw->edges[0]->getID()].push_back(crosswalkId);
149
}
150
}
151
152
// write normal edges (road)
153
for (std::map<std::string, NBEdge*>::const_iterator i = ec.begin(); i != ec.end(); ++i) {
154
const NBEdge* e = (*i).second;
155
const int fromNodeID = e->getIncomingEdges().size() > 0 ? getID(e->getFromNode()->getID(), nodeMap, nodeID) : INVALID_ID;
156
const int toNodeID = e->getConnections().size() > 0 ? getID(e->getToNode()->getID(), nodeMap, nodeID) : INVALID_ID;
157
writeNormalEdge(device, e,
158
getID(e->getID(), edgeMap, edgeID),
159
fromNodeID, toNodeID,
160
origNames, straightThresh,
161
nb.getShapeCont(),
162
signalLanes,
163
crosswalksByEdge[e->getID()]);
164
}
165
device.lf();
166
167
// write junction-internal edges (road). In OpenDRIVE these are called 'paths' or 'connecting roads'
168
OutputDevice_String junctionOSS(3);
169
for (std::map<std::string, NBNode*>::const_iterator i = nc.begin(); i != nc.end(); ++i) {
170
NBNode* n = (*i).second;
171
int connectionID = 0; // unique within a junction
172
const int nID = getID(n->getID(), nodeMap, nodeID);
173
if (n->numNormalConnections() > 0) {
174
junctionOSS << " <junction name=\"" << n->getID() << "\" id=\"" << nID << "\">\n";
175
}
176
std::vector<NBEdge*> incoming = (*i).second->getIncomingEdges();
177
if (lefthand) {
178
std::reverse(incoming.begin(), incoming.end());
179
}
180
for (NBEdge* inEdge : incoming) {
181
std::string centerMark = "none";
182
const int inEdgeID = getID(inEdge->getID(), edgeMap, edgeID);
183
// group parallel edges
184
const NBEdge* outEdge = nullptr;
185
bool isOuterEdge = true; // determine where a solid outer border should be drawn
186
int lastFromLane = -1;
187
std::vector<NBEdge::Connection> parallel;
188
std::vector<NBEdge::Connection> connections = inEdge->getConnections();
189
for (const NBEdge::Connection& c : connections) {
190
assert(c.toEdge != 0);
191
if (outEdge != c.toEdge || c.fromLane == lastFromLane) {
192
if (outEdge != nullptr) {
193
if (isOuterEdge) {
194
addPedestrianConnection(inEdge, outEdge, parallel);
195
}
196
connectionID = writeInternalEdge(device, junctionOSS, inEdge, nID,
197
getID(parallel.back().getInternalLaneID(), edgeMap, edgeID),
198
inEdgeID,
199
getID(outEdge->getID(), edgeMap, edgeID),
200
connectionID,
201
parallel, isOuterEdge, straightThresh, centerMark,
202
signalLanes);
203
parallel.clear();
204
isOuterEdge = false;
205
}
206
outEdge = c.toEdge;
207
}
208
lastFromLane = c.fromLane;
209
parallel.push_back(c);
210
}
211
if (isOuterEdge) {
212
addPedestrianConnection(inEdge, outEdge, parallel);
213
}
214
if (!parallel.empty()) {
215
if (!lefthand && (n->geometryLike() || inEdge->isTurningDirectionAt(outEdge))) {
216
centerMark = "solid";
217
}
218
connectionID = writeInternalEdge(device, junctionOSS, inEdge, nID,
219
getID(parallel.back().getInternalLaneID(), edgeMap, edgeID),
220
inEdgeID,
221
getID(outEdge->getID(), edgeMap, edgeID),
222
connectionID,
223
parallel, isOuterEdge, straightThresh, centerMark,
224
signalLanes);
225
parallel.clear();
226
}
227
}
228
if (n->numNormalConnections() > 0) {
229
junctionOSS << " </junction>\n";
230
}
231
}
232
device.lf();
233
// write controllers
234
for (std::map<std::string, NBNode*>::const_iterator i = nc.begin(); i != nc.end(); ++i) {
235
NBNode* n = (*i).second;
236
if (n->isTLControlled()) {
237
NBTrafficLightDefinition* tl = *n->getControllingTLS().begin();
238
std::set<std::string> ids;
239
device.openTag("controller");
240
device.writeAttr("id", tl->getID());
241
for (const NBConnection& c : tl->getControlledLinks()) {
242
const std::string id = tl->getID() + "_" + toString(c.getTLIndex());
243
if (ids.count(id) == 0) {
244
ids.insert(id);
245
device.openTag("control");
246
device.writeAttr("signalId", id);
247
device.closeTag();
248
}
249
}
250
device.closeTag();
251
}
252
}
253
// write junctions (junction)
254
device << junctionOSS.getString();
255
256
device.closeTag();
257
device.close();
258
}
259
260
261
std::string
262
NWWriter_OpenDrive::getDividerType(const NBEdge* e) {
263
std::map<std::string, std::string> dividerTypeMapping;
264
dividerTypeMapping["solid_line"] = "solid";
265
dividerTypeMapping["dashed_line"] = "broken";
266
dividerTypeMapping["double_solid_line"] = "solid solid";
267
dividerTypeMapping["no"] = "none";
268
269
// defaulting to solid as in the original code
270
std::string dividerType = "solid";
271
272
if (e->getParametersMap().count("divider") > 0) {
273
std::string divider = e->getParametersMap().find("divider")->second;
274
if (dividerTypeMapping.count(divider) > 0) {
275
dividerType = dividerTypeMapping.find(divider)->second;
276
}
277
}
278
return dividerType;
279
}
280
281
282
void
283
NWWriter_OpenDrive::writeNormalEdge(OutputDevice& device, const NBEdge* e,
284
int edgeID, int fromNodeID, int toNodeID,
285
const bool origNames,
286
const double straightThresh,
287
const ShapeContainer& shc,
288
SignalLanes& signalLanes,
289
const std::vector<std::string>& crossings) {
290
// buffer output because some fields are computed out of order
291
OutputDevice_String elevationOSS(3);
292
elevationOSS.setPrecision(8);
293
OutputDevice_String planViewOSS(2);
294
planViewOSS.setPrecision(8);
295
double length = 0;
296
297
planViewOSS.openTag("planView");
298
// for the shape we need to use the leftmost border of the leftmost lane
299
const std::vector<NBEdge::Lane>& lanes = e->getLanes();
300
PositionVector ls = getInnerLaneBorder(e);
301
#ifdef DEBUG_SMOOTH_GEOM
302
if (DEBUGCOND) {
303
std::cout << "write planview for edge " << e->getID() << "\n";
304
}
305
#endif
306
307
if (ls.size() == 2 || e->getPermissions() == SVC_PEDESTRIAN) {
308
// foot paths may contain sharp angles
309
length = writeGeomLines(ls, planViewOSS, elevationOSS);
310
} else {
311
bool ok = writeGeomSmooth(ls, e->getSpeed(), planViewOSS, elevationOSS, straightThresh, length);
312
if (!ok) {
313
WRITE_WARNINGF(TL("Could not compute smooth shape for edge '%'."), e->getID());
314
}
315
}
316
planViewOSS.closeTag();
317
318
device.openTag("road");
319
device.writeAttr("name", StringUtils::escapeXML(e->getStreetName()));
320
device.setPrecision(8); // length requires higher precision
321
device.writeAttr("length", MAX2(POSITION_EPS, length));
322
device.setPrecision(gPrecision);
323
device.writeAttr("id", edgeID);
324
device.writeAttr("junction", -1);
325
if (fromNodeID != INVALID_ID || toNodeID != INVALID_ID) {
326
device.openTag("link");
327
if (fromNodeID != INVALID_ID) {
328
device.openTag("predecessor");
329
device.writeAttr("elementType", "junction");
330
device.writeAttr("elementId", fromNodeID);
331
device.closeTag();
332
}
333
if (toNodeID != INVALID_ID) {
334
device.openTag("successor");
335
device.writeAttr("elementType", "junction");
336
device.writeAttr("elementId", toNodeID);
337
device.closeTag();
338
}
339
device.closeTag();
340
}
341
device.openTag("type").writeAttr("s", 0).writeAttr("type", "town").closeTag();
342
device << planViewOSS.getString();
343
writeElevationProfile(ls, device, elevationOSS);
344
device << " <lateralProfile/>\n";
345
device << " <lanes>\n";
346
device << " <laneSection s=\"0\">\n";
347
const std::string centerMark = e->getPermissions(e->getNumLanes() - 1) == 0 ? "none" : getDividerType(e);
348
if (!LHLL) {
349
writeEmptyCenterLane(device, centerMark, 0.13);
350
}
351
const std::string side = LHLL ? "left" : "right";
352
device << " <" << side << ">\n";
353
const int numLanes = e->getNumLanes();
354
for (int jRH = numLanes; --jRH >= 0;) {
355
// XODR always has the lanes left to right (by default this is
356
// inner-to-outer but in LH networks its outer-to-inner)
357
const int j = lefthand ? numLanes - 1 - jRH : jRH;
358
std::string laneType = e->getLaneStruct(j).type;
359
if (laneType == "") {
360
laneType = getLaneType(e->getPermissions(j));
361
}
362
device << " <lane id=\"" << s2x(j, numLanes) << "\" type=\"" << laneType << "\" level=\"true\">\n";
363
device << " <link/>\n";
364
// this could be used for geometry-link junctions without u-turn,
365
// predecessor and sucessors would be lane indices,
366
// road predecessor / succesfors would be of type 'road' rather than
367
// 'junction'
368
//device << " <predecessor id=\"-1\"/>\n";
369
//device << " <successor id=\"-1\"/>\n";
370
//device << " </link>\n";
371
device << " <width sOffset=\"0\" a=\"" << e->getLaneWidth(j) << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
372
std::string markType = "broken";
373
if (LHRL) {
374
if (j == numLanes - 1) {
375
// solid road mark in the middle of the road
376
markType = "solid";
377
} else if ((e->getPermissions(j) & ~(SVC_PEDESTRIAN | SVC_BICYCLE)) == 0) {
378
// solid road mark to the right of sidewalk or bicycle lane
379
markType = "solid";
380
}
381
} else {
382
if (j == 0) {
383
markType = "solid";
384
} else if (j > 0
385
&& (e->getPermissions(j - 1) & ~(SVC_PEDESTRIAN | SVC_BICYCLE)) == 0) {
386
// solid road mark to the left of sidewalk or bicycle lane
387
markType = "solid";
388
} else if (e->getPermissions(j) == 0) {
389
// solid road mark to the right of a forbidden lane
390
markType = "solid";
391
}
392
}
393
device << " <roadMark sOffset=\"0\" type=\"" << markType << "\" weight=\"standard\" color=\"standard\" width=\"0.13\"/>\n";
394
device << " <speed sOffset=\"0\" max=\"" << lanes[j].speed << "\"/>\n";
395
device << " </lane>\n";
396
}
397
device << " </" << side << ">\n";
398
if (LHLL) {
399
writeEmptyCenterLane(device, centerMark, 0.13);
400
}
401
device << " </laneSection>\n";
402
device << " </lanes>\n";
403
writeRoadObjects(device, e, shc, crossings);
404
writeSignals(device, e, length, signalLanes, shc);
405
if (origNames) {
406
device << " <userData code=\"sumoId\" value=\"" << e->getID() << "\"/>\n";
407
}
408
device.closeTag();
409
checkLaneGeometries(e);
410
}
411
412
void
413
NWWriter_OpenDrive::addPedestrianConnection(const NBEdge* inEdge, const NBEdge* outEdge, std::vector<NBEdge::Connection>& parallel) {
414
// by default there are no internal lanes for pedestrians. Determine if
415
// one is feasible and does not exist yet.
416
if (outEdge != nullptr
417
&& inEdge->getPermissions(0) == SVC_PEDESTRIAN
418
&& outEdge->getPermissions(0) == SVC_PEDESTRIAN
419
&& (parallel.empty()
420
|| parallel.front().fromLane != 0
421
|| parallel.front().toLane != 0)) {
422
parallel.insert(parallel.begin(), NBEdge::Connection(0, const_cast<NBEdge*>(outEdge), 0));
423
parallel.front().vmax = (inEdge->getLanes()[0].speed + outEdge->getLanes()[0].speed) / (double) 2.0;
424
}
425
}
426
427
428
int
429
NWWriter_OpenDrive::writeInternalEdge(OutputDevice& device, OutputDevice& junctionDevice, const NBEdge* inEdge, int nodeID,
430
int edgeID, int inEdgeID, int outEdgeID,
431
int connectionID,
432
const std::vector<NBEdge::Connection>& parallel,
433
const bool isOuterEdge,
434
const double straightThresh,
435
const std::string& centerMark,
436
SignalLanes& signalLanes) {
437
assert(parallel.size() != 0);
438
const NBEdge::Connection& cLeft = LHRL ? parallel.front() : parallel.back();
439
const NBEdge* outEdge = cLeft.toEdge;
440
PositionVector begShape = getInnerLaneBorder(inEdge, cLeft.fromLane);
441
PositionVector endShape = getInnerLaneBorder(outEdge, cLeft.toLane);
442
//std::cout << "computing reference line for internal lane " << cLeft.getInternalLaneID() << " begLane=" << inEdge->getLaneShape(cLeft.fromLane) << " endLane=" << outEdge->getLaneShape(cLeft.toLane) << "\n";
443
444
double length;
445
double laneOffset = 0;
446
PositionVector fallBackShape;
447
fallBackShape.push_back(begShape.back());
448
fallBackShape.push_back(endShape.front());
449
const bool turnaround = inEdge->isTurningDirectionAt(outEdge);
450
bool ok = true;
451
PositionVector init = NBNode::bezierControlPoints(begShape, endShape, turnaround, 25, 25, ok, nullptr, straightThresh);
452
if (init.size() == 0) {
453
length = fallBackShape.length2D();
454
// problem with turnarounds is known, method currently returns 'ok' (#2539)
455
if (!ok) {
456
WRITE_WARNINGF(TL("Could not compute smooth shape from lane '%' to lane '%'. Use option 'junctions.scurve-stretch' or increase radius of junction '%' to fix this."), inEdge->getLaneID(cLeft.fromLane), outEdge->getLaneID(cLeft.toLane), inEdge->getToNode()->getID());
457
} else if (length <= NUMERICAL_EPS) {
458
// left-curving geometry-like edges must use the right
459
// side as reference line and shift
460
begShape = getOuterLaneBorder(inEdge, cLeft.fromLane);
461
endShape = getOuterLaneBorder(outEdge, cLeft.toLane);
462
init = NBNode::bezierControlPoints(begShape, endShape, turnaround, 25, 25, ok, nullptr, straightThresh);
463
if (init.size() != 0) {
464
length = init.bezier(12).length2D();
465
laneOffset = outEdge->getLaneWidth(cLeft.toLane);
466
//std::cout << " internalLane=" << cLeft.getInternalLaneID() << " length=" << length << "\n";
467
}
468
}
469
} else {
470
length = init.bezier(12).length2D();
471
}
472
double roadLength = MAX2(POSITION_EPS, length);
473
474
junctionDevice << " <connection id=\"" << connectionID << "\" incomingRoad=\"" << inEdgeID << "\" connectingRoad=\"" << edgeID << "\" contactPoint=\"start\">\n";
475
device.openTag("road");
476
device.writeAttr("name", cLeft.id);
477
device.setPrecision(8); // length requires higher precision
478
device.writeAttr("length", roadLength);
479
device.setPrecision(gPrecision);
480
device.writeAttr("id", edgeID);
481
device.writeAttr("junction", nodeID);
482
device.openTag("link");
483
device.openTag("predecessor");
484
device.writeAttr("elementType", "road");
485
device.writeAttr("elementId", inEdgeID);
486
device.writeAttr("contactPoint", "end");
487
device.closeTag();
488
device.openTag("successor");
489
device.writeAttr("elementType", "road");
490
device.writeAttr("elementId", outEdgeID);
491
device.writeAttr("contactPoint", "start");
492
device.closeTag();
493
device.closeTag();
494
device.openTag("type").writeAttr("s", 0).writeAttr("type", "town").closeTag();
495
device.openTag("planView");
496
device.setPrecision(8); // geometry hdg requires higher precision
497
OutputDevice_String elevationOSS(3);
498
elevationOSS.setPrecision(8);
499
#ifdef DEBUG_SMOOTH_GEOM
500
if (DEBUGCOND) {
501
std::cout << "write planview for internal edge " << cLeft.id << " init=" << init << " fallback=" << fallBackShape
502
<< " begShape=" << begShape << " endShape=" << endShape
503
<< "\n";
504
}
505
#endif
506
if (init.size() == 0) {
507
writeGeomLines(fallBackShape, device, elevationOSS);
508
} else {
509
writeGeomPP3(device, elevationOSS, init, length);
510
}
511
device.setPrecision(gPrecision);
512
device.closeTag();
513
writeElevationProfile(fallBackShape, device, elevationOSS);
514
device << " <lateralProfile/>\n";
515
device << " <lanes>\n";
516
if (laneOffset != 0) {
517
device << " <laneOffset s=\"0\" a=\"" << laneOffset << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
518
}
519
device << " <laneSection s=\"0\">\n";
520
if (!lefthand) {
521
writeEmptyCenterLane(device, centerMark, 0);
522
}
523
const std::string side = lefthand ? "left" : "right";
524
device << " <" << side << ">\n";
525
const int numLanes = (int)parallel.size();
526
for (int jRH = numLanes; --jRH >= 0;) {
527
const int j = lefthand ? numLanes - 1 - jRH : jRH;
528
const int xJ = s2x(j, numLanes);
529
const NBEdge::Connection& c = parallel[j];
530
const int fromIndex = s2x(c.fromLane, inEdge->getNumLanes());
531
const int toIndex = s2x(c.toLane, outEdge->getNumLanes());
532
533
double inEdgeWidth = inEdge->getLaneWidth(c.fromLane);
534
double outEdgeWidth = outEdge->getLaneWidth(c.toLane);
535
// Ideally a polynomial function of third order would be needed for more precision.
536
// This is obtained by specifying c and d coefficients, keeping it simple and linear
537
// as we only know final and initial width.
538
double bCoefficient = (outEdgeWidth - inEdgeWidth) / roadLength;
539
double cCoefficient = 0;
540
double dCoefficient = 0;
541
device << " <lane id=\"" << xJ << "\" type=\"" << getLaneType(outEdge->getPermissions(c.toLane)) << "\" level=\"true\">\n";
542
device << " <link>\n";
543
device << " <predecessor id=\"" << fromIndex << "\"/>\n";
544
device << " <successor id=\"" << toIndex << "\"/>\n";
545
device << " </link>\n";
546
device << " <width sOffset=\"0\" a=\"" << inEdgeWidth << "\" b=\"" << bCoefficient << "\" c=\"" << cCoefficient << "\" d=\"" << dCoefficient << "\"/>\n";
547
std::string markType = "broken";
548
if (inEdge->isTurningDirectionAt(outEdge)) {
549
markType = "none";
550
} else if (c.fromLane == 0 && c.toLane == 0 && isOuterEdge) {
551
// solid road mark at the outer border
552
markType = "solid";
553
} else if (isOuterEdge && j > 0
554
&& (outEdge->getPermissions(parallel[j - 1].toLane) & ~(SVC_PEDESTRIAN | SVC_BICYCLE)) == 0) {
555
// solid road mark to the left of sidewalk or bicycle lane
556
markType = "solid";
557
} else if (!inEdge->getToNode()->geometryLike()) {
558
// draw shorter road marks to indicate turning paths
559
LinkDirection dir = inEdge->getToNode()->getDirection(inEdge, outEdge, lefthand);
560
if (dir == LinkDirection::LEFT || dir == LinkDirection::RIGHT || dir == LinkDirection::PARTLEFT || dir == LinkDirection::PARTRIGHT) {
561
// XXX <type><line/><type> is not rendered by odrViewer so cannot be validated
562
// device << " <type name=\"broken\" width=\"0.13\">\n";
563
// device << " <line length=\"0.5\" space=\"0.5\" tOffset=\"0\" sOffset=\"0\" rule=\"none\"/>\n";
564
// device << " </type>\n";
565
markType = "none";
566
}
567
}
568
device << " <roadMark sOffset=\"0\" type=\"" << markType << "\" weight=\"standard\" color=\"standard\" width=\"0.13\"/>\n";
569
device << " <speed sOffset=\"0\" max=\"" << c.vmax << "\"/>\n";
570
device << " </lane>\n";
571
572
junctionDevice << " <laneLink from=\"" << fromIndex << "\" to=\"" << xJ << "\"/>\n";
573
connectionID++;
574
}
575
device << " </" << side << ">\n";
576
if (lefthand) {
577
writeEmptyCenterLane(device, centerMark, 0);
578
}
579
device << " </laneSection>\n";
580
device << " </lanes>\n";
581
device << " <objects/>\n";
582
UNUSED_PARAMETER(signalLanes);
583
device << " <signals/>\n";
584
device.closeTag();
585
junctionDevice << " </connection>\n";
586
587
return connectionID;
588
}
589
590
591
double
592
NWWriter_OpenDrive::writeGeomLines(const PositionVector& shape, OutputDevice& device, OutputDevice& elevationDevice, double offset) {
593
for (int j = 0; j < (int)shape.size() - 1; ++j) {
594
const Position& p = shape[j];
595
const Position& p2 = shape[j + 1];
596
const double hdg = shape.angleAt2D(j);
597
const double length = p.distanceTo2D(p2);
598
device.openTag("geometry");
599
device.writeAttr("s", offset);
600
device.writeAttr("x", p.x());
601
device.writeAttr("y", p.y());
602
device.writeAttr("hdg", hdg);
603
device.writeAttr("length", length < 1e-8 ? 1e-8 : length);
604
device.openTag("line").closeTag();
605
device.closeTag();
606
elevationDevice << " <elevation s=\"" << offset << "\" a=\"" << p.z() << "\" b=\"" << (p2.z() - p.z()) / MAX2(POSITION_EPS, length) << "\" c=\"0\" d=\"0\"/>\n";
607
offset += length;
608
}
609
return offset;
610
}
611
612
613
void
614
NWWriter_OpenDrive::writeEmptyCenterLane(OutputDevice& device, const std::string& mark, double markWidth) {
615
device << " <center>\n";
616
device << " <lane id=\"0\" type=\"none\" level=\"true\">\n";
617
device << " <link/>\n";
618
device << " <roadMark sOffset=\"0\" type=\"" << mark << "\" weight=\"standard\" color=\"standard\" width=\"" << markWidth << "\"/>\n";
619
device << " </lane>\n";
620
device << " </center>\n";
621
}
622
623
624
int
625
NWWriter_OpenDrive::getID(const std::string& origID, StringBijection<int>& map, int& lastID) {
626
if (map.hasString(origID)) {
627
return map.get(origID);
628
}
629
map.insert(origID, lastID++);
630
return lastID - 1;
631
}
632
633
634
std::string
635
NWWriter_OpenDrive::getLaneType(SVCPermissions permissions) {
636
switch (permissions) {
637
case SVC_PEDESTRIAN:
638
return "sidewalk";
639
//case (SVC_BICYCLE | SVC_PEDESTRIAN):
640
// WRITE_WARNING("Ambiguous lane type (biking+driving) for road '" + roadID + "'");
641
// return "sidewalk";
642
case SVC_BICYCLE:
643
return "biking";
644
case 0:
645
// ambiguous
646
return "none";
647
case SVC_RAIL:
648
case SVC_RAIL_URBAN:
649
case SVC_RAIL_ELECTRIC:
650
case SVC_RAIL_FAST:
651
return "rail";
652
case SVC_TRAM:
653
return "tram";
654
default: {
655
// complex permissions
656
if (permissions == SVCAll) {
657
return "driving";
658
} else if (isRailway(permissions)) {
659
return "rail";
660
} else if ((permissions & SVC_PASSENGER) != 0) {
661
return "driving";
662
} else {
663
return "restricted";
664
}
665
}
666
}
667
}
668
669
670
PositionVector
671
NWWriter_OpenDrive::getInnerLaneBorder(const NBEdge* edge, int laneIndex, double widthOffset) {
672
if (laneIndex == -1) {
673
// innermost lane
674
laneIndex = (int)edge->getNumLanes() - 1;
675
if (LHRL) {
676
laneIndex = 0;
677
}
678
}
679
PositionVector result = edge->getLaneShape(laneIndex);
680
widthOffset -= (LHLL ? -1 : 1) * edge->getLaneWidth(laneIndex) / 2;
681
try {
682
result.move2side(widthOffset);
683
} catch (InvalidArgument&) { }
684
return result;
685
}
686
687
688
PositionVector
689
NWWriter_OpenDrive::getOuterLaneBorder(const NBEdge* edge, int laneIndex) {
690
return getInnerLaneBorder(edge, laneIndex, edge->getLaneWidth(laneIndex));
691
}
692
693
694
double
695
NWWriter_OpenDrive::writeGeomPP3(
696
OutputDevice& device,
697
OutputDevice& elevationDevice,
698
PositionVector init,
699
double length,
700
double offset) {
701
assert(init.size() == 3 || init.size() == 4);
702
703
// avoid division by 0
704
length = MAX2(POSITION_EPS, length);
705
706
const Position p = init.front();
707
const double hdg = init.angleAt2D(0);
708
709
// backup elevation values
710
const PositionVector initZ = init;
711
// translate to u,v coordinates
712
init.add(-p.x(), -p.y(), -p.z());
713
init.rotate2D(-hdg);
714
715
// parametric coefficients
716
double aU, bU, cU, dU;
717
double aV, bV, cV, dV;
718
double aZ, bZ, cZ, dZ;
719
720
// unfactor the Bernstein polynomials of degree 2 (or 3) and collect the coefficients
721
if (init.size() == 3) {
722
//f(x, a, b ,c) = a + (2*b - 2*a)*x + (a - 2*b + c)*x*x
723
aU = init[0].x();
724
bU = 2 * init[1].x() - 2 * init[0].x();
725
cU = init[0].x() - 2 * init[1].x() + init[2].x();
726
dU = 0;
727
728
aV = init[0].y();
729
bV = 2 * init[1].y() - 2 * init[0].y();
730
cV = init[0].y() - 2 * init[1].y() + init[2].y();
731
dV = 0;
732
733
// elevation is not parameteric on [0:1] but on [0:length]
734
aZ = initZ[0].z();
735
bZ = (2 * initZ[1].z() - 2 * initZ[0].z()) / length;
736
cZ = (initZ[0].z() - 2 * initZ[1].z() + initZ[2].z()) / (length * length);
737
dZ = 0;
738
739
} else {
740
// f(x, a, b, c, d) = a + (x*((3*b) - (3*a))) + ((x*x)*((3*a) + (3*c) - (6*b))) + ((x*x*x)*((3*b) - (3*c) - a + d))
741
aU = init[0].x();
742
bU = 3 * init[1].x() - 3 * init[0].x();
743
cU = 3 * init[0].x() - 6 * init[1].x() + 3 * init[2].x();
744
dU = -init[0].x() + 3 * init[1].x() - 3 * init[2].x() + init[3].x();
745
746
aV = init[0].y();
747
bV = 3 * init[1].y() - 3 * init[0].y();
748
cV = 3 * init[0].y() - 6 * init[1].y() + 3 * init[2].y();
749
dV = -init[0].y() + 3 * init[1].y() - 3 * init[2].y() + init[3].y();
750
751
// elevation is not parameteric on [0:1] but on [0:length]
752
aZ = initZ[0].z();
753
bZ = (3 * initZ[1].z() - 3 * initZ[0].z()) / length;
754
cZ = (3 * initZ[0].z() - 6 * initZ[1].z() + 3 * initZ[2].z()) / (length * length);
755
dZ = (-initZ[0].z() + 3 * initZ[1].z() - 3 * initZ[2].z() + initZ[3].z()) / (length * length * length);
756
}
757
758
device.openTag("geometry");
759
device.writeAttr("s", offset);
760
device.writeAttr("x", p.x());
761
device.writeAttr("y", p.y());
762
device.writeAttr("hdg", hdg);
763
device.writeAttr("length", length);
764
765
device.openTag("paramPoly3");
766
device.writeAttr("aU", aU);
767
device.writeAttr("bU", bU);
768
device.writeAttr("cU", cU);
769
device.writeAttr("dU", dU);
770
device.writeAttr("aV", aV);
771
device.writeAttr("bV", bV);
772
device.writeAttr("cV", cV);
773
device.writeAttr("dV", dV);
774
device.writeAttr("pRange", "normalized");
775
device.closeTag();
776
device.closeTag();
777
778
// write elevation
779
elevationDevice.openTag("elevation");
780
elevationDevice.writeAttr("s", offset);
781
elevationDevice.writeAttr("a", aZ);
782
elevationDevice.writeAttr("b", bZ);
783
elevationDevice.writeAttr("c", cZ);
784
elevationDevice.writeAttr("d", dZ);
785
elevationDevice.closeTag();
786
787
return offset + length;
788
}
789
790
791
bool
792
NWWriter_OpenDrive::writeGeomSmooth(const PositionVector& shape, double speed, OutputDevice& device, OutputDevice& elevationDevice, double straightThresh, double& length) {
793
#ifdef DEBUG_SMOOTH_GEOM
794
if (DEBUGCOND) {
795
std::cout << "writeGeomSmooth\n n=" << shape.size() << " shape=" << toString(shape) << "\n";
796
}
797
#endif
798
bool ok = true;
799
const double longThresh = speed; // 16.0; // make user-configurable (should match the sampling rate of the source data)
800
const double curveCutout = longThresh / 2; // 8.0; // make user-configurable (related to the maximum turning rate)
801
// the length of the segment that is added for cutting a corner can be bounded by 2*curveCutout (prevent the segment to be classified as 'long')
802
assert(longThresh >= 2 * curveCutout);
803
assert(shape.size() > 2);
804
// add intermediate points wherever there is a strong angular change between long segments
805
// assume the geometry is simplified so as not to contain consecutive colinear points
806
PositionVector shape2 = shape;
807
double maxAngleDiff = 0;
808
double offset = 0;
809
for (int j = 1; j < (int)shape.size() - 1; ++j) {
810
//const double hdg = shape.angleAt2D(j);
811
const Position& p0 = shape[j - 1];
812
const Position& p1 = shape[j];
813
const Position& p2 = shape[j + 1];
814
const double dAngle = fabs(GeomHelper::angleDiff(p0.angleTo2D(p1), p1.angleTo2D(p2)));
815
const double length1 = p0.distanceTo2D(p1);
816
const double length2 = p1.distanceTo2D(p2);
817
maxAngleDiff = MAX2(maxAngleDiff, dAngle);
818
#ifdef DEBUG_SMOOTH_GEOM
819
if (DEBUGCOND) {
820
std::cout << " j=" << j << " dAngle=" << RAD2DEG(dAngle) << " length1=" << length1 << " length2=" << length2 << "\n";
821
}
822
#endif
823
if (dAngle > straightThresh
824
&& (length1 > longThresh || j == 1)
825
&& (length2 > longThresh || j == (int)shape.size() - 2)) {
826
// NBNode::bezierControlPoints checks for minimum length of POSITION_EPS so we make sure there is no instability
827
shape2.insertAtClosest(shape.positionAtOffset2D(offset + length1 - MIN2(length1 - 2 * POSITION_EPS, curveCutout)), false);
828
shape2.insertAtClosest(shape.positionAtOffset2D(offset + length1 + MIN2(length2 - 2 * POSITION_EPS, curveCutout)), false);
829
shape2.removeClosest(p1);
830
}
831
offset += length1;
832
}
833
const int numPoints = (int)shape2.size();
834
#ifdef DEBUG_SMOOTH_GEOM
835
if (DEBUGCOND) {
836
std::cout << " n=" << numPoints << " shape2=" << toString(shape2) << "\n";
837
}
838
#endif
839
840
if (maxAngleDiff < straightThresh) {
841
length = writeGeomLines(shape2, device, elevationDevice, 0);
842
#ifdef DEBUG_SMOOTH_GEOM
843
if (DEBUGCOND) {
844
std::cout << " special case: all lines. maxAngleDiff=" << maxAngleDiff << "\n";
845
}
846
#endif
847
return ok;
848
}
849
850
// write the long segments as lines, short segments as curves
851
offset = 0;
852
for (int j = 0; j < numPoints - 1; ++j) {
853
const Position& p0 = shape2[j];
854
const Position& p1 = shape2[j + 1];
855
PositionVector line;
856
line.push_back(p0);
857
line.push_back(p1);
858
const double lineLength = line.length2D();
859
if (lineLength >= longThresh) {
860
offset = writeGeomLines(line, device, elevationDevice, offset);
861
#ifdef DEBUG_SMOOTH_GEOM
862
if (DEBUGCOND) {
863
std::cout << " writeLine=" << toString(line) << "\n";
864
}
865
#endif
866
} else {
867
// find control points
868
PositionVector begShape;
869
PositionVector endShape;
870
if (j == 0 || j == numPoints - 2) {
871
// keep the angle of the first/last segment but end at the front of the shape
872
begShape = line;
873
begShape.add(p0 - begShape.back());
874
} else if (j == 1 || p0.distanceTo2D(shape2[j - 1]) > longThresh) {
875
// use the previous segment if it is long or the first one
876
begShape.push_back(shape2[j - 1]);
877
begShape.push_back(p0);
878
} else {
879
// end at p0 with mean angle of the previous and current segment
880
begShape.push_back(shape2[j - 1]);
881
begShape.push_back(p1);
882
begShape.add(p0 - begShape.back());
883
}
884
885
if (j == 0 || j == numPoints - 2) {
886
// keep the angle of the first/last segment but start at the end of the shape
887
endShape = line;
888
endShape.add(p1 - endShape.front());
889
} else if (j == numPoints - 3 || p1.distanceTo2D(shape2[j + 2]) > longThresh) {
890
// use the next segment if it is long or the final one
891
endShape.push_back(p1);
892
endShape.push_back(shape2[j + 2]);
893
} else {
894
// start at p1 with mean angle of the current and next segment
895
endShape.push_back(p0);
896
endShape.push_back(shape2[j + 2]);
897
endShape.add(p1 - endShape.front());
898
}
899
const double extrapolateLength = MIN2((double)25, lineLength / 4);
900
PositionVector init = NBNode::bezierControlPoints(begShape, endShape, false, extrapolateLength, extrapolateLength, ok, nullptr, straightThresh);
901
if (init.size() == 0) {
902
// could not compute control points, write line
903
offset = writeGeomLines(line, device, elevationDevice, offset);
904
#ifdef DEBUG_SMOOTH_GEOM
905
if (DEBUGCOND) {
906
std::cout << " writeLine lineLength=" << lineLength << " begShape" << j << "=" << toString(begShape) << " endShape" << j << "=" << toString(endShape) << " init" << j << "=" << toString(init) << "\n";
907
}
908
#endif
909
} else {
910
// write bezier
911
const double curveLength = init.bezier(12).length2D();
912
offset = writeGeomPP3(device, elevationDevice, init, curveLength, offset);
913
#ifdef DEBUG_SMOOTH_GEOM
914
if (DEBUGCOND) {
915
std::cout << " writeCurve lineLength=" << lineLength << " curveLength=" << curveLength << " begShape" << j << "=" << toString(begShape) << " endShape" << j << "=" << toString(endShape) << " init" << j << "=" << toString(init) << "\n";
916
}
917
#endif
918
}
919
}
920
}
921
length = offset;
922
return ok;
923
}
924
925
926
void
927
NWWriter_OpenDrive::writeElevationProfile(const PositionVector& shape, OutputDevice& device, const OutputDevice_String& elevationDevice) {
928
// check if the shape is flat
929
bool flat = true;
930
double z = shape.size() == 0 ? 0 : shape[0].z();
931
for (int i = 1; i < (int)shape.size(); ++i) {
932
if (fabs(shape[i].z() - z) > NUMERICAL_EPS) {
933
flat = false;
934
break;
935
}
936
}
937
device << " <elevationProfile>\n";
938
if (flat) {
939
device << " <elevation s=\"0\" a=\"" << z << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
940
} else {
941
device << elevationDevice.getString();
942
}
943
device << " </elevationProfile>\n";
944
945
}
946
947
948
void
949
NWWriter_OpenDrive::checkLaneGeometries(const NBEdge* e) {
950
if (e->getNumLanes() > 1) {
951
// compute 'stop line' of rightmost lane
952
const PositionVector shape0 = e->getLaneShape(0);
953
assert(shape0.size() >= 2);
954
const Position& from = shape0[-2];
955
const Position& to = shape0[-1];
956
PositionVector stopLine;
957
stopLine.push_back(to);
958
stopLine.push_back(to - PositionVector::sideOffset(from, to, lefthand ? 1000 : -1000));
959
// endpoints of all other lanes should be on the stop line
960
for (int lane = 1; lane < e->getNumLanes(); ++lane) {
961
const double dist = stopLine.distance2D(e->getLaneShape(lane)[-1]);
962
if (dist > NUMERICAL_EPS) {
963
WRITE_WARNINGF(TL("Uneven stop line at lane '%' (dist=%) cannot be represented in OpenDRIVE."), e->getLaneID(lane), toString(dist));
964
}
965
}
966
}
967
}
968
969
void
970
NWWriter_OpenDrive::writeRoadObjects(OutputDevice& device, const NBEdge* e, const ShapeContainer& shc, const std::vector<std::string>& crossings) {
971
device.openTag("objects");
972
if (e->hasParameter(ROAD_OBJECTS)) {
973
device.setPrecision(8); // geometry hdg requires higher precision
974
PositionVector road = getInnerLaneBorder(e);
975
for (std::string id : StringTokenizer(e->getParameter(ROAD_OBJECTS, "")).getVector()) {
976
SUMOPolygon* p = shc.getPolygons().get(id);
977
if (p == nullptr) {
978
PointOfInterest* poi = shc.getPOIs().get(id);
979
if (poi == nullptr) {
980
WRITE_WARNINGF("Road object polygon or POI '%' not found for edge '%'", id, e->getID());
981
} else {
982
writeRoadObjectPOI(device, e, road, poi);
983
}
984
} else {
985
writeRoadObjectPoly(device, e, road, p);
986
}
987
}
988
device.setPrecision(gPrecision);
989
}
990
if (crossings.size() > 0) {
991
device.setPrecision(8); // geometry hdg requires higher precision
992
PositionVector road = getInnerLaneBorder(e);
993
for (size_t ic = 0; ic < crossings.size(); ic++) {
994
SUMOPolygon* p = shc.getPolygons().get(crossings[ic]);
995
if (p != 0) {
996
writeRoadObjectPoly(device, e, road, p);
997
}
998
}
999
device.setPrecision(gPrecision);
1000
}
1001
device.closeTag();
1002
}
1003
1004
1005
std::vector<NWWriter_OpenDrive::TrafficSign>
1006
NWWriter_OpenDrive::parseTrafficSign(const std::string& trafficSign, PointOfInterest* poi) {
1007
std::vector<TrafficSign> result;
1008
// check for maxspeed, stop, give_way and hazard
1009
if (trafficSign == "maxspeed" && poi->hasParameter("maxspeed")) {
1010
result.push_back(TrafficSign{ "OpenDrive", "maxspeed", "", poi->getParameter("maxspeed")});
1011
} else if (trafficSign == "stop") {
1012
result.push_back(TrafficSign{ "OpenDrive", trafficSign, "", "" });
1013
} else if (trafficSign == "give_way") {
1014
result.push_back(TrafficSign{ "OpenDrive", trafficSign, "", "" });
1015
} else if (trafficSign == "hazard" && poi->hasParameter("hazard")) {
1016
result.push_back(TrafficSign{ "OpenDrive", trafficSign, poi->getParameter("hazard"), "" });
1017
} else {
1018
if (trafficSign.find_first_of(",;") != std::string::npos) {
1019
std::string::size_type colon = trafficSign.find(':');
1020
const std::string country = trafficSign.substr(0, colon);
1021
const std::string remaining = trafficSign.substr(colon + 1);
1022
1023
std::vector<std::string> tokens;
1024
std::string::size_type lastPos = 0;
1025
std::string::size_type pos = remaining.find_first_of(",;");
1026
while (pos != std::string::npos) {
1027
// add country and colon before pushing the token
1028
tokens.push_back(country + ":" + remaining.substr(lastPos, pos - lastPos));
1029
lastPos = pos + 1;
1030
pos = remaining.find_first_of(",;", lastPos);
1031
}
1032
tokens.push_back(country + ":" + remaining.substr(lastPos));
1033
// call for each token the parseTrafficSignId function and fill the result vector
1034
for (std::string token : tokens) {
1035
result.push_back(parseTrafficSignId(token));
1036
}
1037
} else {
1038
result.push_back(parseTrafficSignId(trafficSign));
1039
}
1040
}
1041
return result;
1042
}
1043
1044
1045
NWWriter_OpenDrive::TrafficSign
1046
NWWriter_OpenDrive::parseTrafficSignId(const std::string& trafficSign) {
1047
// for OpenDrive 1.4 the country code is specified as ISO 3166-1, alpha-3,
1048
// but OSM uses ISO 3166-1, alpha-2. In order to be OpenDrive 1.4 compliant
1049
// we would need to convert it back to alpha-3. However, newer OpenDrive
1050
// versions support alpha-2, so we use that instead.
1051
1052
//std::regex re("([A-Z]{2}):([0-9]{1,3})(?:\\[([0-9]{1,3})\\])?");
1053
1054
// This regex is used to parse the traffic sign id. It matches the following groups:
1055
// 1. country code (2 letters)
1056
// 2. sign id (1-4 digits) e.g. 1020 or 274.1
1057
// 3. value in brackets e.g. 1020[50] or 274.1[50] -> 50
1058
// 4. value after the hyphen 1020-50 or 274.1-50 -> 50
1059
std::regex re("([A-Z]{2}):([0-9.]{1,6}|[A-Z]{1}[0-9.]{2,6})(?:\\[(.*)\\])?(?:-(.*))?");
1060
std::smatch match;
1061
std::regex_match(trafficSign, match, re);
1062
if (match.size() == 5) {
1063
std::string value;
1064
if (match[3].matched) {
1065
value = match[3].str();
1066
} else if (match[4].matched) {
1067
value = match[4].str();
1068
}
1069
return TrafficSign{ match[1], match[2], "", value};
1070
} else {
1071
return TrafficSign{ "OpenDrive", trafficSign, "", ""};
1072
}
1073
}
1074
1075
1076
void
1077
NWWriter_OpenDrive::writeSignals(OutputDevice& device, const NBEdge* e, double length,
1078
SignalLanes& signalLanes, const ShapeContainer& shc) {
1079
device.openTag("signals");
1080
if (e->getToNode()->isTLControlled()) {
1081
// try to faithfully represent the SUMO signal layout
1082
// (if a realistic number of signals is needed, the user should set
1083
// option --tls.group-signals)
1084
NBTrafficLightDefinition* tl = *e->getToNode()->getControllingTLS().begin();
1085
std::map<std::string, bool> toWrite;
1086
for (const NBConnection& c : tl->getControlledLinks()) {
1087
if (c.getFrom() == e) {
1088
const std::string id = tl->getID() + "_" + toString(c.getTLIndex());
1089
if (toWrite.count(id) == 0) {
1090
toWrite[id] = signalLanes.count(id) == 0;
1091
}
1092
signalLanes[id].first.insert(c.getFromLane());
1093
signalLanes[id].second.insert(e->getToNode()->getDirection(e, c.getTo()));
1094
}
1095
}
1096
for (auto item : toWrite) {
1097
const std::string id = item.first;
1098
const bool isNew = item.second;
1099
const std::set<LinkDirection>& dirs = signalLanes[id].second;
1100
const bool l = dirs.count(LinkDirection::LEFT) != 0 || dirs.count(LinkDirection::PARTLEFT) != 0;
1101
const bool r = dirs.count(LinkDirection::RIGHT) != 0 || dirs.count(LinkDirection::PARTRIGHT) != 0;
1102
const bool s = dirs.count(LinkDirection::STRAIGHT) != 0;
1103
const std::string tag = isNew ? "signal" : "signalReference";
1104
int firstLane = *signalLanes[id].first.begin();
1105
double t = e->getLaneWidth(firstLane) * 0.5;
1106
for (int i = firstLane + 1; i < e->getNumLanes(); i++) {
1107
t += e->getLaneWidth(i);
1108
}
1109
device.openTag(tag);
1110
device.writeAttr("id", id);
1111
device.setPrecision(8);
1112
device.writeAttr("s", length);
1113
device.writeAttr("t", -t);
1114
device.writeAttr("orientation", "+");
1115
if (isNew) {
1116
int type = 1000001;
1117
int subType = -1;
1118
if (l && !s && !r) {
1119
type = 1000011;
1120
subType = 10;
1121
} else if (!l && !s && r) {
1122
type = 1000011;
1123
subType = 20;
1124
} else if (!l && s && !r) {
1125
type = 1000011;
1126
subType = 30;
1127
} else if (l && s && !r) {
1128
type = 1000011;
1129
subType = 40;
1130
} else if (!l && s && r) {
1131
type = 1000011;
1132
subType = 50;
1133
}
1134
device.writeAttr("dynamic", "yes");
1135
device.writeAttr("zOffset", 5);
1136
device.writeAttr("country", "OpenDRIVE");
1137
device.writeAttr("type", type);
1138
device.writeAttr("subtype", subType);
1139
device.writeAttr("height", 0.78);
1140
device.writeAttr("width", 0.26);
1141
}
1142
for (int lane : signalLanes[id].first) {
1143
device.openTag("validity");
1144
device.writeAttr("fromLane", s2x(lane, e->getNumLanes()));
1145
device.writeAttr("toLane", s2x(lane, e->getNumLanes()));
1146
device.closeTag();
1147
}
1148
device.closeTag();
1149
}
1150
} else if (e->hasParameter(ROAD_OBJECTS)) {
1151
PositionVector roadShape = getInnerLaneBorder(e);
1152
for (std::string id : StringTokenizer(e->getParameter(ROAD_OBJECTS, "")).getVector()) {
1153
PointOfInterest* poi = shc.getPOIs().get(id);
1154
if (poi != nullptr) {
1155
if (poi->getShapeType() == "traffic_sign" && poi->hasParameter("traffic_sign")) {
1156
std::string traffic_sign_type = poi->getParameter("traffic_sign");
1157
1158
std::vector<TrafficSign> trafficSigns = parseTrafficSign(traffic_sign_type, poi);
1159
1160
auto distance = roadShape.nearest_offset_to_point2D(*poi, true);
1161
double t = getRoadSideOffset(e);
1162
double calculatedZOffset = 3.0;
1163
for (auto it = trafficSigns.rbegin(); it != trafficSigns.rend(); ++it) {
1164
TrafficSign trafficSign = *it;
1165
device.openTag("signal");
1166
device.writeAttr("id", id);
1167
device.writeAttr("s", distance);
1168
device.writeAttr("t", t);
1169
device.writeAttr("orientation", "-");
1170
device.writeAttr("dynamic", "no");
1171
device.writeAttr("zOffset", calculatedZOffset);
1172
device.writeAttr("country", trafficSign.country);
1173
device.writeAttr("type", trafficSign.type);
1174
device.writeAttr("subtype", trafficSign.subtype);
1175
device.writeAttr("value", trafficSign.value);
1176
device.writeAttr("height", 0.78);
1177
device.writeAttr("width", 0.78);
1178
device.closeTag();
1179
calculatedZOffset += 0.78;
1180
}
1181
}
1182
}
1183
}
1184
}
1185
device.closeTag();
1186
}
1187
1188
1189
double
1190
NWWriter_OpenDrive::getRoadSideOffset(const NBEdge* e) {
1191
double t = 0.30;
1192
if (!lefthand || LHLL) {
1193
for (int i = 0; i < e->getNumLanes(); i++) {
1194
t += e->getPermissions(i) == SVC_PEDESTRIAN ? e->getLaneWidth(i) * 0.2 : e->getLaneWidth(i);
1195
}
1196
}
1197
t = LHRL ? t : -t;
1198
return t;
1199
}
1200
1201
1202
int
1203
NWWriter_OpenDrive::s2x(int sumoIndex, int numLanes) {
1204
// sumo lanes: 0, 1, 2 (0 being the outermost lane)
1205
// XODR: -3,-2,-1 (written in reverse order)
1206
// LHLL: 3, 2, 1 (written in reverse order)
1207
// lefthand (old):-1,-2,-3
1208
return (lefthand
1209
? (LHLL
1210
? numLanes - sumoIndex
1211
: - sumoIndex - 1)
1212
: sumoIndex - numLanes);
1213
}
1214
1215
1216
void
1217
NWWriter_OpenDrive::mapmatchRoadObjects(const ShapeContainer& shc, const NBEdgeCont& ec) {
1218
if (shc.getPolygons().size() == 0 && shc.getPOIs().size() == 0) {
1219
return;
1220
}
1221
const double maxDist = OptionsCont::getOptions().getFloat("opendrive-output.shape-match-dist");
1222
if (maxDist < 0) {
1223
return;
1224
}
1225
// register custom assignements
1226
std::set<std::string> assigned;
1227
for (auto it = ec.begin(); it != ec.end(); ++it) {
1228
NBEdge* e = it->second;
1229
if (e->hasParameter(ROAD_OBJECTS)) {
1230
for (std::string id : StringTokenizer(e->getParameter(ROAD_OBJECTS, "")).getVector()) {
1231
assigned.insert(id);
1232
}
1233
}
1234
}
1235
// build rtree for edges
1236
NamedRTree r;
1237
for (auto it = ec.begin(); it != ec.end(); ++it) {
1238
NBEdge* edge = it->second;
1239
Boundary bound = edge->getGeometry().getBoxBoundary();
1240
bound.grow(maxDist);
1241
float min[2] = { static_cast<float>(bound.xmin()), static_cast<float>(bound.ymin()) };
1242
float max[2] = { static_cast<float>(bound.xmax()), static_cast<float>(bound.ymax()) };
1243
r.Insert(min, max, edge);
1244
}
1245
1246
for (auto itPoly = shc.getPolygons().begin(); itPoly != shc.getPolygons().end(); itPoly++) {
1247
SUMOPolygon* p = itPoly->second;
1248
Boundary bound = p->getShape().getBoxBoundary();
1249
float min[2] = { static_cast<float>(bound.xmin()), static_cast<float>(bound.ymin()) };
1250
float max[2] = { static_cast<float>(bound.xmax()), static_cast<float>(bound.ymax()) };
1251
std::set<const Named*> edges;
1252
Named::StoringVisitor visitor(edges);
1253
r.Search(min, max, visitor);
1254
std::vector<std::pair<double, std::string> > nearby;
1255
for (const Named* namedEdge : edges) {
1256
NBEdge* e = const_cast<NBEdge*>(dynamic_cast<const NBEdge*>(namedEdge));
1257
const double distance = VectorHelper<double>::minValue(p->getShape().distances(e->getLaneShape(0), true));
1258
if (distance <= maxDist) {
1259
// sort by distance and ID to stabilize results
1260
nearby.push_back(std::make_pair(distance, e->getID()));
1261
//std::cout << " poly=" << p->getID() << " e=" << e->getID() << " dist=" << distance << "\n";
1262
}
1263
}
1264
if (nearby.size() > 0) {
1265
std::sort(nearby.begin(), nearby.end());
1266
NBEdge* closest = ec.retrieve(nearby.front().second);
1267
std::string objects = closest->getParameter(ROAD_OBJECTS, "");
1268
if (objects != "") {
1269
objects += " ";
1270
}
1271
objects += p->getID();
1272
closest->setParameter(ROAD_OBJECTS, objects);
1273
//std::cout << "poly=" << p->getID() << " closest=" << closest->getID() << "\n";
1274
}
1275
}
1276
for (auto itPoi = shc.getPOIs().begin(); itPoi != shc.getPOIs().end(); itPoi++) {
1277
PointOfInterest* p = itPoi->second;
1278
float min[2] = { static_cast<float>(p->x()), static_cast<float>(p->y()) };
1279
float max[2] = { static_cast<float>(p->x()), static_cast<float>(p->y()) };
1280
std::set<const Named*> edges;
1281
Named::StoringVisitor visitor(edges);
1282
r.Search(min, max, visitor);
1283
std::vector<std::pair<double, std::string> > nearby;
1284
for (const Named* namedEdge : edges) {
1285
NBEdge* e = const_cast<NBEdge*>(dynamic_cast<const NBEdge*>(namedEdge));
1286
const double distance = e->getLaneShape(0).distance2D(*p, true);
1287
if (distance != GeomHelper::INVALID_OFFSET && distance <= maxDist) {
1288
// sort by distance and ID to stabilize results
1289
nearby.push_back(std::make_pair(distance, e->getID()));
1290
//if (p->getID() == "1275468911") {
1291
// std::cout << " poly=" << p->getID() << " e=" << e->getID() << " dist=" << distance << "\n";
1292
//}
1293
}
1294
}
1295
if (nearby.size() > 0) {
1296
std::sort(nearby.begin(), nearby.end());
1297
NBEdge* closest = ec.retrieve(nearby.front().second);
1298
std::string objects = closest->getParameter(ROAD_OBJECTS, "");
1299
if (objects != "") {
1300
objects += " ";
1301
}
1302
objects += p->getID();
1303
closest->setParameter(ROAD_OBJECTS, objects);
1304
//std::cout << "poly=" << p->getID() << " closest=" << closest->getID() << "\n";
1305
}
1306
}
1307
}
1308
1309
1310
void
1311
NWWriter_OpenDrive::writeRoadObjectPOI(OutputDevice& device, const NBEdge* e, const PositionVector& roadShape, const PointOfInterest* poi) {
1312
Position center = *poi;
1313
const double edgeOffset = roadShape.nearest_offset_to_point2D(center, false);
1314
if (edgeOffset == GeomHelper::INVALID_OFFSET) {
1315
WRITE_WARNINGF("Cannot map road object POI '%' with center % onto edge '%'", poi->getID(), center, e->getID());
1316
return;
1317
}
1318
1319
Position edgePos = roadShape.positionAtOffset2D(edgeOffset);
1320
double sideOffset = center.distanceTo2D(edgePos);
1321
// determine sign of sideOffset
1322
PositionVector tmp = roadShape.getSubpart2D(MAX2(0.0, edgeOffset - 1), MIN2(roadShape.length2D(), edgeOffset + 1));
1323
tmp.move2side(sideOffset);
1324
if (tmp.distance2D(center) < sideOffset) {
1325
sideOffset *= -1;
1326
}
1327
// place traffic signs on appropriate side of the road
1328
std::string type = poi->getShapeType();
1329
std::string name = StringUtils::escapeXML(poi->getParameter("name", ""), true);
1330
if (poi->getShapeType() == "traffic_sign") {
1331
sideOffset = getRoadSideOffset(e);
1332
type = "pole";
1333
name = "pole";
1334
}
1335
1336
device.openTag("object");
1337
device.writeAttr("id", poi->getID());
1338
device.writeAttr("type", type);
1339
device.writeAttr("name", name);
1340
device.writeAttr("s", edgeOffset);
1341
device.writeAttr("t", sideOffset);
1342
device.writeAttr("hdg", 0);
1343
device.closeTag();
1344
}
1345
1346
void
1347
NWWriter_OpenDrive::writeRoadObjectPoly(OutputDevice& device, const NBEdge* e, const PositionVector& roadShape, const SUMOPolygon* p) {
1348
PositionVector shape = p->getShape();
1349
Position center = shape.getPolygonCenter();
1350
1351
const double edgeOffset = roadShape.nearest_offset_to_point2D(center, false);
1352
if (edgeOffset == GeomHelper::INVALID_OFFSET) {
1353
WRITE_WARNINGF("Cannot map road object polygon '%' with center % onto edge '%'", p->getID(), center, e->getID());
1354
return;
1355
}
1356
Position edgePos = roadShape.positionAtOffset2D(edgeOffset);
1357
const double edgeAngle = roadShape.rotationAtOffset(edgeOffset);
1358
double sideOffset = center.distanceTo2D(edgePos);
1359
// determine sign of sideOffset
1360
PositionVector tmp = roadShape.getSubpart2D(MAX2(0.0, edgeOffset - 1), MIN2(roadShape.length2D(), edgeOffset + 1));
1361
tmp.move2side(sideOffset);
1362
if (tmp.distance2D(center) < sideOffset) {
1363
sideOffset *= -1;
1364
}
1365
//std::cout << " id=" << id
1366
// << " shape=" << shape
1367
// << " center=" << center
1368
// << " edgeOffset=" << edgeOffset
1369
// << "\n";
1370
auto shapeType = p->getShapeType();
1371
device.openTag("object");
1372
device.writeAttr("id", p->getID());
1373
device.writeAttr("type", shapeType);
1374
if (p->hasParameter("name")) {
1375
device.writeAttr("name", StringUtils::escapeXML(p->getParameter("name", ""), true));
1376
}
1377
device.writeAttr("s", edgeOffset);
1378
device.writeAttr("t", shapeType == "crosswalk" && !lefthand ? 0 : sideOffset);
1379
double hdg = -edgeAngle;
1380
if (p->hasParameter("hdg")) {
1381
try {
1382
hdg = StringUtils::toDoubleSecure(p->getParameter("hdg", ""), 0);
1383
} catch (NumberFormatException&) {}
1384
}
1385
device.writeAttr("hdg", hdg);
1386
if (p->hasParameter("length")) {
1387
try {
1388
device.writeAttr("length", StringUtils::toDoubleSecure(p->getParameter("length", ""), 0));
1389
} catch (NumberFormatException&) {}
1390
}
1391
if (p->hasParameter("width")) {
1392
try {
1393
device.writeAttr("width", StringUtils::toDoubleSecure(p->getParameter("width", ""), 0));
1394
} catch (NumberFormatException&) {}
1395
}
1396
double height = 0;
1397
if (p->hasParameter("height")) {
1398
try {
1399
height = StringUtils::toDoubleSecure(p->getParameter("height", ""), 0);
1400
device.writeAttr("height", height);
1401
} catch (NumberFormatException&) {}
1402
}
1403
//device.openTag("outlines");
1404
device.openTag("outline");
1405
device.writeAttr("id", 0);
1406
device.writeAttr("fillType", "concrete");
1407
device.writeAttr("outer", "true");
1408
device.writeAttr("closed", p->getShape().isClosed() ? "true" : "false");
1409
device.writeAttr("laneType", "border");
1410
1411
shape.sub(center);
1412
if (hdg != -edgeAngle) {
1413
shape.rotate2D(-edgeAngle - hdg);
1414
}
1415
int i = 0;
1416
for (Position pos : shape) {
1417
device.openTag("cornerLocal");
1418
device.writeAttr("u", pos.x());
1419
device.writeAttr("v", pos.y());
1420
device.writeAttr("z", MAX2(0.0, p->getShapeLayer()));
1421
device.writeAttr("height", height);
1422
device.writeAttr("id", i++);
1423
device.closeTag();
1424
}
1425
1426
1427
//device.closeTag();
1428
device.closeTag();
1429
device.closeTag();
1430
}
1431
1432
/****************************************************************************/
1433
1434