SUMO - Simulation of Urban MObility
NWWriter_OpenDrive.cpp
Go to the documentation of this file.
1 /****************************************************************************/
8 // Exporter writing networks using the openDRIVE format
9 /****************************************************************************/
10 // SUMO, Simulation of Urban MObility; see http://sumo.dlr.de/
11 // Copyright (C) 2011-2017 DLR (http://www.dlr.de/) and contributors
12 /****************************************************************************/
13 //
14 // This file is part of SUMO.
15 // SUMO is free software: you can redistribute it and/or modify
16 // it under the terms of the GNU General Public License as published by
17 // the Free Software Foundation, either version 3 of the License, or
18 // (at your option) any later version.
19 //
20 /****************************************************************************/
21 
22 
23 // ===========================================================================
24 // included modules
25 // ===========================================================================
26 #ifdef _MSC_VER
27 #include <windows_config.h>
28 #else
29 #include <config.h>
30 #endif
31 
32 #include <ctime>
33 #include "NWWriter_OpenDrive.h"
36 #include <netbuild/NBEdge.h>
37 #include <netbuild/NBEdgeCont.h>
38 #include <netbuild/NBNode.h>
39 #include <netbuild/NBNodeCont.h>
40 #include <netbuild/NBNetBuilder.h>
43 #include <utils/geom/bezier.h>
45 #include <utils/common/StdDefs.h>
48 
49 #ifdef CHECK_MEMORY_LEAKS
50 #include <foreign/nvwa/debug_new.h>
51 #endif // CHECK_MEMORY_LEAKS
52 
53 //#define DEBUG_SMOOTH_GEOM
54 #define DEBUGCOND true
55 
56 #define MIN_TURN_DIAMETER 2.0
57 
58 
59 // ===========================================================================
60 // method definitions
61 // ===========================================================================
62 // ---------------------------------------------------------------------------
63 // static methods
64 // ---------------------------------------------------------------------------
65 void
67  // check whether an opendrive-file shall be generated
68  if (!oc.isSet("opendrive-output")) {
69  return;
70  }
71  const NBNodeCont& nc = nb.getNodeCont();
72  const NBEdgeCont& ec = nb.getEdgeCont();
73  const bool origNames = oc.getBool("output.original-names");
74  const SUMOReal straightThresh = DEG2RAD(oc.getFloat("opendrive-output.straight-threshold"));
75  // some internal mapping containers
76  int nodeID = 1;
77  int edgeID = nc.size() * 10; // distinct from node ids
78  StringBijection<int> edgeMap;
79  StringBijection<int> nodeMap;
80  //
81  OutputDevice& device = OutputDevice::getDevice(oc.getString("opendrive-output"));
82  device << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
83  device.openTag("OpenDRIVE");
84  time_t now = time(0);
85  std::string dstr(ctime(&now));
87  // write header
88  device.openTag("header");
89  device.writeAttr("revMajor", "1");
90  device.writeAttr("revMinor", "4");
91  device.writeAttr("name", "");
92  device.writeAttr("version", "1.00");
93  device.writeAttr("date", dstr.substr(0, dstr.length() - 1));
94  device.writeAttr("north", b.ymax());
95  device.writeAttr("south", b.ymin());
96  device.writeAttr("east", b.xmax());
97  device.writeAttr("west", b.xmin());
98  /* @note obsolete in 1.4
99  device.writeAttr("maxRoad", ec.size());
100  device.writeAttr("maxJunc", nc.size());
101  device.writeAttr("maxPrg", 0);
102  */
103  device.closeTag();
104 
105  // write normal edges (road)
106  for (std::map<std::string, NBEdge*>::const_iterator i = ec.begin(); i != ec.end(); ++i) {
107  const NBEdge* e = (*i).second;
108 
109  // buffer output because some fields are computed out of order
110  OutputDevice_String elevationOSS(false, 3);
111  elevationOSS.setPrecision(8);
112  OutputDevice_String planViewOSS(false, 2);
113  planViewOSS.setPrecision(8);
114  SUMOReal length = 0;
115 
116  planViewOSS.openTag("planView");
117  planViewOSS.setPrecision(8); // geometry hdg requires higher precision
118  // for the shape we need to use the leftmost border of the leftmost lane
119  const std::vector<NBEdge::Lane>& lanes = e->getLanes();
121 #ifdef DEBUG_SMOOTH_GEOM
122  if (DEBUGCOND) {
123  std::cout << "write planview for edge " << e->getID() << "\n";
124  }
125 #endif
126 
127  if (ls.size() == 2 || e->getPermissions() == SVC_PEDESTRIAN) {
128  // foot paths may contain sharp angles
129  length = writeGeomLines(ls, planViewOSS, elevationOSS);
130  } else {
131  bool ok = writeGeomSmooth(ls, e->getSpeed(), planViewOSS, elevationOSS, straightThresh, length);
132  if (!ok) {
133  WRITE_WARNING("Could not compute smooth shape for edge '" + e->getID() + "'.");
134  }
135  }
136  planViewOSS.closeTag();
137 
138  device.openTag("road");
139  device.writeAttr("name", StringUtils::escapeXML(e->getStreetName()));
140  device.setPrecision(8); // length requires higher precision
141  device.writeAttr("length", MAX2(POSITION_EPS, length));
142  device.setPrecision(gPrecision);
143  device.writeAttr("id", getID(e->getID(), edgeMap, edgeID));
144  device.writeAttr("junction", -1);
145  const bool hasSucc = e->getConnections().size() > 0;
146  const bool hasPred = e->getIncomingEdges().size() > 0;
147  if (hasPred || hasSucc) {
148  device.openTag("link");
149  if (hasPred) {
150  device.openTag("predecessor");
151  device.writeAttr("elementType", "junction");
152  device.writeAttr("elementId", getID(e->getFromNode()->getID(), nodeMap, nodeID));
153  device.closeTag();
154  }
155  if (hasSucc) {
156  device.openTag("successor");
157  device.writeAttr("elementType", "junction");
158  device.writeAttr("elementId", getID(e->getToNode()->getID(), nodeMap, nodeID));
159  device.closeTag();
160  }
161  device.closeTag();
162  }
163  device.openTag("type").writeAttr("s", 0).writeAttr("type", "town").closeTag();
164  device << planViewOSS.getString();
165  writeElevationProfile(ls, device, elevationOSS);
166  device << " <lateralProfile/>\n";
167  device << " <lanes>\n";
168  device << " <laneSection s=\"0\">\n";
169  writeEmptyCenterLane(device, "solid", 0.13);
170  device << " <right>\n";
171  for (int j = e->getNumLanes(); --j >= 0;) {
172  device << " <lane id=\"-" << e->getNumLanes() - j << "\" type=\"" << getLaneType(e->getPermissions(j)) << "\" level=\"true\">\n";
173  device << " <link/>\n";
174  // this could be used for geometry-link junctions without u-turn,
175  // predecessor and sucessors would be lane indices,
176  // road predecessor / succesfors would be of type 'road' rather than
177  // 'junction'
178  //device << " <predecessor id=\"-1\"/>\n";
179  //device << " <successor id=\"-1\"/>\n";
180  //device << " </link>\n";
181  device << " <width sOffset=\"0\" a=\"" << e->getLaneWidth(j) << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
182  std::string markType = "broken";
183  if (j == 0) {
184  markType = "solid";
185  }
186  device << " <roadMark sOffset=\"0\" type=\"" << markType << "\" weight=\"standard\" color=\"standard\" width=\"0.13\"/>\n";
187  device << " <speed sOffset=\"0\" max=\"" << lanes[j].speed << "\"/>\n";
188  device << " </lane>\n";
189  }
190  device << " </right>\n";
191  device << " </laneSection>\n";
192  device << " </lanes>\n";
193  device << " <objects/>\n";
194  device << " <signals/>\n";
195  if (origNames) {
196  device << " <userData code=\"sumoId\" value=\"" << e->getID() << "\"/>\n";
197  }
198  device.closeTag();
200  }
201  device.lf();
202 
203  // write junction-internal edges (road). In OpenDRIVE these are called 'paths' or 'connecting roads'
204  for (std::map<std::string, NBNode*>::const_iterator i = nc.begin(); i != nc.end(); ++i) {
205  NBNode* n = (*i).second;
206  const std::vector<NBEdge*>& incoming = (*i).second->getIncomingEdges();
207  for (std::vector<NBEdge*>::const_iterator j = incoming.begin(); j != incoming.end(); ++j) {
208  const NBEdge* inEdge = *j;
209  const std::vector<NBEdge::Connection>& elv = inEdge->getConnections();
210  for (std::vector<NBEdge::Connection>::const_iterator k = elv.begin(); k != elv.end(); ++k) {
211  const NBEdge::Connection& c = *k;
212  const NBEdge* outEdge = c.toEdge;
213  if (outEdge == 0) {
214  continue;
215  }
216  const SUMOReal width = c.toEdge->getLaneWidth(c.toLane);
217  const PositionVector begShape = getLeftLaneBorder(inEdge, c.fromLane);
218  const PositionVector endShape = getLeftLaneBorder(outEdge, c.toLane);
219  //std::cout << "computing reference line for internal lane " << c.getInternalLaneID() << " begLane=" << inEdge->getLaneShape(c.fromLane) << " endLane=" << outEdge->getLaneShape(c.toLane) << "\n";
220 
221  SUMOReal length;
222  PositionVector fallBackShape;
223  fallBackShape.push_back(begShape.back());
224  fallBackShape.push_back(endShape.front());
225  const bool turnaround = inEdge->isTurningDirectionAt(outEdge);
226  bool ok = true;
227  PositionVector init = NBNode::bezierControlPoints(begShape, endShape, turnaround, 25, 25, ok, 0, straightThresh);
228  if (init.size() == 0) {
229  length = fallBackShape.length2D();
230  // problem with turnarounds is known, method currently returns 'ok' (#2539)
231  if (!ok) {
232  WRITE_WARNING("Could not compute smooth shape from lane '" + inEdge->getLaneID(c.fromLane) + "' to lane '" + outEdge->getLaneID(c.toLane) + "'. Use option 'junctions.scurve-stretch' or increase radius of junction '" + inEdge->getToNode()->getID() + "' to fix this.");
233  }
234  } else {
235  length = bezier(init, 12).length2D();
236  }
237 
238  device.openTag("road");
239  device.writeAttr("name", c.getInternalLaneID());
240  device.setPrecision(8); // length requires higher precision
241  device.writeAttr("length", MAX2(POSITION_EPS, length));
242  device.setPrecision(gPrecision);
243  device.writeAttr("id", getID(c.getInternalLaneID(), edgeMap, edgeID));
244  device.writeAttr("junction", getID(n->getID(), nodeMap, nodeID));
245  device.openTag("link");
246  device.openTag("predecessor");
247  device.writeAttr("elementType", "road");
248  device.writeAttr("elementId", getID(inEdge->getID(), edgeMap, edgeID));
249  device.writeAttr("contactPoint", "end");
250  device.closeTag();
251  device.openTag("successor");
252  device.writeAttr("elementType", "road");
253  device.writeAttr("elementId", getID(outEdge->getID(), edgeMap, edgeID));
254  device.writeAttr("contactPoint", "start");
255  device.closeTag();
256  device.closeTag();
257  device.openTag("type").writeAttr("s", 0).writeAttr("type", "town").closeTag();
258  device.openTag("planView");
259  device.setPrecision(8); // geometry hdg requires higher precision
260  OutputDevice_String elevationOSS(false, 3);
261 #ifdef DEBUG_SMOOTH_GEOM
262  if (DEBUGCOND) {
263  std::cout << "write planview for internal edge " << c.getInternalLaneID() << " init=" << init << " fallback=" << fallBackShape << "\n";
264  }
265 #endif
266  if (init.size() == 0) {
267  writeGeomLines(fallBackShape, device, elevationOSS);
268  } else {
269  writeGeomPP3(device, elevationOSS, init, length);
270  }
271  device.setPrecision(gPrecision);
272  device.closeTag();
273  writeElevationProfile(fallBackShape, device, elevationOSS);
274  device << " <lateralProfile/>\n";
275  device << " <lanes>\n";
276  device << " <laneSection s=\"0\">\n";
277  writeEmptyCenterLane(device, "none", 0);
278  device << " <right>\n";
279  device << " <lane id=\"-1\" type=\"" << getLaneType(outEdge->getPermissions(c.toLane)) << "\" level=\"true\">\n";
280  device << " <link>\n";
281  device << " <predecessor id=\"-" << inEdge->getNumLanes() - c.fromLane << "\"/>\n";
282  device << " <successor id=\"-" << outEdge->getNumLanes() - c.toLane << "\"/>\n";
283  device << " </link>\n";
284  device << " <width sOffset=\"0\" a=\"" << width << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
285  device << " <roadMark sOffset=\"0\" type=\"none\" weight=\"standard\" color=\"standard\" width=\"0.13\"/>\n";
286  device << " </lane>\n";
287  device << " </right>\n";
288  device << " </laneSection>\n";
289  device << " </lanes>\n";
290  device << " <objects/>\n";
291  device << " <signals/>\n";
292  device.closeTag();
293  }
294  }
295  }
296 
297  // write junctions (junction)
298  for (std::map<std::string, NBNode*>::const_iterator i = nc.begin(); i != nc.end(); ++i) {
299  NBNode* n = (*i).second;
300  const std::vector<NBEdge*>& incoming = n->getIncomingEdges();
301  // check if any connections must be written
302  int numConnections = 0;
303  for (std::vector<NBEdge*>::const_iterator j = incoming.begin(); j != incoming.end(); ++j) {
304  numConnections += (int)((*j)->getConnections().size());
305  }
306  if (numConnections == 0) {
307  continue;
308  }
309  device << " <junction name=\"" << n->getID() << "\" id=\"" << getID(n->getID(), nodeMap, nodeID) << "\">\n";
310  int index = 0;
311  for (std::vector<NBEdge*>::const_iterator j = incoming.begin(); j != incoming.end(); ++j) {
312  const NBEdge* inEdge = *j;
313  const std::vector<NBEdge::Connection>& elv = inEdge->getConnections();
314  for (std::vector<NBEdge::Connection>::const_iterator k = elv.begin(); k != elv.end(); ++k) {
315  const NBEdge::Connection& c = *k;
316  const NBEdge* outEdge = c.toEdge;
317  if (outEdge == 0) {
318  continue;
319  }
320  device << " <connection id=\""
321  << index << "\" incomingRoad=\"" << getID(inEdge->getID(), edgeMap, edgeID)
322  << "\" connectingRoad=\""
323  << getID(c.getInternalLaneID(), edgeMap, edgeID)
324  << "\" contactPoint=\"start\">\n";
325  device << " <laneLink from=\"-" << inEdge->getNumLanes() - c.fromLane
326  << "\" to=\"-1" // every connection has its own edge
327  << "\"/>\n";
328  device << " </connection>\n";
329  ++index;
330  }
331  }
332  device << " </junction>\n";
333  }
334 
335  device.closeTag();
336  device.close();
337 }
338 
339 
340 SUMOReal
341 NWWriter_OpenDrive::writeGeomLines(const PositionVector& shape, OutputDevice& device, OutputDevice& elevationDevice, SUMOReal offset) {
342  for (int j = 0; j < (int)shape.size() - 1; ++j) {
343  const Position& p = shape[j];
344  const Position& p2 = shape[j + 1];
345  const SUMOReal hdg = shape.angleAt2D(j);
346  const SUMOReal length = p.distanceTo2D(p2);
347  device.openTag("geometry");
348  device.writeAttr("s", offset);
349  device.writeAttr("x", p.x());
350  device.writeAttr("y", p.y());
351  device.writeAttr("hdg", hdg);
352  device.writeAttr("length", length);
353  device.openTag("line").closeTag();
354  device.closeTag();
355  elevationDevice << " <elevation s=\"" << offset << "\" a=\"" << p.z() << "\" b=\"" << (p2.z() - p.z()) / MAX2(POSITION_EPS, length) << "\" c=\"0\" d=\"0\"/>\n";
356  offset += length;
357  }
358  return offset;
359 }
360 
361 
362 void
363 NWWriter_OpenDrive::writeEmptyCenterLane(OutputDevice& device, const std::string& mark, SUMOReal markWidth) {
364  device << " <center>\n";
365  device << " <lane id=\"0\" type=\"none\" level=\"true\">\n";
366  device << " <link/>\n";
367  device << " <roadMark sOffset=\"0\" type=\"" << mark << "\" weight=\"standard\" color=\"standard\" width=\"" << markWidth << "\"/>\n";
368  device << " </lane>\n";
369  device << " </center>\n";
370 }
371 
372 
373 int
374 NWWriter_OpenDrive::getID(const std::string& origID, StringBijection<int>& map, int& lastID) {
375  if (map.hasString(origID)) {
376  return map.get(origID);
377  }
378  map.insert(origID, lastID++);
379  return lastID - 1;
380 }
381 
382 
383 std::string
385  switch (permissions) {
386  case SVC_PEDESTRIAN:
387  return "sidewalk";
388  //case (SVC_BICYCLE | SVC_PEDESTRIAN):
389  // WRITE_WARNING("Ambiguous lane type (biking+driving) for road '" + roadID + "'");
390  // return "sidewalk";
391  case SVC_BICYCLE:
392  return "biking";
393  case 0:
394  // ambiguous
395  return "none";
396  case SVC_RAIL:
397  case SVC_RAIL_URBAN:
398  case SVC_RAIL_ELECTRIC:
399  return "rail";
400  case SVC_TRAM:
401  return "tram";
402  default: {
403  // complex permissions
404  if (permissions == SVCAll) {
405  return "driving";
406  } else if (isRailway(permissions)) {
407  return "rail";
408  } else if ((permissions & SVC_PASSENGER) != 0) {
409  return "driving";
410  } else {
411  return "restricted";
412  }
413  }
414  }
415 }
416 
417 
419 NWWriter_OpenDrive::getLeftLaneBorder(const NBEdge* edge, int laneIndex) {
420  if (laneIndex == -1) {
421  // leftmost lane
422  laneIndex = (int)edge->getNumLanes() - 1;
423  }
425  // PositionVector result = edge->getLaneShape(laneIndex);
426  // (and the moveo2side)
427  // However, the lanes in SUMO have a small lateral gap (SUMO_const_laneOffset) to account for markings
428  // In OpenDRIVE this gap does not exists so we have to do all lateral
429  // computations based on the reference line
430  // This assumes that the 'stop line' for all lanes is colinear!
431  const int leftmost = (int)edge->getNumLanes() - 1;
432  SUMOReal widthOffset = -(edge->getLaneWidth(leftmost) / 2);
433  // collect lane widths from left border of edge to left border of lane to connect to
434  for (int i = leftmost; i > laneIndex; i--) {
435  widthOffset += edge->getLaneWidth(i);
436  }
437  PositionVector result = edge->getLaneShape(leftmost);
438  try {
439  result.move2side(widthOffset);
440  } catch (InvalidArgument&) { }
441  return result;
442 }
443 
444 
445 SUMOReal
447  OutputDevice& device,
448  OutputDevice& elevationDevice,
449  PositionVector init,
450  SUMOReal length,
451  SUMOReal offset) {
452  assert(init.size() == 3 || init.size() == 4);
453 
454  // avoid division by 0
455  length = MAX2(POSITION_EPS, length);
456 
457  const Position p = init.front();
458  const SUMOReal hdg = init.angleAt2D(0);
459 
460  // backup elevation values
461  const PositionVector initZ = init;
462  // translate to u,v coordinates
463  init.add(-p.x(), -p.y(), -p.z());
464  init.rotate2D(-hdg);
465 
466  // parametric coefficients
467  SUMOReal aU, bU, cU, dU;
468  SUMOReal aV, bV, cV, dV;
469  SUMOReal aZ, bZ, cZ, dZ;
470 
471  // unfactor the Bernstein polynomials of degree 2 (or 3) and collect the coefficients
472  if (init.size() == 3) {
473  //f(x, a, b ,c) = a + (2*b - 2*a)*x + (a - 2*b + c)*x*x
474  aU = init[0].x();
475  bU = 2 * init[1].x() - 2 * init[0].x();
476  cU = init[0].x() - 2 * init[1].x() + init[2].x();
477  dU = 0;
478 
479  aV = init[0].y();
480  bV = 2 * init[1].y() - 2 * init[0].y();
481  cV = init[0].y() - 2 * init[1].y() + init[2].y();
482  dV = 0;
483 
484  // elevation is not parameteric on [0:1] but on [0:length]
485  aZ = initZ[0].z();
486  bZ = (2 * initZ[1].z() - 2 * initZ[0].z()) / length;
487  cZ = (initZ[0].z() - 2 * initZ[1].z() + initZ[2].z()) / (length * length);
488  dZ = 0;
489 
490  } else {
491  // 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))
492  aU = init[0].x();
493  bU = 3 * init[1].x() - 3 * init[0].x();
494  cU = 3 * init[0].x() - 6 * init[1].x() + 3 * init[2].x();
495  dU = -init[0].x() + 3 * init[1].x() - 3 * init[2].x() + init[3].x();
496 
497  aV = init[0].y();
498  bV = 3 * init[1].y() - 3 * init[0].y();
499  cV = 3 * init[0].y() - 6 * init[1].y() + 3 * init[2].y();
500  dV = -init[0].y() + 3 * init[1].y() - 3 * init[2].y() + init[3].y();
501 
502  // elevation is not parameteric on [0:1] but on [0:length]
503  aZ = initZ[0].z();
504  bZ = (3 * initZ[1].z() - 3 * initZ[0].z()) / length;
505  cZ = (3 * initZ[0].z() - 6 * initZ[1].z() + 3 * initZ[2].z()) / (length * length);
506  dZ = (-initZ[0].z() + 3 * initZ[1].z() - 3 * initZ[2].z() + initZ[3].z()) / (length * length * length);
507  }
508 
509  device.openTag("geometry");
510  device.writeAttr("s", offset);
511  device.writeAttr("x", p.x());
512  device.writeAttr("y", p.y());
513  device.writeAttr("hdg", hdg);
514  device.writeAttr("length", length);
515 
516  device.openTag("paramPoly3");
517  device.writeAttr("aU", aU);
518  device.writeAttr("bU", bU);
519  device.writeAttr("cU", cU);
520  device.writeAttr("dU", dU);
521  device.writeAttr("aV", aV);
522  device.writeAttr("bV", bV);
523  device.writeAttr("cV", cV);
524  device.writeAttr("dV", dV);
525  device.closeTag();
526  device.closeTag();
527 
528  // write elevation
529  elevationDevice.openTag("elevation");
530  elevationDevice.writeAttr("s", offset);
531  elevationDevice.writeAttr("a", aZ);
532  elevationDevice.writeAttr("b", bZ);
533  elevationDevice.writeAttr("c", cZ);
534  elevationDevice.writeAttr("d", dZ);
535  elevationDevice.closeTag();
536 
537  return offset + length;
538 }
539 
540 
541 bool
542 NWWriter_OpenDrive::writeGeomSmooth(const PositionVector& shape, SUMOReal speed, OutputDevice& device, OutputDevice& elevationDevice, SUMOReal straightThresh, SUMOReal& length) {
543 #ifdef DEBUG_SMOOTH_GEOM
544  if (DEBUGCOND) {
545  std::cout << "writeGeomSmooth\n n=" << shape.size() << " shape=" << toString(shape) << "\n";
546  }
547 #endif
548  bool ok = true;
549  const SUMOReal longThresh = speed; // 16.0; // make user-configurable (should match the sampling rate of the source data)
550  const SUMOReal curveCutout = longThresh / 2; // 8.0; // make user-configurable (related to the maximum turning rate)
551  // 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')
552  assert(longThresh >= 2 * curveCutout);
553  assert(shape.size() > 2);
554  // add intermediate points wherever there is a strong angular change between long segments
555  // assume the geometry is simplified so as not to contain consecutive colinear points
556  PositionVector shape2 = shape;
557  SUMOReal maxAngleDiff = 0;
558  SUMOReal offset = 0;
559  for (int j = 1; j < (int)shape.size() - 1; ++j) {
560  //const SUMOReal hdg = shape.angleAt2D(j);
561  const Position& p0 = shape[j - 1];
562  const Position& p1 = shape[j];
563  const Position& p2 = shape[j + 1];
564  const SUMOReal dAngle = fabs(GeomHelper::angleDiff(p0.angleTo2D(p1), p1.angleTo2D(p2)));
565  const SUMOReal length1 = p0.distanceTo2D(p1);
566  const SUMOReal length2 = p1.distanceTo2D(p2);
567  maxAngleDiff = MAX2(maxAngleDiff, dAngle);
568 #ifdef DEBUG_SMOOTH_GEOM
569  if (DEBUGCOND) {
570  std::cout << " j=" << j << " dAngle=" << RAD2DEG(dAngle) << " length1=" << length1 << " length2=" << length2 << "\n";
571  }
572 #endif
573  if (dAngle > straightThresh
574  && (length1 > longThresh || j == 1)
575  && (length2 > longThresh || j == (int)shape.size() - 2)) {
576  shape2.insertAtClosest(shape.positionAtOffset2D(offset + length1 - MIN2(length1 - POSITION_EPS, curveCutout)));
577  shape2.insertAtClosest(shape.positionAtOffset2D(offset + length1 + MIN2(length2 - POSITION_EPS, curveCutout)));
578  shape2.removeClosest(p1);
579  }
580  offset += length1;
581  }
582  const int numPoints = (int)shape2.size();
583 #ifdef DEBUG_SMOOTH_GEOM
584  if (DEBUGCOND) {
585  std::cout << " n=" << numPoints << " shape2=" << toString(shape2) << "\n";
586  }
587 #endif
588 
589  if (maxAngleDiff < straightThresh) {
590  length = writeGeomLines(shape2, device, elevationDevice, 0);
591 #ifdef DEBUG_SMOOTH_GEOM
592  if (DEBUGCOND) {
593  std::cout << " special case: all lines. maxAngleDiff=" << maxAngleDiff << "\n";
594  }
595 #endif
596  return ok;
597  }
598 
599  // write the long segments as lines, short segments as curves
600  offset = 0;
601  for (int j = 0; j < numPoints - 1; ++j) {
602  const Position& p0 = shape2[j];
603  const Position& p1 = shape2[j + 1];
604  PositionVector line;
605  line.push_back(p0);
606  line.push_back(p1);
607  const SUMOReal lineLength = line.length2D();
608  if (lineLength >= longThresh) {
609  offset = writeGeomLines(line, device, elevationDevice, offset);
610 #ifdef DEBUG_SMOOTH_GEOM
611  if (DEBUGCOND) {
612  std::cout << " writeLine=" << toString(line) << "\n";
613  }
614 #endif
615  } else {
616  // find control points
617  PositionVector begShape;
618  PositionVector endShape;
619  if (j == 0 || j == numPoints - 2) {
620  // keep the angle of the first/last segment but end at the front of the shape
621  begShape = line;
622  begShape.add(p0 - begShape.back());
623  } else if (j == 1 || p0.distanceTo2D(shape2[j - 1]) > longThresh) {
624  // use the previous segment if it is long or the first one
625  begShape.push_back(shape2[j - 1]);
626  begShape.push_back(p0);
627  } else {
628  // end at p0 with mean angle of the previous and current segment
629  begShape.push_back(shape2[j - 1]);
630  begShape.push_back(p1);
631  begShape.add(p0 - begShape.back());
632  }
633 
634  if (j == 0 || j == numPoints - 2) {
635  // keep the angle of the first/last segment but start at the end of the shape
636  endShape = line;
637  endShape.add(p1 - endShape.front());
638  } else if (j == numPoints - 3 || p1.distanceTo2D(shape2[j + 2]) > longThresh) {
639  // use the next segment if it is long or the final one
640  endShape.push_back(p1);
641  endShape.push_back(shape2[j + 2]);
642  } else {
643  // start at p1 with mean angle of the current and next segment
644  endShape.push_back(p0);
645  endShape.push_back(shape2[j + 2]);
646  endShape.add(p1 - endShape.front());
647  }
648  const SUMOReal extrapolateLength = MIN2((SUMOReal)25, lineLength / 4);
649  PositionVector init = NBNode::bezierControlPoints(begShape, endShape, false, extrapolateLength, extrapolateLength, ok, 0, straightThresh);
650  if (init.size() == 0) {
651  // could not compute control points, write line
652  offset = writeGeomLines(line, device, elevationDevice, offset);
653 #ifdef DEBUG_SMOOTH_GEOM
654  if (DEBUGCOND) {
655  std::cout << " writeLine lineLength=" << lineLength << " begShape" << j << "=" << toString(begShape) << " endShape" << j << "=" << toString(endShape) << " init" << j << "=" << toString(init) << "\n";
656  }
657 #endif
658  } else {
659  // write bezier
660  const SUMOReal curveLength = bezier(init, 12).length2D();
661  offset = writeGeomPP3(device, elevationDevice, init, curveLength, offset);
662 #ifdef DEBUG_SMOOTH_GEOM
663  if (DEBUGCOND) {
664  std::cout << " writeCurve lineLength=" << lineLength << " curveLength=" << curveLength << " begShape" << j << "=" << toString(begShape) << " endShape" << j << "=" << toString(endShape) << " init" << j << "=" << toString(init) << "\n";
665  }
666 #endif
667  }
668  }
669  }
670  length = offset;
671  return ok;
672 }
673 
674 
675 void
677  // check if the shape is flat
678  bool flat = true;
679  SUMOReal z = shape.size() == 0 ? 0 : shape[0].z();
680  for (int i = 1; i < (int)shape.size(); ++i) {
681  if (fabs(shape[i].z() - z) > NUMERICAL_EPS) {
682  flat = false;
683  break;
684  }
685  }
686  device << " <elevationProfile>\n";
687  if (flat) {
688  device << " <elevation s=\"0\" a=\"" << z << "\" b=\"0\" c=\"0\" d=\"0\"/>\n";
689  } else {
690  device << elevationDevice.getString();
691  }
692  device << " </elevationProfile>\n";
693 
694 }
695 
696 
697 void
699  if (e->getNumLanes() > 1) {
700  // compute 'stop line' of rightmost lane
701  const PositionVector shape0 = e->getLaneShape(0);
702  assert(shape0.size() >= 2);
703  const Position& from = shape0[-2];
704  const Position& to = shape0[-1];
705  PositionVector stopLine;
706  stopLine.push_back(to);
707  stopLine.push_back(to - PositionVector::sideOffset(from, to, -1000.0));
708  // endpoints of all other lanes should be on the stop line
709  for (int lane = 1; lane < e->getNumLanes(); ++lane) {
710  const SUMOReal dist = stopLine.distance2D(e->getLaneShape(lane)[-1]);
711  if (dist > NUMERICAL_EPS) {
712  WRITE_WARNING("Uneven stop line at lane '" + e->getLaneID(lane) + "' (dist=" + toString(dist) + ") cannot be represented in OpenDRIVE.");
713  }
714  }
715  }
716 }
717 
718 /****************************************************************************/
719 
static SUMOReal writeGeomLines(const PositionVector &shape, OutputDevice &device, OutputDevice &elevationDevice, SUMOReal offset=0)
write geometry as sequence of lines (sumo style)
#define DEBUGCOND
static SUMOReal writeGeomPP3(OutputDevice &device, OutputDevice &elevationDevice, PositionVector init, SUMOReal length, SUMOReal offset=0)
write geometry as a single bezier curve (paramPoly3)
OutputDevice & writeAttr(const SumoXMLAttr attr, const T &val)
writes a named attribute
Definition: OutputDevice.h:256
static Position sideOffset(const Position &beg, const Position &end, const SUMOReal amount)
get a side position of position vector using a offset
const EdgeVector & getIncomingEdges() const
Returns this node&#39;s incoming edges (The edges which yield in this node)
Definition: NBNode.h:240
A structure which describes a connection between edges or lanes.
Definition: NBEdge.h:157
int toLane
The lane the connections yields in.
Definition: NBEdge.h:182
std::string getString() const
Returns the current content as a string.
is a pedestrian
NBEdge * toEdge
The edge the connections yields in.
Definition: NBEdge.h:179
bool hasString(const std::string &str) const
vehicle is a not electrified rail
int gPrecision
the precision for floating point outputs
Definition: StdDefs.cpp:34
static PositionVector bezierControlPoints(const PositionVector &begShape, const PositionVector &endShape, bool isTurnaround, SUMOReal extrapolateBeg, SUMOReal extrapolateEnd, bool &ok, NBNode *recordError=0, SUMOReal straightThresh=DEG2RAD(5))
get bezier control points
Definition: NBNode.cpp:491
SUMOReal ymin() const
Returns minimum y-coordinate.
Definition: Boundary.cpp:142
const std::vector< NBEdge::Lane > & getLanes() const
Returns the lane definitions.
Definition: NBEdge.h:559
vehicle is a bicycle
int SVCPermissions
bool getBool(const std::string &name) const
Returns the boolean-value of the named option (only for Option_Bool)
The representation of a single edge during network building.
Definition: NBEdge.h:71
SUMOReal xmin() const
Returns minimum x-coordinate.
Definition: Boundary.cpp:130
vehicle is a light rail
EdgeVector getIncomingEdges() const
Returns the list of incoming edges unsorted.
Definition: NBEdge.cpp:1045
void setPrecision(int precision=gPrecision)
Sets the precison or resets it to default.
T MAX2(T a, T b)
Definition: StdDefs.h:70
SUMOReal getLaneWidth() const
Returns the default width of lanes of this edge.
Definition: NBEdge.h:512
SUMOReal getFloat(const std::string &name) const
Returns the SUMOReal-value of the named option (only for Option_Float)
#define RAD2DEG(x)
Definition: GeomHelper.h:46
bool isRailway(SVCPermissions permissions)
Returns whether an edge with the given permission is a railway edge.
const SVCPermissions SVCAll
static SUMOReal angleDiff(const SUMOReal angle1, const SUMOReal angle2)
Returns the difference of the second angle to the first angle in radiants.
Definition: GeomHelper.cpp:178
SUMOReal x() const
Returns the x-position.
Definition: Position.h:63
Position positionAtOffset2D(SUMOReal pos, SUMOReal lateralOffset=0) const
Returns the position at the given length.
SUMOReal xmax() const
Returns maximum x-coordinate.
Definition: Boundary.cpp:136
A class that stores a 2D geometrical boundary.
Definition: Boundary.h:48
vehicle is a (possibly fast moving) electric rail
#define WRITE_WARNING(msg)
Definition: MsgHandler.h:200
SUMOReal distance2D(const Position &p, bool perpendicular=false) const
closest 2D-distance to point p (or -1 if perpendicular is true and the point is beyond this vector) ...
vehicle is a city rail
void rotate2D(SUMOReal angle)
std::string getString(const std::string &name) const
Returns the string-value of the named option (only for Option_String)
const std::string & getID() const
Returns the id.
Definition: Named.h:66
SUMOReal length2D() const
Returns the length.
void insert(const std::string str, const T key, bool checkDuplicates=true)
static void writeNetwork(const OptionsCont &oc, NBNetBuilder &nb)
Writes the network into a openDRIVE-file.
std::map< std::string, NBEdge * >::const_iterator end() const
Returns the pointer to the end of the stored edges.
Definition: NBEdgeCont.h:198
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
Definition: ToString.h:55
std::string getInternalLaneID() const
get ID of internal lane
Definition: NBEdge.cpp:82
int fromLane
The lane the connections starts at.
Definition: NBEdge.h:176
A point in 2D or 3D with translation and scaling methods.
Definition: Position.h:46
NBEdgeCont & getEdgeCont()
Definition: NBNetBuilder.h:144
A list of positions.
void add(SUMOReal xoff, SUMOReal yoff, SUMOReal zoff)
SUMOReal z() const
Returns the z-position.
Definition: Position.h:73
int getNumLanes() const
Returns the number of lanes.
Definition: NBEdge.h:402
bool isTurningDirectionAt(const NBEdge *const edge) const
Returns whether the given edge is the opposite direction to this edge.
Definition: NBEdge.cpp:2226
static void writeEmptyCenterLane(OutputDevice &device, const std::string &mark, SUMOReal markWidth)
Storage for edges, including some functionality operating on multiple edges.
Definition: NBEdgeCont.h:66
T MIN2(T a, T b)
Definition: StdDefs.h:64
void bezier(int npts, SUMOReal b[], int cpts, SUMOReal p[])
Definition: bezier.cpp:101
#define POSITION_EPS
Definition: config.h:182
std::map< std::string, NBNode * >::const_iterator end() const
Returns the pointer to the end of the stored nodes.
Definition: NBNodeCont.h:119
#define DEG2RAD(x)
Definition: GeomHelper.h:45
const Boundary & getConvBoundary() const
Returns the converted boundary.
static std::string escapeXML(const std::string &orig, const bool maskDoubleHyphen=false)
Replaces the standard escapes by their XML entities.
vehicle is a passenger car (a "normal" car)
std::map< std::string, NBEdge * >::const_iterator begin() const
Returns the pointer to the begin of the stored edges.
Definition: NBEdgeCont.h:190
SVCPermissions getPermissions(int lane=-1) const
get the union of allowed classes over all lanes or for a specific lane
Definition: NBEdge.cpp:2763
static std::string getLaneType(SVCPermissions permissions)
NBNode * getToNode() const
Returns the destination node of the edge.
Definition: NBEdge.h:423
SUMOReal angleAt2D(int pos) const
get angle in certain position of position vector
NBNodeCont & getNodeCont()
Returns a reference to the node container.
Definition: NBNetBuilder.h:149
Instance responsible for building networks.
Definition: NBNetBuilder.h:112
static OutputDevice & getDevice(const std::string &name)
Returns the described OutputDevice.
SUMOReal y() const
Returns the y-position.
Definition: Position.h:68
A storage for options typed value containers)
Definition: OptionsCont.h:99
static PositionVector getLeftLaneBorder(const NBEdge *edge, int laneIndex=-1)
get the left border of the given lane (the leftmost one by default)
static const GeoConvHelper & getFinal()
the coordinate transformation for writing the location element and for tracking the original coordina...
Represents a single node (junction) during network building.
Definition: NBNode.h:75
T get(const std::string &str) const
SUMOReal distanceTo2D(const Position &p2) const
returns the euclidean distance in the x-y-plane
Definition: Position.h:250
void move2side(SUMOReal amount)
move position vector to side using certain ammount
Static storage of an output device and its base (abstract) implementation.
Definition: OutputDevice.h:71
bool closeTag()
Closes the most recently opened tag.
#define SUMOReal
Definition: config.h:208
SUMOReal ymax() const
Returns maximum y-coordinate.
Definition: Boundary.cpp:148
int size() const
Returns the number of nodes stored in this container.
Definition: NBNodeCont.h:229
#define NUMERICAL_EPS
Definition: config.h:158
std::string getLaneID(int lane) const
get Lane ID (Secure)
Definition: NBEdge.cpp:2536
SUMOReal getSpeed() const
Returns the speed allowed on this edge.
Definition: NBEdge.h:496
Container for nodes during the netbuilding process.
Definition: NBNodeCont.h:63
static void checkLaneGeometries(const NBEdge *e)
check if the lane geometries are compatible with OpenDRIVE assumptions (colinear stop line) ...
static bool writeGeomSmooth(const PositionVector &shape, SUMOReal speed, OutputDevice &device, OutputDevice &elevationDevice, SUMOReal straightThresh, SUMOReal &length)
static int getID(const std::string &origID, StringBijection< int > &map, int &lastID)
const std::string & getStreetName() const
Returns the street name of this edge.
Definition: NBEdge.h:525
const std::vector< Connection > & getConnections() const
Returns the connections.
Definition: NBEdge.h:810
std::map< std::string, NBNode * >::const_iterator begin() const
Returns the pointer to the begin of the stored nodes.
Definition: NBNodeCont.h:114
const PositionVector & getLaneShape(int i) const
Returns the shape of the nth lane.
Definition: NBEdge.cpp:683
static void writeElevationProfile(const PositionVector &shape, OutputDevice &device, const OutputDevice_String &elevationDevice)
OutputDevice & openTag(const std::string &xmlElement)
Opens an XML tag.
SUMOReal angleTo2D(const Position &other) const
returns the angle in the plane of the vector pointing from here to the other position ...
Definition: Position.h:260
An output device that encapsulates an ofstream.
bool isSet(const std::string &name, bool failOnNonExistant=true) const
Returns the information whether the named option is set.
NBNode * getFromNode() const
Returns the origin node of the edge.
Definition: NBEdge.h:416