LCOV - code coverage report
Current view: top level - src/netwrite - NWWriter_OpenDrive.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 97.0 % 797 773
Test Date: 2024-11-22 15:46:21 Functions: 100.0 % 24 24

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

Generated by: LCOV version 2.0-1