Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2011-2024 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
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 1830 : NWWriter_OpenDrive::writeNetwork(const OptionsCont& oc, NBNetBuilder& nb) {
64 : // check whether an opendrive-file shall be generated
65 3660 : if (!oc.isSet("opendrive-output")) {
66 1798 : return;
67 : }
68 : const NBNodeCont& nc = nb.getNodeCont();
69 : const NBEdgeCont& ec = nb.getEdgeCont();
70 32 : const bool origNames = oc.getBool("output.original-names");
71 32 : lefthand = oc.getBool("lefthand");
72 35 : LHLL = lefthand && oc.getBool("opendrive-output.lefthand-left");
73 32 : LHRL = lefthand && !LHLL;
74 32 : const double straightThresh = DEG2RAD(oc.getFloat("opendrive-output.straight-threshold"));
75 : // some internal mapping containers
76 32 : int nodeID = 1;
77 32 : int edgeID = nc.size() * 10; // distinct from node ids
78 : StringBijection<int> edgeMap;
79 : StringBijection<int> nodeMap;
80 : //
81 64 : OutputDevice& device = OutputDevice::getDevice(oc.getString("opendrive-output"));
82 64 : OutputDevice::createDeviceByOption("opendrive-output", "OpenDRIVE");
83 32 : time_t now = time(nullptr);
84 32 : std::string dstr(ctime(&now));
85 32 : const Boundary& b = GeoConvHelper::getFinal().getConvBoundary();
86 : // write header
87 32 : device.openTag("header");
88 32 : device.writeAttr("revMajor", "1");
89 32 : device.writeAttr("revMinor", "4");
90 32 : device.writeAttr("name", "");
91 64 : device.writeAttr("version", "1.00");
92 64 : device.writeAttr("date", dstr.substr(0, dstr.length() - 1));
93 32 : device.writeAttr("north", b.ymax());
94 32 : device.writeAttr("south", b.ymin());
95 32 : device.writeAttr("east", b.xmax());
96 32 : 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 32 : if (gch.usingGeoProjection()) {
105 10 : device.openTag("geoReference");
106 : device.writePreformattedTag(" <![CDATA[\n "
107 10 : + gch.getProjString()
108 20 : + "\n]]>\n");
109 10 : device.closeTag();
110 20 : if (gch.getOffsetBase() != Position(0, 0)) {
111 10 : device.openTag("offset");
112 20 : device.writeAttr("x", gch.getOffsetBase().x());
113 20 : device.writeAttr("y", gch.getOffsetBase().y());
114 20 : device.writeAttr("z", gch.getOffsetBase().z());
115 10 : device.writeAttr("hdg", 0);
116 20 : device.closeTag();
117 : }
118 : }
119 64 : device.closeTag();
120 :
121 : SignalLanes signalLanes;
122 :
123 32 : mapmatchRoadObjects(nb.getShapeCont(), ec);
124 :
125 32 : PositionVector crosswalk_shape;
126 : std::map<std::string, std::vector<std::string>> crosswalksByEdge;
127 471 : for (auto it = nc.begin(); it != nc.end(); ++it) {
128 439 : NBNode* n = it->second;
129 439 : auto crosswalks = n->getCrossings();
130 478 : for (const auto& cw : n->getCrossings()) {
131 : // getting from crosswalk line to a full shape
132 39 : crosswalk_shape = cw->shape;
133 39 : PositionVector rightside = cw->shape;
134 : try {
135 39 : crosswalk_shape.move2side(cw->width / 2);
136 39 : rightside.move2side(cw->width / -2);
137 0 : } catch (InvalidArgument&) { }
138 78 : rightside = rightside.reverse();
139 39 : crosswalk_shape.append(rightside);
140 39 : auto crosswalkId = cw->id;
141 78 : nb.getShapeCont().addPolygon(crosswalkId, "crosswalk", RGBColor::DEFAULT_COLOR, 0,
142 : Shape::DEFAULT_ANGLE, Shape::DEFAULT_IMG_FILE, Shape::DEFAULT_RELATIVEPATH,
143 : crosswalk_shape, false, true, 1, false, crosswalkId);
144 : SUMOPolygon* cwp = nb.getShapeCont().getPolygons().get(crosswalkId);
145 78 : cwp->setParameter("length", toString(cw->width));
146 78 : cwp->setParameter("width", toString(cw->shape.length2D()));
147 78 : cwp->setParameter("hdg", "0");
148 39 : crosswalksByEdge[cw->edges[0]->getID()].push_back(crosswalkId);
149 39 : }
150 : }
151 :
152 : // write normal edges (road)
153 850 : for (std::map<std::string, NBEdge*>::const_iterator i = ec.begin(); i != ec.end(); ++i) {
154 818 : const NBEdge* e = (*i).second;
155 1636 : const int fromNodeID = e->getIncomingEdges().size() > 0 ? getID(e->getFromNode()->getID(), nodeMap, nodeID) : INVALID_ID;
156 818 : const int toNodeID = e->getConnections().size() > 0 ? getID(e->getToNode()->getID(), nodeMap, nodeID) : INVALID_ID;
157 818 : writeNormalEdge(device, e,
158 818 : getID(e->getID(), edgeMap, edgeID),
159 : fromNodeID, toNodeID,
160 : origNames, straightThresh,
161 : nb.getShapeCont(),
162 : signalLanes,
163 818 : crosswalksByEdge[e->getID()]);
164 : }
165 32 : device.lf();
166 :
167 : // write junction-internal edges (road). In OpenDRIVE these are called 'paths' or 'connecting roads'
168 32 : OutputDevice_String junctionOSS(3);
169 471 : for (std::map<std::string, NBNode*>::const_iterator i = nc.begin(); i != nc.end(); ++i) {
170 439 : NBNode* n = (*i).second;
171 : int connectionID = 0; // unique within a junction
172 439 : const int nID = getID(n->getID(), nodeMap, nodeID);
173 439 : if (n->numNormalConnections() > 0) {
174 294 : junctionOSS << " <junction name=\"" << n->getID() << "\" id=\"" << nID << "\">\n";
175 : }
176 439 : std::vector<NBEdge*> incoming = (*i).second->getIncomingEdges();
177 439 : if (lefthand) {
178 : std::reverse(incoming.begin(), incoming.end());
179 : }
180 1257 : for (NBEdge* inEdge : incoming) {
181 818 : std::string centerMark = "none";
182 818 : 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 818 : std::vector<NBEdge::Connection> connections = inEdge->getConnections();
189 2682 : for (const NBEdge::Connection& c : connections) {
190 : assert(c.toEdge != 0);
191 1864 : if (outEdge != c.toEdge || c.fromLane == lastFromLane) {
192 1753 : if (outEdge != nullptr) {
193 1092 : if (isOuterEdge) {
194 512 : addPedestrianConnection(inEdge, outEdge, parallel);
195 : }
196 2184 : connectionID = writeInternalEdge(device, junctionOSS, inEdge, nID,
197 2184 : getID(parallel.back().getInternalLaneID(), edgeMap, edgeID),
198 : inEdgeID,
199 1092 : getID(outEdge->getID(), edgeMap, edgeID),
200 : connectionID,
201 : parallel, isOuterEdge, straightThresh, centerMark,
202 : signalLanes);
203 : parallel.clear();
204 : isOuterEdge = false;
205 : }
206 1753 : outEdge = c.toEdge;
207 : }
208 1864 : lastFromLane = c.fromLane;
209 1864 : parallel.push_back(c);
210 : }
211 818 : if (isOuterEdge) {
212 306 : addPedestrianConnection(inEdge, outEdge, parallel);
213 : }
214 818 : if (!parallel.empty()) {
215 661 : if (!lefthand && (n->geometryLike() || inEdge->isTurningDirectionAt(outEdge))) {
216 : centerMark = "solid";
217 : }
218 1322 : connectionID = writeInternalEdge(device, junctionOSS, inEdge, nID,
219 1322 : getID(parallel.back().getInternalLaneID(), edgeMap, edgeID),
220 : inEdgeID,
221 661 : getID(outEdge->getID(), edgeMap, edgeID),
222 : connectionID,
223 : parallel, isOuterEdge, straightThresh, centerMark,
224 : signalLanes);
225 : parallel.clear();
226 : }
227 818 : }
228 439 : if (n->numNormalConnections() > 0) {
229 294 : junctionOSS << " </junction>\n";
230 : }
231 : }
232 32 : device.lf();
233 : // write controllers
234 471 : for (std::map<std::string, NBNode*>::const_iterator i = nc.begin(); i != nc.end(); ++i) {
235 439 : NBNode* n = (*i).second;
236 439 : if (n->isTLControlled()) {
237 21 : NBTrafficLightDefinition* tl = *n->getControllingTLS().begin();
238 : std::set<std::string> ids;
239 42 : device.openTag("controller");
240 42 : device.writeAttr("id", tl->getID());
241 299 : for (const NBConnection& c : tl->getControlledLinks()) {
242 556 : const std::string id = tl->getID() + "_" + toString(c.getTLIndex());
243 : if (ids.count(id) == 0) {
244 : ids.insert(id);
245 266 : device.openTag("control");
246 266 : device.writeAttr("signalId", id);
247 532 : device.closeTag();
248 : }
249 : }
250 42 : device.closeTag();
251 : }
252 : }
253 : // write junctions (junction)
254 32 : device << junctionOSS.getString();
255 :
256 32 : device.closeTag();
257 32 : device.close();
258 96 : }
259 :
260 :
261 : std::string
262 814 : NWWriter_OpenDrive::getDividerType(const NBEdge* e) {
263 : std::map<std::string, std::string> dividerTypeMapping;
264 814 : dividerTypeMapping["solid_line"] = "solid";
265 814 : dividerTypeMapping["dashed_line"] = "broken";
266 814 : dividerTypeMapping["double_solid_line"] = "solid solid";
267 814 : dividerTypeMapping["no"] = "none";
268 :
269 : // defaulting to solid as in the original code
270 814 : std::string dividerType = "solid";
271 :
272 1628 : if (e->getParametersMap().count("divider") > 0) {
273 4 : std::string divider = e->getParametersMap().find("divider")->second;
274 : if (dividerTypeMapping.count(divider) > 0) {
275 2 : dividerType = dividerTypeMapping.find(divider)->second;
276 : }
277 : }
278 814 : return dividerType;
279 : }
280 :
281 :
282 : void
283 818 : 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 818 : OutputDevice_String elevationOSS(3);
292 818 : elevationOSS.setPrecision(8);
293 818 : OutputDevice_String planViewOSS(2);
294 818 : planViewOSS.setPrecision(8);
295 818 : double length = 0;
296 :
297 818 : 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 818 : 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 818 : if (ls.size() == 2 || e->getPermissions() == SVC_PEDESTRIAN) {
308 : // foot paths may contain sharp angles
309 621 : length = writeGeomLines(ls, planViewOSS, elevationOSS);
310 : } else {
311 197 : bool ok = writeGeomSmooth(ls, e->getSpeed(), planViewOSS, elevationOSS, straightThresh, length);
312 197 : if (!ok) {
313 3 : WRITE_WARNINGF(TL("Could not compute smooth shape for edge '%'."), e->getID());
314 : }
315 : }
316 818 : planViewOSS.closeTag();
317 :
318 1636 : device.openTag("road");
319 1636 : device.writeAttr("name", StringUtils::escapeXML(e->getStreetName()));
320 818 : device.setPrecision(8); // length requires higher precision
321 1636 : device.writeAttr("length", MAX2(POSITION_EPS, length));
322 818 : device.setPrecision(gPrecision);
323 818 : device.writeAttr("id", edgeID);
324 818 : device.writeAttr("junction", -1);
325 818 : if (fromNodeID != INVALID_ID || toNodeID != INVALID_ID) {
326 707 : device.openTag("link");
327 707 : if (fromNodeID != INVALID_ID) {
328 690 : device.openTag("predecessor");
329 690 : device.writeAttr("elementType", "junction");
330 690 : device.writeAttr("elementId", fromNodeID);
331 1380 : device.closeTag();
332 : }
333 707 : if (toNodeID != INVALID_ID) {
334 661 : device.openTag("successor");
335 661 : device.writeAttr("elementType", "junction");
336 661 : device.writeAttr("elementId", toNodeID);
337 1322 : device.closeTag();
338 : }
339 1414 : device.closeTag();
340 : }
341 3272 : device.openTag("type").writeAttr("s", 0).writeAttr("type", "town").closeTag();
342 818 : device << planViewOSS.getString();
343 818 : writeElevationProfile(ls, device, elevationOSS);
344 818 : device << " <lateralProfile/>\n";
345 818 : device << " <lanes>\n";
346 818 : device << " <laneSection s=\"0\">\n";
347 818 : const std::string centerMark = e->getPermissions(e->getNumLanes() - 1) == 0 ? "none" : getDividerType(e);
348 818 : if (!LHLL) {
349 800 : writeEmptyCenterLane(device, centerMark, 0.13);
350 : }
351 1618 : const std::string side = LHLL ? "left" : "right";
352 818 : device << " <" << side << ">\n";
353 : const int numLanes = e->getNumLanes();
354 1886 : 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 1068 : const int j = lefthand ? numLanes - 1 - jRH : jRH;
358 : std::string laneType = e->getLaneStruct(j).type;
359 1068 : if (laneType == "") {
360 2074 : laneType = getLaneType(e->getPermissions(j));
361 : }
362 1068 : device << " <lane id=\"" << s2x(j, numLanes) << "\" type=\"" << laneType << "\" level=\"true\">\n";
363 1068 : 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 1068 : device << " <width sOffset=\"0\" a=\"" << e->getLaneWidth(j) << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
372 1068 : std::string markType = "broken";
373 1068 : if (LHRL) {
374 34 : if (j == numLanes - 1) {
375 : // solid road mark in the middle of the road
376 : markType = "solid";
377 16 : } 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 1034 : if (j == 0) {
383 : markType = "solid";
384 : } else if (j > 0
385 234 : && (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 144 : } else if (e->getPermissions(j) == 0) {
389 : // solid road mark to the right of a forbidden lane
390 : markType = "solid";
391 : }
392 : }
393 1068 : device << " <roadMark sOffset=\"0\" type=\"" << markType << "\" weight=\"standard\" color=\"standard\" width=\"0.13\"/>\n";
394 1068 : device << " <speed sOffset=\"0\" max=\"" << lanes[j].speed << "\"/>\n";
395 1068 : device << " </lane>\n";
396 : }
397 818 : device << " </" << side << ">\n";
398 818 : if (LHLL) {
399 18 : writeEmptyCenterLane(device, centerMark, 0.13);
400 : }
401 818 : device << " </laneSection>\n";
402 818 : device << " </lanes>\n";
403 818 : writeRoadObjects(device, e, shc, crossings);
404 818 : writeSignals(device, e, length, signalLanes, shc);
405 818 : if (origNames) {
406 404 : device << " <userData code=\"sumoId\" value=\"" << e->getID() << "\"/>\n";
407 : }
408 818 : device.closeTag();
409 818 : checkLaneGeometries(e);
410 818 : }
411 :
412 : void
413 818 : 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 661 : && inEdge->getPermissions(0) == SVC_PEDESTRIAN
418 134 : && outEdge->getPermissions(0) == SVC_PEDESTRIAN
419 927 : && (parallel.empty()
420 109 : || parallel.front().fromLane != 0
421 64 : || parallel.front().toLane != 0)) {
422 90 : parallel.insert(parallel.begin(), NBEdge::Connection(0, const_cast<NBEdge*>(outEdge), 0));
423 45 : parallel.front().vmax = (inEdge->getLanes()[0].speed + outEdge->getLanes()[0].speed) / (double) 2.0;
424 : }
425 818 : }
426 :
427 :
428 : int
429 1753 : 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 1753 : const NBEdge::Connection& cLeft = LHRL ? parallel.front() : parallel.back();
439 1753 : const NBEdge* outEdge = cLeft.toEdge;
440 1753 : PositionVector begShape = getInnerLaneBorder(inEdge, cLeft.fromLane);
441 1753 : 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 1753 : double laneOffset = 0;
446 1753 : PositionVector fallBackShape;
447 1753 : fallBackShape.push_back(begShape.back());
448 1753 : fallBackShape.push_back(endShape.front());
449 1753 : const bool turnaround = inEdge->isTurningDirectionAt(outEdge);
450 1753 : bool ok = true;
451 1753 : PositionVector init = NBNode::bezierControlPoints(begShape, endShape, turnaround, 25, 25, ok, nullptr, straightThresh);
452 1753 : if (init.size() == 0) {
453 533 : length = fallBackShape.length2D();
454 : // problem with turnarounds is known, method currently returns 'ok' (#2539)
455 533 : if (!ok) {
456 17 : 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 528 : } else if (length <= NUMERICAL_EPS) {
458 : // left-curving geometry-like edges must use the right
459 : // side as reference line and shift
460 960 : begShape = getOuterLaneBorder(inEdge, cLeft.fromLane);
461 960 : endShape = getOuterLaneBorder(outEdge, cLeft.toLane);
462 960 : init = NBNode::bezierControlPoints(begShape, endShape, turnaround, 25, 25, ok, nullptr, straightThresh);
463 480 : if (init.size() != 0) {
464 480 : length = init.bezier(12).length2D();
465 480 : laneOffset = outEdge->getLaneWidth(cLeft.toLane);
466 : //std::cout << " internalLane=" << cLeft.getInternalLaneID() << " length=" << length << "\n";
467 : }
468 : }
469 : } else {
470 1220 : length = init.bezier(12).length2D();
471 : }
472 1753 : double roadLength = MAX2(POSITION_EPS, length);
473 :
474 1753 : junctionDevice << " <connection id=\"" << connectionID << "\" incomingRoad=\"" << inEdgeID << "\" connectingRoad=\"" << edgeID << "\" contactPoint=\"start\">\n";
475 1753 : device.openTag("road");
476 1753 : device.writeAttr("name", cLeft.id);
477 1753 : device.setPrecision(8); // length requires higher precision
478 1753 : device.writeAttr("length", roadLength);
479 1753 : device.setPrecision(gPrecision);
480 1753 : device.writeAttr("id", edgeID);
481 1753 : device.writeAttr("junction", nodeID);
482 1753 : device.openTag("link");
483 1753 : device.openTag("predecessor");
484 1753 : device.writeAttr("elementType", "road");
485 1753 : device.writeAttr("elementId", inEdgeID);
486 1753 : device.writeAttr("contactPoint", "end");
487 1753 : device.closeTag();
488 1753 : device.openTag("successor");
489 1753 : device.writeAttr("elementType", "road");
490 1753 : device.writeAttr("elementId", outEdgeID);
491 1753 : device.writeAttr("contactPoint", "start");
492 1753 : device.closeTag();
493 1753 : device.closeTag();
494 7012 : device.openTag("type").writeAttr("s", 0).writeAttr("type", "town").closeTag();
495 1753 : device.openTag("planView");
496 1753 : device.setPrecision(8); // geometry hdg requires higher precision
497 1753 : OutputDevice_String elevationOSS(3);
498 1753 : 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 1753 : if (init.size() == 0) {
507 53 : writeGeomLines(fallBackShape, device, elevationOSS);
508 : } else {
509 1700 : writeGeomPP3(device, elevationOSS, init, length);
510 : }
511 1753 : device.setPrecision(gPrecision);
512 1753 : device.closeTag();
513 1753 : writeElevationProfile(fallBackShape, device, elevationOSS);
514 1753 : device << " <lateralProfile/>\n";
515 1753 : device << " <lanes>\n";
516 1753 : if (laneOffset != 0) {
517 480 : device << " <laneOffset s=\"0\" a=\"" << laneOffset << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
518 : }
519 1753 : device << " <laneSection s=\"0\">\n";
520 1753 : if (!lefthand) {
521 1689 : writeEmptyCenterLane(device, centerMark, 0);
522 : }
523 3442 : const std::string side = lefthand ? "left" : "right";
524 1753 : device << " <" << side << ">\n";
525 1753 : const int numLanes = (int)parallel.size();
526 3662 : for (int jRH = numLanes; --jRH >= 0;) {
527 1909 : const int j = lefthand ? numLanes - 1 - jRH : jRH;
528 1909 : const int xJ = s2x(j, numLanes);
529 1909 : const NBEdge::Connection& c = parallel[j];
530 1909 : const int fromIndex = s2x(c.fromLane, inEdge->getNumLanes());
531 1909 : const int toIndex = s2x(c.toLane, outEdge->getNumLanes());
532 :
533 1909 : double inEdgeWidth = inEdge->getLaneWidth(c.fromLane);
534 1909 : 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 1909 : double bCoefficient = (outEdgeWidth - inEdgeWidth) / roadLength;
539 1909 : double cCoefficient = 0;
540 1909 : double dCoefficient = 0;
541 1909 : device << " <lane id=\"" << xJ << "\" type=\"" << getLaneType(outEdge->getPermissions(c.toLane)) << "\" level=\"true\">\n";
542 1909 : device << " <link>\n";
543 1909 : device << " <predecessor id=\"" << fromIndex << "\"/>\n";
544 1909 : device << " <successor id=\"" << toIndex << "\"/>\n";
545 1909 : device << " </link>\n";
546 1909 : device << " <width sOffset=\"0\" a=\"" << inEdgeWidth << "\" b=\"" << bCoefficient << "\" c=\"" << cCoefficient << "\" d=\"" << dCoefficient << "\"/>\n";
547 1909 : std::string markType = "broken";
548 1909 : if (inEdge->isTurningDirectionAt(outEdge)) {
549 : markType = "none";
550 1396 : } else if (c.fromLane == 0 && c.toLane == 0 && isOuterEdge) {
551 : // solid road mark at the outer border
552 : markType = "solid";
553 808 : } else if (isOuterEdge && j > 0
554 808 : && (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 756 : } else if (!inEdge->getToNode()->geometryLike()) {
558 : // draw shorter road marks to indicate turning paths
559 717 : LinkDirection dir = inEdge->getToNode()->getDirection(inEdge, outEdge, lefthand);
560 717 : 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 1909 : device << " <roadMark sOffset=\"0\" type=\"" << markType << "\" weight=\"standard\" color=\"standard\" width=\"0.13\"/>\n";
569 1909 : device << " <speed sOffset=\"0\" max=\"" << c.vmax << "\"/>\n";
570 1909 : device << " </lane>\n";
571 :
572 1909 : junctionDevice << " <laneLink from=\"" << fromIndex << "\" to=\"" << xJ << "\"/>\n";
573 1909 : connectionID++;
574 : }
575 1753 : device << " </" << side << ">\n";
576 1753 : if (lefthand) {
577 64 : writeEmptyCenterLane(device, centerMark, 0);
578 : }
579 1753 : device << " </laneSection>\n";
580 1753 : device << " </lanes>\n";
581 1753 : device << " <objects/>\n";
582 : UNUSED_PARAMETER(signalLanes);
583 1753 : device << " <signals/>\n";
584 1753 : device.closeTag();
585 1753 : junctionDevice << " </connection>\n";
586 :
587 3506 : return connectionID;
588 1753 : }
589 :
590 :
591 : double
592 1330 : NWWriter_OpenDrive::writeGeomLines(const PositionVector& shape, OutputDevice& device, OutputDevice& elevationDevice, double offset) {
593 2738 : for (int j = 0; j < (int)shape.size() - 1; ++j) {
594 1408 : const Position& p = shape[j];
595 1408 : const Position& p2 = shape[j + 1];
596 1408 : const double hdg = shape.angleAt2D(j);
597 1408 : const double length = p.distanceTo2D(p2);
598 1408 : device.openTag("geometry");
599 2816 : device.writeAttr("s", offset);
600 2816 : device.writeAttr("x", p.x());
601 1408 : device.writeAttr("y", p.y());
602 1408 : device.writeAttr("hdg", hdg);
603 1408 : device.writeAttr("length", length);
604 2816 : device.openTag("line").closeTag();
605 1408 : device.closeTag();
606 2813 : elevationDevice << " <elevation s=\"" << offset << "\" a=\"" << p.z() << "\" b=\"" << (p2.z() - p.z()) / MAX2(POSITION_EPS, length) << "\" c=\"0\" d=\"0\"/>\n";
607 1408 : offset += length;
608 : }
609 1330 : return offset;
610 : }
611 :
612 :
613 : void
614 2571 : NWWriter_OpenDrive::writeEmptyCenterLane(OutputDevice& device, const std::string& mark, double markWidth) {
615 2571 : device << " <center>\n";
616 2571 : device << " <lane id=\"0\" type=\"none\" level=\"true\">\n";
617 2571 : device << " <link/>\n";
618 2571 : device << " <roadMark sOffset=\"0\" type=\"" << mark << "\" weight=\"standard\" color=\"standard\" width=\"" << markWidth << "\"/>\n";
619 2571 : device << " </lane>\n";
620 2571 : device << " </center>\n";
621 2571 : }
622 :
623 :
624 : int
625 6932 : NWWriter_OpenDrive::getID(const std::string& origID, StringBijection<int>& map, int& lastID) {
626 6932 : if (map.hasString(origID)) {
627 3922 : return map.get(origID);
628 : }
629 3010 : map.insert(origID, lastID++);
630 3010 : return lastID - 1;
631 : }
632 :
633 :
634 : std::string
635 2946 : NWWriter_OpenDrive::getLaneType(SVCPermissions permissions) {
636 2946 : switch (permissions) {
637 : case SVC_PEDESTRIAN:
638 603 : 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 79 : return "biking";
644 : case 0:
645 : // ambiguous
646 12 : return "none";
647 : case SVC_RAIL:
648 : case SVC_RAIL_URBAN:
649 : case SVC_RAIL_ELECTRIC:
650 : case SVC_RAIL_FAST:
651 4 : return "rail";
652 : case SVC_TRAM:
653 18 : return "tram";
654 2230 : default: {
655 : // complex permissions
656 2230 : if (permissions == SVCAll) {
657 1155 : return "driving";
658 1075 : } else if (isRailway(permissions)) {
659 0 : return "rail";
660 1075 : } else if ((permissions & SVC_PASSENGER) != 0) {
661 714 : return "driving";
662 : } else {
663 361 : return "restricted";
664 : }
665 : }
666 : }
667 : }
668 :
669 :
670 : PositionVector
671 5418 : NWWriter_OpenDrive::getInnerLaneBorder(const NBEdge* edge, int laneIndex, double widthOffset) {
672 5418 : if (laneIndex == -1) {
673 : // innermost lane
674 952 : laneIndex = (int)edge->getNumLanes() - 1;
675 952 : if (LHRL) {
676 : laneIndex = 0;
677 : }
678 : }
679 5418 : PositionVector result = edge->getLaneShape(laneIndex);
680 10732 : widthOffset -= (LHLL ? -1 : 1) * edge->getLaneWidth(laneIndex) / 2;
681 : try {
682 5418 : result.move2side(widthOffset);
683 0 : } catch (InvalidArgument&) { }
684 5418 : return result;
685 0 : }
686 :
687 :
688 : PositionVector
689 960 : NWWriter_OpenDrive::getOuterLaneBorder(const NBEdge* edge, int laneIndex) {
690 960 : return getInnerLaneBorder(edge, laneIndex, edge->getLaneWidth(laneIndex));
691 : }
692 :
693 :
694 : double
695 2332 : 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 4664 : length = MAX2(POSITION_EPS, length);
705 :
706 2332 : const Position p = init.front();
707 2332 : const double hdg = init.angleAt2D(0);
708 :
709 : // backup elevation values
710 : const PositionVector initZ = init;
711 : // translate to u,v coordinates
712 2332 : init.add(-p.x(), -p.y(), -p.z());
713 2332 : 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 2332 : if (init.size() == 3) {
722 : //f(x, a, b ,c) = a + (2*b - 2*a)*x + (a - 2*b + c)*x*x
723 1306 : aU = init[0].x();
724 1306 : bU = 2 * init[1].x() - 2 * init[0].x();
725 1306 : cU = init[0].x() - 2 * init[1].x() + init[2].x();
726 1306 : dU = 0;
727 :
728 1306 : aV = init[0].y();
729 1306 : bV = 2 * init[1].y() - 2 * init[0].y();
730 1306 : cV = init[0].y() - 2 * init[1].y() + init[2].y();
731 1306 : dV = 0;
732 :
733 : // elevation is not parameteric on [0:1] but on [0:length]
734 1306 : aZ = initZ[0].z();
735 1306 : bZ = (2 * initZ[1].z() - 2 * initZ[0].z()) / length;
736 1306 : cZ = (initZ[0].z() - 2 * initZ[1].z() + initZ[2].z()) / (length * length);
737 1306 : 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 1026 : aU = init[0].x();
742 1026 : bU = 3 * init[1].x() - 3 * init[0].x();
743 1026 : cU = 3 * init[0].x() - 6 * init[1].x() + 3 * init[2].x();
744 1026 : dU = -init[0].x() + 3 * init[1].x() - 3 * init[2].x() + init[3].x();
745 :
746 1026 : aV = init[0].y();
747 1026 : bV = 3 * init[1].y() - 3 * init[0].y();
748 1026 : cV = 3 * init[0].y() - 6 * init[1].y() + 3 * init[2].y();
749 1026 : 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 1026 : aZ = initZ[0].z();
753 1026 : bZ = (3 * initZ[1].z() - 3 * initZ[0].z()) / length;
754 1026 : cZ = (3 * initZ[0].z() - 6 * initZ[1].z() + 3 * initZ[2].z()) / (length * length);
755 1026 : dZ = (-initZ[0].z() + 3 * initZ[1].z() - 3 * initZ[2].z() + initZ[3].z()) / (length * length * length);
756 : }
757 :
758 2332 : device.openTag("geometry");
759 2332 : device.writeAttr("s", offset);
760 2332 : device.writeAttr("x", p.x());
761 2332 : device.writeAttr("y", p.y());
762 2332 : device.writeAttr("hdg", hdg);
763 2332 : device.writeAttr("length", length);
764 :
765 2332 : device.openTag("paramPoly3");
766 2332 : device.writeAttr("aU", aU);
767 2332 : device.writeAttr("bU", bU);
768 2332 : device.writeAttr("cU", cU);
769 2332 : device.writeAttr("dU", dU);
770 2332 : device.writeAttr("aV", aV);
771 2332 : device.writeAttr("bV", bV);
772 2332 : device.writeAttr("cV", cV);
773 2332 : device.writeAttr("dV", dV);
774 2332 : device.writeAttr("pRange", "normalized");
775 2332 : device.closeTag();
776 2332 : device.closeTag();
777 :
778 : // write elevation
779 2332 : elevationDevice.openTag("elevation");
780 2332 : elevationDevice.writeAttr("s", offset);
781 2332 : elevationDevice.writeAttr("a", aZ);
782 2332 : elevationDevice.writeAttr("b", bZ);
783 2332 : elevationDevice.writeAttr("c", cZ);
784 2332 : elevationDevice.writeAttr("d", dZ);
785 2332 : elevationDevice.closeTag();
786 :
787 2332 : return offset + length;
788 2332 : }
789 :
790 :
791 : bool
792 197 : 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 197 : bool ok = true;
799 : const double longThresh = speed; // 16.0; // make user-configurable (should match the sampling rate of the source data)
800 197 : 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 948 : for (int j = 1; j < (int)shape.size() - 1; ++j) {
810 : //const double hdg = shape.angleAt2D(j);
811 751 : const Position& p0 = shape[j - 1];
812 751 : const Position& p1 = shape[j];
813 751 : const Position& p2 = shape[j + 1];
814 751 : 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 599 : && (length1 > longThresh || j == 1)
825 1139 : && (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 663 : shape2.insertAtClosest(shape.positionAtOffset2D(offset + length1 - MIN2(length1 - 2 * POSITION_EPS, curveCutout)), false);
828 665 : shape2.insertAtClosest(shape.positionAtOffset2D(offset + length1 + MIN2(length2 - 2 * POSITION_EPS, curveCutout)), false);
829 340 : shape2.removeClosest(p1);
830 : }
831 751 : offset += length1;
832 : }
833 197 : 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 197 : if (maxAngleDiff < straightThresh) {
841 0 : 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 0 : return ok;
848 : }
849 :
850 : // write the long segments as lines, short segments as curves
851 : offset = 0;
852 1485 : for (int j = 0; j < numPoints - 1; ++j) {
853 1288 : const Position& p0 = shape2[j];
854 1288 : const Position& p1 = shape2[j + 1];
855 1288 : PositionVector line;
856 1288 : line.push_back(p0);
857 1288 : line.push_back(p1);
858 1288 : const double lineLength = line.length2D();
859 1288 : if (lineLength >= longThresh) {
860 394 : 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 894 : PositionVector begShape;
869 894 : PositionVector endShape;
870 894 : 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 122 : begShape.add(p0 - begShape.back());
874 772 : } else if (j == 1 || p0.distanceTo2D(shape2[j - 1]) > longThresh) {
875 : // use the previous segment if it is long or the first one
876 319 : begShape.push_back(shape2[j - 1]);
877 319 : begShape.push_back(p0);
878 : } else {
879 : // end at p0 with mean angle of the previous and current segment
880 453 : begShape.push_back(shape2[j - 1]);
881 453 : begShape.push_back(p1);
882 453 : begShape.add(p0 - begShape.back());
883 : }
884 :
885 894 : 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 122 : endShape.add(p1 - endShape.front());
889 772 : } 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 319 : endShape.push_back(p1);
892 319 : endShape.push_back(shape2[j + 2]);
893 : } else {
894 : // start at p1 with mean angle of the current and next segment
895 453 : endShape.push_back(p0);
896 453 : endShape.push_back(shape2[j + 2]);
897 453 : endShape.add(p1 - endShape.front());
898 : }
899 894 : const double extrapolateLength = MIN2((double)25, lineLength / 4);
900 894 : PositionVector init = NBNode::bezierControlPoints(begShape, endShape, false, extrapolateLength, extrapolateLength, ok, nullptr, straightThresh);
901 894 : if (init.size() == 0) {
902 : // could not compute control points, write line
903 262 : 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 632 : const double curveLength = init.bezier(12).length2D();
912 632 : 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 894 : }
920 1288 : }
921 197 : length = offset;
922 197 : return ok;
923 197 : }
924 :
925 :
926 : void
927 2571 : NWWriter_OpenDrive::writeElevationProfile(const PositionVector& shape, OutputDevice& device, const OutputDevice_String& elevationDevice) {
928 : // check if the shape is flat
929 : bool flat = true;
930 2571 : double z = shape.size() == 0 ? 0 : shape[0].z();
931 5739 : for (int i = 1; i < (int)shape.size(); ++i) {
932 3170 : if (fabs(shape[i].z() - z) > NUMERICAL_EPS) {
933 : flat = false;
934 : break;
935 : }
936 : }
937 2571 : device << " <elevationProfile>\n";
938 2571 : if (flat) {
939 2569 : device << " <elevation s=\"0\" a=\"" << z << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
940 : } else {
941 4 : device << elevationDevice.getString();
942 : }
943 2571 : device << " </elevationProfile>\n";
944 :
945 2571 : }
946 :
947 :
948 : void
949 818 : NWWriter_OpenDrive::checkLaneGeometries(const NBEdge* e) {
950 818 : if (e->getNumLanes() > 1) {
951 : // compute 'stop line' of rightmost lane
952 142 : const PositionVector shape0 = e->getLaneShape(0);
953 : assert(shape0.size() >= 2);
954 142 : const Position& from = shape0[-2];
955 142 : const Position& to = shape0[-1];
956 142 : PositionVector stopLine;
957 142 : stopLine.push_back(to);
958 252 : stopLine.push_back(to - PositionVector::sideOffset(from, to, lefthand ? 1000 : -1000));
959 : // endpoints of all other lanes should be on the stop line
960 392 : for (int lane = 1; lane < e->getNumLanes(); ++lane) {
961 250 : const double dist = stopLine.distance2D(e->getLaneShape(lane)[-1]);
962 250 : if (dist > NUMERICAL_EPS) {
963 0 : WRITE_WARNINGF(TL("Uneven stop line at lane '%' (dist=%) cannot be represented in OpenDRIVE."), e->getLaneID(lane), toString(dist));
964 : }
965 : }
966 142 : }
967 818 : }
968 :
969 : void
970 818 : NWWriter_OpenDrive::writeRoadObjects(OutputDevice& device, const NBEdge* e, const ShapeContainer& shc, const std::vector<std::string>& crossings) {
971 818 : device.openTag("objects");
972 1636 : if (e->hasParameter(ROAD_OBJECTS)) {
973 52 : device.setPrecision(8); // geometry hdg requires higher precision
974 52 : PositionVector road = getInnerLaneBorder(e);
975 194 : for (std::string id : StringTokenizer(e->getParameter(ROAD_OBJECTS, "")).getVector()) {
976 : SUMOPolygon* p = shc.getPolygons().get(id);
977 62 : if (p == nullptr) {
978 : PointOfInterest* poi = shc.getPOIs().get(id);
979 58 : if (poi == nullptr) {
980 0 : WRITE_WARNINGF("Road object polygon or POI '%' not found for edge '%'", id, e->getID());
981 : } else {
982 58 : writeRoadObjectPOI(device, e, road, poi);
983 : }
984 : } else {
985 62 : writeRoadObjectPoly(device, e, road, p);
986 : }
987 52 : }
988 52 : device.setPrecision(gPrecision);
989 52 : }
990 818 : if (crossings.size() > 0) {
991 34 : device.setPrecision(8); // geometry hdg requires higher precision
992 34 : PositionVector road = getInnerLaneBorder(e);
993 73 : for (size_t ic = 0; ic < crossings.size(); ic++) {
994 : SUMOPolygon* p = shc.getPolygons().get(crossings[ic]);
995 39 : if (p != 0) {
996 39 : writeRoadObjectPoly(device, e, road, p);
997 : }
998 : }
999 34 : device.setPrecision(gPrecision);
1000 34 : }
1001 818 : device.closeTag();
1002 818 : }
1003 :
1004 :
1005 : std::vector<NWWriter_OpenDrive::TrafficSign>
1006 4 : NWWriter_OpenDrive::parseTrafficSign(const std::string& trafficSign, PointOfInterest* poi) {
1007 : std::vector<TrafficSign> result;
1008 : // check for maxspeed, stop, give_way and hazard
1009 7 : if (trafficSign == "maxspeed" && poi->hasParameter("maxspeed")) {
1010 6 : result.push_back(TrafficSign{ "OpenDrive", "maxspeed", "", poi->getParameter("maxspeed")});
1011 1 : } else if (trafficSign == "stop") {
1012 0 : result.push_back(TrafficSign{ "OpenDrive", trafficSign, "", "" });
1013 1 : } else if (trafficSign == "give_way") {
1014 0 : result.push_back(TrafficSign{ "OpenDrive", trafficSign, "", "" });
1015 1 : } else if (trafficSign == "hazard" && poi->hasParameter("hazard")) {
1016 0 : result.push_back(TrafficSign{ "OpenDrive", trafficSign, poi->getParameter("hazard"), "" });
1017 : } else {
1018 1 : if (trafficSign.find_first_of(",;") != std::string::npos) {
1019 1 : std::string::size_type colon = trafficSign.find(':');
1020 1 : const std::string country = trafficSign.substr(0, colon);
1021 1 : 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 3 : while (pos != std::string::npos) {
1027 : // add country and colon before pushing the token
1028 4 : tokens.push_back(country + ":" + remaining.substr(lastPos, pos - lastPos));
1029 2 : lastPos = pos + 1;
1030 : pos = remaining.find_first_of(",;", lastPos);
1031 : }
1032 2 : tokens.push_back(country + ":" + remaining.substr(lastPos));
1033 : // call for each token the parseTrafficSignId function and fill the result vector
1034 4 : for (std::string token : tokens) {
1035 6 : result.push_back(parseTrafficSignId(token));
1036 : }
1037 1 : } else {
1038 0 : result.push_back(parseTrafficSignId(trafficSign));
1039 : }
1040 : }
1041 4 : return result;
1042 0 : }
1043 :
1044 :
1045 : NWWriter_OpenDrive::TrafficSign
1046 3 : 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 3 : 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 3 : if (match.size() == 5) {
1063 : std::string value;
1064 : if (match[3].matched) {
1065 2 : value = match[3].str();
1066 2 : } else if (match[4].matched) {
1067 2 : value = match[4].str();
1068 : }
1069 6 : return TrafficSign{ match[1], match[2], "", value};
1070 : } else {
1071 0 : return TrafficSign{ "OpenDrive", trafficSign, "", ""};
1072 : }
1073 3 : }
1074 :
1075 :
1076 : void
1077 818 : NWWriter_OpenDrive::writeSignals(OutputDevice& device, const NBEdge* e, double length,
1078 : SignalLanes& signalLanes, const ShapeContainer& shc) {
1079 1636 : device.openTag("signals");
1080 818 : 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 65 : NBTrafficLightDefinition* tl = *e->getToNode()->getControllingTLS().begin();
1085 : std::map<std::string, bool> toWrite;
1086 1208 : for (const NBConnection& c : tl->getControlledLinks()) {
1087 1143 : if (c.getFrom() == e) {
1088 476 : const std::string id = tl->getID() + "_" + toString(c.getTLIndex());
1089 : if (toWrite.count(id) == 0) {
1090 226 : toWrite[id] = signalLanes.count(id) == 0;
1091 : }
1092 238 : signalLanes[id].first.insert(c.getFromLane());
1093 238 : signalLanes[id].second.insert(e->getToNode()->getDirection(e, c.getTo()));
1094 : }
1095 : }
1096 291 : for (auto item : toWrite) {
1097 : const std::string id = item.first;
1098 226 : const bool isNew = item.second;
1099 226 : 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 226 : const bool s = dirs.count(LinkDirection::STRAIGHT) != 0;
1103 226 : const std::string tag = isNew ? "signal" : "signalReference";
1104 226 : int firstLane = *signalLanes[id].first.begin();
1105 226 : double t = e->getLaneWidth(firstLane) * 0.5;
1106 352 : for (int i = firstLane + 1; i < e->getNumLanes(); i++) {
1107 126 : t += e->getLaneWidth(i);
1108 : }
1109 226 : device.openTag(tag);
1110 226 : device.writeAttr("id", id);
1111 226 : device.writeAttr("s", length);
1112 226 : device.writeAttr("t", -t);
1113 226 : device.writeAttr("orientation", "+");
1114 226 : if (isNew) {
1115 226 : int type = 1000001;
1116 226 : int subType = -1;
1117 226 : if (l && !s && !r) {
1118 47 : type = 1000011;
1119 47 : subType = 10;
1120 179 : } else if (!l && !s && r) {
1121 48 : type = 1000011;
1122 48 : subType = 20;
1123 131 : } else if (!l && s && !r) {
1124 94 : type = 1000011;
1125 94 : subType = 30;
1126 37 : } else if (l && s && !r) {
1127 0 : type = 1000011;
1128 0 : subType = 40;
1129 37 : } else if (!l && s && r) {
1130 4 : type = 1000011;
1131 4 : subType = 50;
1132 : }
1133 226 : device.writeAttr("dynamic", "yes");
1134 226 : device.writeAttr("zOffset", 5);
1135 226 : device.writeAttr("country", "OpenDRIVE");
1136 226 : device.writeAttr("type", type);
1137 226 : device.writeAttr("subtype", subType);
1138 226 : device.writeAttr("height", 0.78);
1139 452 : device.writeAttr("width", 0.26);
1140 : }
1141 456 : for (int lane : signalLanes[id].first) {
1142 230 : device.openTag("validity");
1143 230 : device.writeAttr("fromLane", lane);
1144 230 : device.writeAttr("toLane", lane);
1145 460 : device.closeTag();
1146 : }
1147 452 : device.closeTag();
1148 : }
1149 1506 : } else if (e->hasParameter(ROAD_OBJECTS)) {
1150 48 : PositionVector roadShape = getInnerLaneBorder(e);
1151 182 : for (std::string id : StringTokenizer(e->getParameter(ROAD_OBJECTS, "")).getVector()) {
1152 : PointOfInterest* poi = shc.getPOIs().get(id);
1153 53 : if (poi != nullptr) {
1154 57 : if (poi->getShapeType() == "traffic_sign" && poi->hasParameter("traffic_sign")) {
1155 8 : std::string traffic_sign_type = poi->getParameter("traffic_sign");
1156 :
1157 4 : std::vector<TrafficSign> trafficSigns = parseTrafficSign(traffic_sign_type, poi);
1158 :
1159 4 : auto distance = roadShape.nearest_offset_to_point2D(*poi, true);
1160 4 : double t = getRoadSideOffset(e);
1161 4 : double calculatedZOffset = 3.0;
1162 10 : for (auto it = trafficSigns.rbegin(); it != trafficSigns.rend(); ++it) {
1163 6 : TrafficSign trafficSign = *it;
1164 6 : device.openTag("signal");
1165 6 : device.writeAttr("id", id);
1166 6 : device.writeAttr("s", distance);
1167 6 : device.writeAttr("t", t);
1168 6 : device.writeAttr("orientation", "-");
1169 6 : device.writeAttr("dynamic", "no");
1170 6 : device.writeAttr("zOffset", calculatedZOffset);
1171 6 : device.writeAttr("country", trafficSign.country);
1172 6 : device.writeAttr("type", trafficSign.type);
1173 6 : device.writeAttr("subtype", trafficSign.subtype);
1174 6 : device.writeAttr("value", trafficSign.value);
1175 6 : device.writeAttr("height", 0.78);
1176 6 : device.writeAttr("width", 0.78);
1177 6 : device.closeTag();
1178 6 : calculatedZOffset += 0.78;
1179 6 : }
1180 4 : }
1181 : }
1182 48 : }
1183 48 : }
1184 818 : device.closeTag();
1185 818 : }
1186 :
1187 :
1188 : double
1189 8 : NWWriter_OpenDrive::getRoadSideOffset(const NBEdge* e) {
1190 : double t = 0.30;
1191 8 : if (!lefthand || LHLL) {
1192 12 : for (int i = 0; i < e->getNumLanes(); i++) {
1193 6 : t += e->getPermissions(i) == SVC_PEDESTRIAN ? e->getLaneWidth(i) * 0.2 : e->getLaneWidth(i);
1194 : }
1195 : }
1196 8 : t = LHRL ? t : -t;
1197 8 : return t;
1198 : }
1199 :
1200 :
1201 : int
1202 6795 : NWWriter_OpenDrive::s2x(int sumoIndex, int numLanes) {
1203 : // sumo lanes: 0, 1, 2 (0 being the outermost lane)
1204 : // XODR: -3,-2,-1 (written in reverse order)
1205 : // LHLL: 3, 2, 1 (written in reverse order)
1206 : // lefthand (old):-1,-2,-3
1207 : return (lefthand
1208 6795 : ? (LHLL
1209 308 : ? numLanes - sumoIndex
1210 : : - sumoIndex - 1)
1211 6795 : : sumoIndex - numLanes);
1212 : }
1213 :
1214 :
1215 : void
1216 32 : NWWriter_OpenDrive::mapmatchRoadObjects(const ShapeContainer& shc, const NBEdgeCont& ec) {
1217 32 : if (shc.getPolygons().size() == 0 && shc.getPOIs().size() == 0) {
1218 27 : return;
1219 : }
1220 5 : const double maxDist = OptionsCont::getOptions().getFloat("opendrive-output.shape-match-dist");
1221 5 : if (maxDist < 0) {
1222 : return;
1223 : }
1224 : // register custom assignements
1225 : std::set<std::string> assigned;
1226 149 : for (auto it = ec.begin(); it != ec.end(); ++it) {
1227 144 : NBEdge* e = it->second;
1228 288 : if (e->hasParameter(ROAD_OBJECTS)) {
1229 0 : for (std::string id : StringTokenizer(e->getParameter(ROAD_OBJECTS, "")).getVector()) {
1230 : assigned.insert(id);
1231 0 : }
1232 : }
1233 : }
1234 : // build rtree for edges
1235 : NamedRTree r;
1236 149 : for (auto it = ec.begin(); it != ec.end(); ++it) {
1237 144 : NBEdge* edge = it->second;
1238 144 : Boundary bound = edge->getGeometry().getBoxBoundary();
1239 144 : bound.grow(maxDist);
1240 144 : float min[2] = { static_cast<float>(bound.xmin()), static_cast<float>(bound.ymin()) };
1241 144 : float max[2] = { static_cast<float>(bound.xmax()), static_cast<float>(bound.ymax()) };
1242 144 : r.Insert(min, max, edge);
1243 144 : }
1244 :
1245 67 : for (auto itPoly = shc.getPolygons().begin(); itPoly != shc.getPolygons().end(); itPoly++) {
1246 62 : SUMOPolygon* p = itPoly->second;
1247 62 : Boundary bound = p->getShape().getBoxBoundary();
1248 62 : float min[2] = { static_cast<float>(bound.xmin()), static_cast<float>(bound.ymin()) };
1249 62 : float max[2] = { static_cast<float>(bound.xmax()), static_cast<float>(bound.ymax()) };
1250 : std::set<const Named*> edges;
1251 : Named::StoringVisitor visitor(edges);
1252 : r.Search(min, max, visitor);
1253 : std::vector<std::pair<double, std::string> > nearby;
1254 2714 : for (const Named* namedEdge : edges) {
1255 2652 : NBEdge* e = const_cast<NBEdge*>(dynamic_cast<const NBEdge*>(namedEdge));
1256 2652 : const double distance = VectorHelper<double>::minValue(p->getShape().distances(e->getLaneShape(0), true));
1257 2652 : if (distance <= maxDist) {
1258 : // sort by distance and ID to stabilize results
1259 3562 : nearby.push_back(std::make_pair(distance, e->getID()));
1260 : //std::cout << " poly=" << p->getID() << " e=" << e->getID() << " dist=" << distance << "\n";
1261 : }
1262 : }
1263 62 : if (nearby.size() > 0) {
1264 62 : std::sort(nearby.begin(), nearby.end());
1265 62 : NBEdge* closest = ec.retrieve(nearby.front().second);
1266 124 : std::string objects = closest->getParameter(ROAD_OBJECTS, "");
1267 62 : if (objects != "") {
1268 : objects += " ";
1269 : }
1270 : objects += p->getID();
1271 124 : closest->setParameter(ROAD_OBJECTS, objects);
1272 : //std::cout << "poly=" << p->getID() << " closest=" << closest->getID() << "\n";
1273 : }
1274 124 : }
1275 63 : for (auto itPoi = shc.getPOIs().begin(); itPoi != shc.getPOIs().end(); itPoi++) {
1276 58 : PointOfInterest* p = itPoi->second;
1277 58 : float min[2] = { static_cast<float>(p->x()), static_cast<float>(p->y()) };
1278 58 : float max[2] = { static_cast<float>(p->x()), static_cast<float>(p->y()) };
1279 : std::set<const Named*> edges;
1280 : Named::StoringVisitor visitor(edges);
1281 : r.Search(min, max, visitor);
1282 : std::vector<std::pair<double, std::string> > nearby;
1283 1774 : for (const Named* namedEdge : edges) {
1284 1716 : NBEdge* e = const_cast<NBEdge*>(dynamic_cast<const NBEdge*>(namedEdge));
1285 1716 : const double distance = e->getLaneShape(0).distance2D(*p, true);
1286 1716 : if (distance != GeomHelper::INVALID_OFFSET && distance <= maxDist) {
1287 : // sort by distance and ID to stabilize results
1288 766 : nearby.push_back(std::make_pair(distance, e->getID()));
1289 : //if (p->getID() == "1275468911") {
1290 : // std::cout << " poly=" << p->getID() << " e=" << e->getID() << " dist=" << distance << "\n";
1291 : //}
1292 : }
1293 : }
1294 58 : if (nearby.size() > 0) {
1295 58 : std::sort(nearby.begin(), nearby.end());
1296 58 : NBEdge* closest = ec.retrieve(nearby.front().second);
1297 116 : std::string objects = closest->getParameter(ROAD_OBJECTS, "");
1298 58 : if (objects != "") {
1299 : objects += " ";
1300 : }
1301 : objects += p->getID();
1302 116 : closest->setParameter(ROAD_OBJECTS, objects);
1303 : //std::cout << "poly=" << p->getID() << " closest=" << closest->getID() << "\n";
1304 : }
1305 58 : }
1306 : }
1307 :
1308 :
1309 : void
1310 58 : NWWriter_OpenDrive::writeRoadObjectPOI(OutputDevice& device, const NBEdge* e, const PositionVector& roadShape, const PointOfInterest* poi) {
1311 58 : Position center = *poi;
1312 58 : const double edgeOffset = roadShape.nearest_offset_to_point2D(center, false);
1313 58 : if (edgeOffset == GeomHelper::INVALID_OFFSET) {
1314 0 : WRITE_WARNINGF("Cannot map road object POI '%' with center % onto edge '%'", poi->getID(), center, e->getID());
1315 : return;
1316 : }
1317 :
1318 58 : Position edgePos = roadShape.positionAtOffset2D(edgeOffset);
1319 58 : double sideOffset = center.distanceTo2D(edgePos);
1320 : // determine sign of sideOffset
1321 174 : PositionVector tmp = roadShape.getSubpart2D(MAX2(0.0, edgeOffset - 1), MIN2(roadShape.length2D(), edgeOffset + 1));
1322 58 : tmp.move2side(sideOffset);
1323 58 : if (tmp.distance2D(center) < sideOffset) {
1324 45 : sideOffset *= -1;
1325 : }
1326 : // place traffic signs on appropriate side of the road
1327 : std::string type = poi->getShapeType();
1328 116 : std::string name = StringUtils::escapeXML(poi->getParameter("name", ""), true);
1329 58 : if (poi->getShapeType() == "traffic_sign") {
1330 4 : sideOffset = getRoadSideOffset(e);
1331 : type = "pole";
1332 : name = "pole";
1333 : }
1334 :
1335 116 : device.openTag("object");
1336 58 : device.writeAttr("id", poi->getID());
1337 58 : device.writeAttr("type", type);
1338 58 : device.writeAttr("name", name);
1339 58 : device.writeAttr("s", edgeOffset);
1340 58 : device.writeAttr("t", sideOffset);
1341 58 : device.writeAttr("hdg", 0);
1342 116 : device.closeTag();
1343 58 : }
1344 :
1345 : void
1346 101 : NWWriter_OpenDrive::writeRoadObjectPoly(OutputDevice& device, const NBEdge* e, const PositionVector& roadShape, const SUMOPolygon* p) {
1347 101 : PositionVector shape = p->getShape();
1348 101 : Position center = shape.getPolygonCenter();
1349 :
1350 101 : const double edgeOffset = roadShape.nearest_offset_to_point2D(center, false);
1351 101 : if (edgeOffset == GeomHelper::INVALID_OFFSET) {
1352 0 : WRITE_WARNINGF("Cannot map road object polygon '%' with center % onto edge '%'", p->getID(), center, e->getID());
1353 : return;
1354 : }
1355 101 : Position edgePos = roadShape.positionAtOffset2D(edgeOffset);
1356 101 : const double edgeAngle = roadShape.rotationAtOffset(edgeOffset);
1357 : double sideOffset = center.distanceTo2D(edgePos);
1358 : // determine sign of sideOffset
1359 269 : PositionVector tmp = roadShape.getSubpart2D(MAX2(0.0, edgeOffset - 1), MIN2(roadShape.length2D(), edgeOffset + 1));
1360 101 : tmp.move2side(sideOffset);
1361 101 : if (tmp.distance2D(center) < sideOffset) {
1362 62 : sideOffset *= -1;
1363 : }
1364 : //std::cout << " id=" << id
1365 : // << " shape=" << shape
1366 : // << " center=" << center
1367 : // << " edgeOffset=" << edgeOffset
1368 : // << "\n";
1369 : auto shapeType = p->getShapeType();
1370 202 : device.openTag("object");
1371 101 : device.writeAttr("id", p->getID());
1372 101 : device.writeAttr("type", shapeType);
1373 202 : if (p->hasParameter("name")) {
1374 36 : device.writeAttr("name", StringUtils::escapeXML(p->getParameter("name", ""), true));
1375 : }
1376 202 : device.writeAttr("s", edgeOffset);
1377 132 : device.writeAttr("t", shapeType == "crosswalk" && !lefthand ? 0 : sideOffset);
1378 101 : double hdg = -edgeAngle;
1379 202 : if (p->hasParameter("hdg")) {
1380 : try {
1381 39 : hdg = StringUtils::toDoubleSecure(p->getParameter("hdg", ""), 0);
1382 0 : } catch (NumberFormatException&) {}
1383 : }
1384 101 : device.writeAttr("hdg", hdg);
1385 202 : if (p->hasParameter("length")) {
1386 : try {
1387 78 : device.writeAttr("length", StringUtils::toDoubleSecure(p->getParameter("length", ""), 0));
1388 0 : } catch (NumberFormatException&) {}
1389 : }
1390 202 : if (p->hasParameter("width")) {
1391 : try {
1392 78 : device.writeAttr("width", StringUtils::toDoubleSecure(p->getParameter("width", ""), 0));
1393 0 : } catch (NumberFormatException&) {}
1394 : }
1395 101 : double height = 0;
1396 202 : if (p->hasParameter("height")) {
1397 : try {
1398 3 : height = StringUtils::toDoubleSecure(p->getParameter("height", ""), 0);
1399 6 : device.writeAttr("height", height);
1400 0 : } catch (NumberFormatException&) {}
1401 : }
1402 : //device.openTag("outlines");
1403 101 : device.openTag("outline");
1404 101 : device.writeAttr("id", 0);
1405 101 : device.writeAttr("fillType", "concrete");
1406 101 : device.writeAttr("outer", "true");
1407 142 : device.writeAttr("closed", p->getShape().isClosed() ? "true" : "false");
1408 101 : device.writeAttr("laneType", "border");
1409 :
1410 101 : shape.sub(center);
1411 101 : if (hdg != -edgeAngle) {
1412 36 : shape.rotate2D(-edgeAngle - hdg);
1413 : }
1414 : int i = 0;
1415 1244 : for (Position pos : shape) {
1416 1143 : device.openTag("cornerLocal");
1417 1143 : device.writeAttr("u", pos.x());
1418 2286 : device.writeAttr("v", pos.y());
1419 1143 : device.writeAttr("z", MAX2(0.0, p->getShapeLayer()));
1420 1143 : device.writeAttr("height", height);
1421 1143 : device.writeAttr("id", i++);
1422 2286 : device.closeTag();
1423 : }
1424 :
1425 :
1426 : //device.closeTag();
1427 101 : device.closeTag();
1428 202 : device.closeTag();
1429 101 : }
1430 :
1431 : /****************************************************************************/
|