View Javadoc
1   package org.opentrafficsim.road.network.factory.vissim;
2   
3   import java.awt.Color;
4   import java.util.ArrayList;
5   import java.util.HashSet;
6   import java.util.List;
7   import java.util.Set;
8   
9   import javax.naming.NamingException;
10  
11  import org.djunits.unit.AngleUnit;
12  import org.djunits.unit.DirectionUnit;
13  import org.djunits.unit.LengthUnit;
14  import org.djunits.unit.SpeedUnit;
15  import org.djunits.value.AngleUtil;
16  import org.djunits.value.vdouble.scalar.Angle;
17  import org.djunits.value.vdouble.scalar.Direction;
18  import org.djunits.value.vdouble.scalar.Length;
19  import org.djunits.value.vdouble.scalar.Speed;
20  import org.opentrafficsim.core.compatibility.Compatible;
21  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
22  import org.opentrafficsim.core.geometry.Bezier;
23  import org.opentrafficsim.core.geometry.OTSGeometryException;
24  import org.opentrafficsim.core.geometry.OTSLine3D;
25  import org.opentrafficsim.core.geometry.OTSPoint3D;
26  import org.opentrafficsim.core.gtu.GTUException;
27  import org.opentrafficsim.core.gtu.RelativePosition;
28  import org.opentrafficsim.core.network.LateralDirectionality;
29  import org.opentrafficsim.core.network.LinkType;
30  import org.opentrafficsim.core.network.LongitudinalDirectionality;
31  import org.opentrafficsim.core.network.NetworkException;
32  import org.opentrafficsim.road.network.factory.vissim.ArcTag.ArcDirection;
33  import org.opentrafficsim.road.network.lane.CrossSectionElement;
34  import org.opentrafficsim.road.network.lane.CrossSectionLink;
35  import org.opentrafficsim.road.network.lane.Lane;
36  import org.opentrafficsim.road.network.lane.LaneType;
37  import org.opentrafficsim.road.network.lane.object.sensor.SimpleReportingSensor;
38  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
39  import org.opentrafficsim.road.network.lane.object.trafficlight.SimpleTrafficLight;
40  import org.xml.sax.SAXException;
41  
42  import nl.tudelft.simulation.dsol.SimRuntimeException;
43  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
44  import nl.tudelft.simulation.language.d3.CartesianPoint;
45  import nl.tudelft.simulation.language.d3.DirectedPoint;
46  
47  /**
48   * <p>
49   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
50   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
51   * <p>
52   * LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 4833 $, by $Author: averbraeck $,
53   * initial version Jul 25, 2015 <br>
54   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
55   */
56  final class Links
57  {
58      /** Utility class. */
59      private Links()
60      {
61          // do not instantiate
62      }
63  
64      /**
65       * Find the nodes one by one that have one coordinate defined, and one not defined, and try to build the network from there.
66       * @param parser VissimNetworkLaneParser; the parser with the lists of information
67       * @throws NetworkException when both nodes are null.
68       * @throws NamingException when node animation cannot link to the animation context.
69       */
70      @SuppressWarnings("methodlength")
71      static void calculateNodeCoordinates(final VissimNetworkLaneParser parser) throws NetworkException, NamingException
72      {
73          // are there straight tags with nodes without an angle?
74          for (LinkTag linkTag : parser.getLinkTags().values())
75          {
76              if (linkTag.straightTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
77              {
78                  if (linkTag.nodeStartTag.angle == null)
79                  {
80                      double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
81                      double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
82                      linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
83                  }
84                  if (linkTag.nodeEndTag.angle == null)
85                  {
86                      double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
87                      double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
88                      linkTag.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
89                  }
90              }
91          }
92  
93          // see if we can find the coordinates of the nodes that have not yet been fixed.
94          Set<NodeTag> nodeTags = new HashSet<>();
95          for (LinkTag linkTag : parser.getLinkTags().values())
96          {
97  
98              if (linkTag.nodeStartTag.coordinate == null)
99              {
100                 nodeTags.add(linkTag.nodeStartTag);
101             }
102             if (linkTag.nodeEndTag.coordinate == null)
103             {
104                 nodeTags.add(linkTag.nodeEndTag);
105             }
106         }
107 
108         while (nodeTags.size() > 0)
109         {
110             boolean found = false;
111             for (LinkTag linkTag : parser.getLinkTags().values())
112             {
113                 if (linkTag.straightTag != null || linkTag.polyLineTag != null || linkTag.arcTag != null)
114                 {
115                     if (nodeTags.contains(linkTag.nodeStartTag) == nodeTags.contains(linkTag.nodeEndTag))
116                     {
117                         continue;
118                     }
119 
120                     if (linkTag.straightTag != null)
121                     {
122                         double lengthSI = linkTag.straightTag.length.getSI();
123                         if (linkTag.nodeEndTag.node == null)
124                         {
125                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeStartTag.node.getLocation().getX(),
126                                     linkTag.nodeStartTag.node.getLocation().getY(),
127                                     linkTag.nodeStartTag.node.getLocation().getZ());
128                             double angle = linkTag.nodeStartTag.node.getDirection().getInUnit();
129                             double slope = linkTag.nodeStartTag.node.getSlope().getSI();
130                             coordinate.x += lengthSI * Math.cos(angle);
131                             coordinate.y += lengthSI * Math.sin(angle);
132                             coordinate.z += lengthSI * Math.sin(slope);
133                             NodeTag nodeTag = linkTag.nodeEndTag;
134                             nodeTag.angle = new Direction(angle, DirectionUnit.EAST_RADIAN);
135                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
136                             nodeTag.slope = new Angle(slope, AngleUnit.SI);
137                             linkTag.nodeEndTag.node = NodeTag.makeOTSNode(nodeTag, parser);
138                             nodeTags.remove(linkTag.nodeEndTag);
139                         }
140                         else if (linkTag.nodeStartTag.node == null)
141                         {
142                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeEndTag.node.getLocation().getX(),
143                                     linkTag.nodeEndTag.node.getLocation().getY(), linkTag.nodeEndTag.node.getLocation().getZ());
144                             double angle = linkTag.nodeEndTag.node.getDirection().getInUnit();
145                             double slope = linkTag.nodeEndTag.node.getSlope().getSI();
146                             coordinate.x -= lengthSI * Math.cos(angle);
147                             coordinate.y -= lengthSI * Math.sin(angle);
148                             coordinate.z -= lengthSI * Math.sin(slope);
149                             NodeTag nodeTag = linkTag.nodeStartTag;
150                             nodeTag.angle = new Direction(angle, DirectionUnit.EAST_RADIAN);
151                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
152                             nodeTag.slope = new Angle(slope, AngleUnit.SI);
153                             linkTag.nodeStartTag.node = NodeTag.makeOTSNode(nodeTag, parser);
154                             nodeTags.remove(linkTag.nodeStartTag);
155                         }
156                     }
157                     else if (linkTag.polyLineTag != null)
158                     {
159                         double lengthSI = linkTag.polyLineTag.length.getSI();
160                         // TODO create for polyLine
161                         // if (linkTag.nodeEndTag.node == null) {
162                         // CartesianPoint coordinate = new CartesianPoint(linkTag.nodeStartTag.node.getLocation().getX(),
163                         // linkTag.nodeStartTag.node.getLocation().getY(), linkTag.nodeStartTag.node.getLocation()
164                         // .getZ());
165                         // double angle = linkTag.nodeStartTag.node.getDirection().getInUnit();
166                         // double slope = linkTag.nodeStartTag.node.getSlope().getSI();
167                         // coordinate.x += lengthSI * Math.cos(angle);
168                         // coordinate.y += lengthSI * Math.sin(angle);
169                         // coordinate.z += lengthSI * Math.sin(slope);
170                         // NodeTag nodeTag = linkTag.nodeEndTag;
171                         // nodeTag.angle = new Direction(angle, DirectionUnit.EAST_RADIAN);
172                         // nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
173                         // nodeTag.slope = new Direction(slope, DirectionUnit.EAST_RADIAN);
174                         // linkTag.nodeEndTag.node = NodeTag.makeOTSNode(nodeTag, parser);
175                         // nodeTags.remove(linkTag.nodeEndTag);
176                         // } else if (linkTag.nodeStartTag.node == null) {
177                         // CartesianPoint coordinate = new CartesianPoint(linkTag.nodeEndTag.node.getLocation().getX(),
178                         // linkTag.nodeEndTag.node.getLocation().getY(), linkTag.nodeEndTag.node.getLocation().getZ());
179                         // double angle = linkTag.nodeEndTag.node.getDirection().getInUnit();
180                         // double slope = linkTag.nodeEndTag.node.getSlope().getSI();
181                         // coordinate.x -= lengthSI * Math.cos(angle);
182                         // coordinate.y -= lengthSI * Math.sin(angle);
183                         // coordinate.z -= lengthSI * Math.sin(slope);
184                         // NodeTag nodeTag = linkTag.nodeStartTag;
185                         // nodeTag.angle = new Direction(angle, DirectionUnit.EAST_RADIAN);
186                         // nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
187                         // nodeTag.slope = new Direction(slope, DirectionUnit.EAST_RADIAN);
188                         // linkTag.nodeStartTag.node = NodeTag.makeOTSNode(nodeTag, parser);
189                         // nodeTags.remove(linkTag.nodeStartTag);
190                         // }
191                     }
192                     else if (linkTag.arcTag != null)
193                     {
194                         double radiusSI = linkTag.arcTag.radius.getSI();
195                         double angle = linkTag.arcTag.angle.getInUnit();
196                         ArcDirection direction = linkTag.arcTag.direction;
197 
198                         if (linkTag.nodeEndTag.node == null)
199                         {
200                             CartesianPoint coordinate = new CartesianPoint(0.0, 0.0, 0.0);
201                             double startAngle = linkTag.nodeStartTag.node.getDirection().getInUnit();
202                             double slope = linkTag.nodeStartTag.node.getSlope().getSI();
203                             double lengthSI = radiusSI * angle;
204                             NodeTag nodeTag = linkTag.nodeEndTag;
205                             if (direction.equals(ArcDirection.LEFT))
206                             {
207                                 linkTag.arcTag.center = new OTSPoint3D(
208                                         linkTag.nodeStartTag.node.getLocation().getX()
209                                                 + radiusSI * Math.cos(startAngle + Math.PI / 2.0),
210                                         linkTag.nodeStartTag.node.getLocation().getY()
211                                                 + radiusSI * Math.sin(startAngle + Math.PI / 2.0),
212                                         0.0);
213                                 linkTag.arcTag.startAngle = startAngle - Math.PI / 2.0;
214                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle + angle);
215                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle + angle);
216                                 nodeTag.angle =
217                                         new Direction(AngleUtil.normalize(startAngle + angle), DirectionUnit.EAST_RADIAN);
218                             }
219                             else
220                             {
221                                 linkTag.arcTag.center = new OTSPoint3D(
222                                         linkTag.nodeStartTag.node.getLocation().getX()
223                                                 - radiusSI * Math.cos(startAngle + Math.PI / 2.0),
224                                         linkTag.nodeStartTag.node.getLocation().getY()
225                                                 - radiusSI * Math.sin(startAngle + Math.PI / 2.0),
226                                         0.0);
227                                 linkTag.arcTag.startAngle = startAngle + Math.PI / 2.0;
228                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle - angle);
229                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle - angle);
230                                 nodeTag.angle =
231                                         new Direction(AngleUtil.normalize(startAngle - angle), DirectionUnit.EAST_RADIAN);
232                             }
233                             coordinate.z = linkTag.nodeStartTag.node.getLocation().getZ() + lengthSI * Math.sin(slope);
234                             nodeTag.slope = new Angle(slope, AngleUnit.SI);
235                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
236                             linkTag.nodeEndTag.node = NodeTag.makeOTSNode(nodeTag, parser);
237                             nodeTags.remove(linkTag.nodeEndTag);
238                         }
239 
240                         else if (linkTag.nodeStartTag.node == null)
241                         {
242                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeEndTag.node.getLocation().getX(),
243                                     linkTag.nodeEndTag.node.getLocation().getY(), linkTag.nodeEndTag.node.getLocation().getZ());
244                             double endAngle = linkTag.nodeEndTag.node.getDirection().getInUnit();
245                             double slope = linkTag.nodeEndTag.node.getSlope().getSI();
246                             double lengthSI = radiusSI * angle;
247                             NodeTag nodeTag = linkTag.nodeStartTag;
248                             if (direction.equals(ArcDirection.LEFT))
249                             {
250                                 linkTag.arcTag.center =
251                                         new OTSPoint3D(coordinate.x + radiusSI * Math.cos(endAngle + Math.PI / 2.0),
252                                                 coordinate.y + radiusSI * Math.sin(endAngle + Math.PI / 2.0), 0.0);
253                                 linkTag.arcTag.startAngle = endAngle - Math.PI / 2.0 - angle;
254                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
255                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
256                                 nodeTag.angle = new Direction(AngleUtil.normalize(linkTag.arcTag.startAngle + Math.PI / 2.0),
257                                         DirectionUnit.EAST_RADIAN);
258                             }
259                             else
260                             {
261                                 linkTag.arcTag.center =
262                                         new OTSPoint3D(coordinate.x + radiusSI * Math.cos(endAngle - Math.PI / 2.0),
263                                                 coordinate.y + radiusSI * Math.sin(endAngle - Math.PI / 2.0), 0.0);
264                                 linkTag.arcTag.startAngle = endAngle + Math.PI / 2.0 + angle;
265                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
266                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
267                                 nodeTag.angle = new Direction(AngleUtil.normalize(linkTag.arcTag.startAngle - Math.PI / 2.0),
268                                         DirectionUnit.EAST_RADIAN);
269                             }
270                             coordinate.z -= lengthSI * Math.sin(slope);
271                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
272                             nodeTag.slope = new Angle(slope, AngleUnit.SI);
273                             linkTag.nodeStartTag.node = NodeTag.makeOTSNode(nodeTag, parser);
274                             nodeTags.remove(linkTag.nodeStartTag);
275                         }
276                     }
277                 }
278             }
279             if (!found)
280             {
281                 throw new NetworkException("Cannot find coordinates of one or more nodes");
282             }
283         }
284 
285         // are there straight tags with nodes without an angle?
286         for (LinkTag linkTag : parser.getLinkTags().values())
287         {
288             if (linkTag.straightTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
289             {
290                 double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
291                 double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
292                 linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
293                 dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
294                 dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
295                 linkTag.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
296             }
297         }
298 
299         // are there polyLine tags with nodes without an angle?
300 
301         for (LinkTag linkTag : parser.getLinkTags().values())
302         {
303             if (linkTag.polyLineTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
304             {
305                 double dx = linkTag.polyLineTag.vertices[0].x - linkTag.nodeStartTag.coordinate.x;
306                 double dy = linkTag.polyLineTag.vertices[0].y - linkTag.nodeStartTag.coordinate.y;
307                 linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
308                 int arrayLength = linkTag.polyLineTag.vertices.length;
309                 dx = linkTag.nodeEndTag.coordinate.x - linkTag.polyLineTag.vertices[arrayLength - 1].x;
310                 dy = linkTag.nodeEndTag.coordinate.y - linkTag.polyLineTag.vertices[arrayLength - 1].y;
311                 linkTag.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
312             }
313         }
314 
315         // which nodes have not yet been created?
316         for (NodeTag nodeTag : parser.getNodeTags().values())
317         {
318             if (nodeTag.coordinate != null && nodeTag.node == null)
319             {
320                 if (nodeTag.angle == null)
321                 {
322                     nodeTag.angle = Direction.ZERO;
323                 }
324                 if (nodeTag.slope == null)
325                 {
326                     nodeTag.slope = Angle.ZERO;
327                 }
328                 nodeTag.node = NodeTag.makeOTSNode(nodeTag, parser);
329             }
330         }
331 
332     }
333 
334     /**
335      * Find the nodes one by one that have one coordinate defined, and one not defined, and try to build the network from there.
336      * @param linkTag LinkTag; the link to process
337      * @param parser VissimNetworkLaneParser; the parser with the lists of information
338      * @param simulator OTSSimulatorInterface; to be able to make the animation
339      * @throws OTSGeometryException when both nodes are null.
340      * @throws NamingException when node animation cannot link to the animation context.
341      * @throws NetworkException when tag type not filled
342      */
343     static void buildLink(final LinkTag linkTag, final VissimNetworkLaneParser parser, final OTSSimulatorInterface simulator)
344             throws OTSGeometryException, NamingException, NetworkException
345     {
346         NodeTag from = linkTag.nodeStartTag;
347         OTSPoint3D startPoint = new OTSPoint3D(from.coordinate);
348         double startAngle = 0.0;
349         if (from.angle != null)
350         {
351             startAngle = from.angle.getInUnit();
352         }
353         if (linkTag.offsetStart != null && linkTag.offsetStart.si != 0.0)
354         {
355             // shift the start point perpendicular to the node direction or read from tag
356             double offset = linkTag.offsetStart.si;
357             startPoint = new OTSPoint3D(startPoint.x + offset * Math.cos(startAngle + Math.PI / 2.0),
358                     startPoint.y + offset * Math.sin(startAngle + Math.PI / 2.0), startPoint.z);
359             System.out
360                     .println("fc = " + from.coordinate + ", sa = " + startAngle + ", so = " + offset + ", sp = " + startPoint);
361         }
362 
363         NodeTag to = linkTag.nodeEndTag;
364         OTSPoint3D endPoint = new OTSPoint3D(to.coordinate);
365         double endAngle = to.angle.getInUnit();
366         if (linkTag.offsetEnd != null && linkTag.offsetEnd.si != 0.0)
367         {
368             // shift the start point perpendicular to the node direction or read from tag
369             double offset = linkTag.offsetEnd.si;
370             endPoint = new OTSPoint3D(endPoint.x + offset * Math.cos(endAngle + Math.PI / 2.0),
371                     endPoint.y + offset * Math.sin(endAngle + Math.PI / 2.0), endPoint.z);
372             System.out.println("tc = " + to.coordinate + ", ea = " + endAngle + ", eo = " + offset + ", ep = " + endPoint);
373         }
374 
375         OTSPoint3D[] coordinates = null;
376 
377         if (linkTag.straightTag != null)
378         {
379             coordinates = new OTSPoint3D[2];
380             coordinates[0] = startPoint;
381             coordinates[1] = endPoint;
382         }
383 
384         else if (linkTag.polyLineTag != null)
385         {
386             int intermediatePoints = linkTag.polyLineTag.vertices.length;
387             coordinates = new OTSPoint3D[intermediatePoints + 2];
388             coordinates[0] = startPoint;
389             coordinates[intermediatePoints + 1] = endPoint;
390             for (int p = 0; p < intermediatePoints; p++)
391             {
392                 coordinates[p + 1] = linkTag.polyLineTag.vertices[p];
393             }
394 
395         }
396         else if (linkTag.arcTag != null)
397         {
398             // TODO move the radius if there is an start and end offset? How?
399             int points = (Math.abs(linkTag.arcTag.angle.getInUnit()) <= Math.PI / 2.0) ? 64 : 128;
400             coordinates = new OTSPoint3D[points];
401             coordinates[0] = new OTSPoint3D(from.coordinate.x, from.coordinate.y, from.coordinate.z);
402             coordinates[coordinates.length - 1] = new OTSPoint3D(to.coordinate.x, to.coordinate.y, to.coordinate.z);
403             double angleStep = linkTag.arcTag.angle.getInUnit() / points;
404             double slopeStep = (to.coordinate.z - from.coordinate.z) / points;
405             double radiusSI = linkTag.arcTag.radius.getSI();
406             if (linkTag.arcTag.direction.equals(ArcDirection.RIGHT))
407             {
408                 for (int p = 1; p < points - 1; p++)
409                 {
410                     coordinates[p] = new OTSPoint3D(
411                             linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle - angleStep * p),
412                             linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle - angleStep * p),
413                             from.coordinate.z + slopeStep * p);
414                 }
415             }
416             else
417             {
418                 for (int p = 1; p < points - 1; p++)
419                 {
420                     try
421                     {
422                         System.err.println("linkTag.arcTag.center = " + linkTag.arcTag.center);
423                         System.err.println("linkTag.arcTag.startAngle = " + linkTag.arcTag.startAngle);
424                         coordinates[p] = new OTSPoint3D(
425                                 linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle + angleStep * p),
426                                 linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle + angleStep * p),
427                                 from.coordinate.z + slopeStep * p);
428                     }
429                     catch (NullPointerException npe)
430                     {
431                         npe.printStackTrace();
432                         System.err.println(npe.getMessage());
433                     }
434                 }
435             }
436         }
437 
438         else if (linkTag.bezierTag != null)
439         {
440             if (new DirectedPoint(startPoint.x, startPoint.y, startPoint.z, 0, 0, startAngle)
441                     .equals(new DirectedPoint(endPoint.x, endPoint.y, endPoint.z, 0, 0, endAngle)))
442             {
443                 System.out.println("   ");
444             }
445 
446             coordinates = Bezier.cubic(128, new DirectedPoint(startPoint.x, startPoint.y, startPoint.z, 0, 0, startAngle),
447                     new DirectedPoint(endPoint.x, endPoint.y, endPoint.z, 0, 0, endAngle)).getPoints();
448         }
449 
450         else
451         {
452             throw new NetworkException(
453                     "Making link, but link " + linkTag.name + " has no filled straight, arc, or bezier curve");
454         }
455         if (coordinates.length < 2)
456         {
457             throw new OTSGeometryException(
458                     "Degenerate OTSLine3D; has " + coordinates.length + " point" + (coordinates.length != 1 ? "s" : ""));
459         }
460         OTSLine3D designLine = OTSLine3D.createAndCleanOTSLine3D(coordinates);
461 
462         // Directionality has to be added later when the lanes and their direction are known.
463         CrossSectionLink link = new CrossSectionLink(parser.getNetwork(), linkTag.name, linkTag.nodeStartTag.node,
464                 linkTag.nodeEndTag.node, LinkType.ROAD, designLine, simulator, linkTag.laneKeepingPolicy);
465         linkTag.link = link;
466     }
467 
468     /**
469      * @param linkTag LinkTag; the link to process
470      * @param parser VissimNetworkLaneParser; the parser with the lists of information
471      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; to be able to make the animation
472      * @throws NetworkException when the stripe cannot be instantiated
473      * @throws NamingException when the /animation/2D tree cannot be found in the context
474      * @throws SAXException when the stripe type cannot be parsed correctly
475      * @throws GTUException when lane block cannot be created
476      * @throws OTSGeometryException when construction of the offset-line or contour fails
477      * @throws SimRuntimeException when construction of the generator fails
478      */
479     @SuppressWarnings({ "checkstyle:needbraces", "checkstyle:methodlength" })
480     static void applyRoadTypeToLink(final LinkTag linkTag, final VissimNetworkLaneParser parser,
481             final DEVSSimulatorInterface.TimeDoubleUnit simulator)
482             throws NetworkException, NamingException, SAXException, GTUException, OTSGeometryException, SimRuntimeException
483     {
484         CrossSectionLink csl = linkTag.link;
485 
486         List<CrossSectionElement> cseList = new ArrayList<>();
487         List<Lane> lanes = new ArrayList<>();
488         // TODO Map<GTUType, LongitudinalDirectionality> linkDirections = new HashMap<>();
489         LongitudinalDirectionality linkDirection = LongitudinalDirectionality.DIR_NONE;
490 
491         // add information from the lanes
492         // first the total width is computed
493         // The offset of the lanes will be computed to match this Vissim layout
494         Double roadWidth = 0.0;
495         for (LaneTag laneTag : linkTag.laneTags.values())
496         {
497             roadWidth += Double.parseDouble(laneTag.width);
498         }
499 
500         Color color;
501         if (linkTag.connector)
502         {
503             color = Color.LIGHT_GRAY;
504         }
505         else
506         {
507             color = Color.DARK_GRAY;
508 
509         }
510 
511         // The lanes are ordered from the outside to the inner side of the road
512         Double totalLaneWidth = 0.0;
513 
514         if (!linkTag.connector)
515         {
516             for (LaneTag laneTag : linkTag.laneTags.values())
517             {
518                 String name = laneTag.laneNo;
519                 Double laneWidth = Double.parseDouble(laneTag.width);
520                 Length thisLaneWidth = new Length(laneWidth, LengthUnit.METER);
521 
522                 // the road offset is negative if the lanes are at the right side of the median (for right hand rule)
523                 // Vissim puts the lanes around the centerline of the road (all lanes joined)
524                 // therefore we use a factor 0.5 * roadWidth....
525                 Double negativeOffset = -(0.5 * roadWidth - totalLaneWidth - laneWidth / 2);
526                 Length lateralOffset = new Length(negativeOffset, LengthUnit.METER);
527 
528                 LaneType laneType = LaneType.FREEWAY;
529                 Speed speedLimit = new Speed(Double.parseDouble(linkTag.legalSpeed), SpeedUnit.KM_PER_HOUR);
530                 // OvertakingConditions overtakingConditions; TODO (not clear yet)
531                 Lane lane = new Lane(csl, name, lateralOffset, thisLaneWidth, laneType, speedLimit, null);
532                 if (!linkTag.sensors.isEmpty())
533                 {
534                     for (SensorTag sensorTag : linkTag.sensors)
535                     {
536                         if (sensorTag.laneName.equals(laneTag.laneNo))
537                         {
538                             Length pos = new Length(Double.parseDouble(sensorTag.positionStr), LengthUnit.METER);
539                             if (pos.lt(lane.getLength()))
540                             {
541                                 try
542                                 {
543                                     new SimpleReportingSensor(sensorTag.laneName + ".S" + sensorTag.name, lane, pos,
544                                             RelativePosition.FRONT, simulator, Compatible.EVERYTHING);
545                                 }
546                                 catch (Exception exception)
547                                 {
548                                     System.err.println(exception.getMessage());
549                                 }
550                             }
551                         }
552                     }
553                 }
554 
555                 if (!linkTag.signalHeads.isEmpty())
556                 {
557                     for (SignalHeadTag signalHeadTag : linkTag.signalHeads)
558                     {
559                         if (signalHeadTag.laneName.equals(laneTag.laneNo))
560                         {
561                             Length pos = new Length(Double.parseDouble(signalHeadTag.positionStr), LengthUnit.METER);
562                             if (pos.lt(lane.getLength()))
563                             {
564                                 new SimpleTrafficLight(signalHeadTag.no, lane, pos, simulator);
565                             }
566                         }
567                     }
568                 }
569 
570                 cseList.add(lane);
571 
572                 lanes.add(lane);
573                 linkTag.lanes.put(name, lane);
574                 // update totalLaneWidth
575                 totalLaneWidth += Double.parseDouble(laneTag.width);
576 
577                 // TODO: parser.network.addDrawingInfoBase(lane, new DrawingInfoShape<Lane>(color));
578             }
579         }
580 
581     }
582 
583     /**
584      * @param linkTag LinkTag; the link to process
585      * @param parser VissimNetworkLaneParser; the parser with the lists of information
586      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; to be able to make the animation
587      * @throws NetworkException when the stripe cannot be instantiated
588      * @throws NamingException when the /animation/2D tree cannot be found in the context
589      * @throws SAXException when the stripe type cannot be parsed correctly
590      * @throws GTUException when lane block cannot be created
591      * @throws OTSGeometryException when construction of the offset-line or contour fails
592      * @throws SimRuntimeException when construction of the generator fails
593      */
594     @SuppressWarnings({ "checkstyle:needbraces", "checkstyle:methodlength" })
595     static void applyRoadTypeToConnector(final LinkTag linkTag, final VissimNetworkLaneParser parser,
596             final DEVSSimulatorInterface.TimeDoubleUnit simulator)
597             throws NetworkException, NamingException, SAXException, GTUException, OTSGeometryException, SimRuntimeException
598     {
599         CrossSectionLink csl = linkTag.link;
600 
601         List<CrossSectionElement> cseList = new ArrayList<>();
602         List<Lane> lanes = new ArrayList<>();
603 
604         // add information from the lanes
605         // first the total width is computed
606         // The offset of the lanes will be computed to match this Vissim layout
607         Double roadWidth = 0.0;
608         for (LaneTag laneTag : linkTag.laneTags.values())
609         {
610             roadWidth += Double.parseDouble(laneTag.width);
611         }
612 
613         Color color;
614         if (linkTag.connector)
615         {
616             color = Color.LIGHT_GRAY;
617         }
618         else
619         {
620             color = Color.DARK_GRAY;
621 
622         }
623 
624         // some generic definitions necessary to create a Lane object
625         LaneType laneType = LaneType.FREEWAY;
626         Speed speedLimit = new Speed(Double.parseDouble(linkTag.legalSpeed), SpeedUnit.KM_PER_HOUR);
627 
628         // The lanes are ordered from the outside to the inner side of the road
629         // only lanes from connectors are being set
630         if (linkTag.connector)
631         {
632             Double totalFromLaneWidth = -1.75;
633             Double totalToLaneWidth = -1.75;
634             // find the link and lane downstream
635             LinkTag linkToTag = parser.getRealLinkTags().get(linkTag.connectorTag.toLinkNo);
636             Lane laneTo = linkToTag.lanes.get(linkTag.connectorTag.toLaneNo);
637 
638             // find the link and lane upstream
639             LinkTag linkFromTag = parser.getRealLinkTags().get(linkTag.connectorTag.fromLinkNo);
640             Lane laneFrom = linkFromTag.lanes.get(linkTag.connectorTag.fromLaneNo);
641 
642             // loop along all lanes (Tags)
643             for (LaneTag connectLaneTag : linkTag.laneTags.values())
644             {
645                 // the name (number) of the downstream lane (the order is from outer to center)
646                 String name = connectLaneTag.laneNo;
647                 // the width of the current lane
648                 Length thisLaneWidth = new Length(Double.parseDouble(connectLaneTag.width), LengthUnit.METER);
649                 // the total lanewidth of all previous lanes that already have been looped through
650                 Length totfromLaneWidth = new Length(totalFromLaneWidth, LengthUnit.METER);
651                 Length totToLaneWidth = new Length(totalToLaneWidth, LengthUnit.METER);
652 
653                 Length lateralOffsetStart;
654                 Length lateralOffsetEnd =
655                         laneTo.getLateralBoundaryPosition(LateralDirectionality.RIGHT, 0).plus(totToLaneWidth);
656                 // the lateral offset
657                 if (laneFrom != null)
658                 {
659                     lateralOffsetStart =
660                             laneFrom.getLateralBoundaryPosition(LateralDirectionality.RIGHT, 0).plus(totfromLaneWidth);
661                 }
662                 else
663                 {
664                     lateralOffsetStart = lateralOffsetEnd;
665                 }
666 
667                 // the road offset is negative if the lanes are at the right side of the median (for right hand rule)
668                 // OvertakingConditions overtakingConditions; TODO (not clear yet)
669                 Lane lane = new Lane(csl, name, lateralOffsetStart, lateralOffsetEnd, thisLaneWidth, thisLaneWidth, laneType,
670                         speedLimit, null);
671                 cseList.add(lane);
672                 lanes.add(lane);
673                 linkTag.lanes.put(name, lane);
674 
675                 // update totalLaneWidth
676                 totalFromLaneWidth += Double.parseDouble(connectLaneTag.width);
677                 totalToLaneWidth += Double.parseDouble(connectLaneTag.width);
678 
679                 // TODO: parser.network.addDrawingInfoBase(lane, new DrawingInfoShape<Lane>(color));
680 
681             }
682         }
683 
684     }
685 
686     /**
687      * @param realLinkTag LinkTag;
688      * @param vissimNetworkLaneParser VissimNetworkLaneParser;
689      * @param simulator DEVSSimulatorInterface.TimeDoubleUnit;
690      * @throws NetworkException
691      */
692     public static void createSinkSensor(LinkTag realLinkTag, VissimNetworkLaneParser vissimNetworkLaneParser,
693             DEVSSimulatorInterface.TimeDoubleUnit simulator) throws NetworkException
694     {
695         if (!realLinkTag.connector)
696         {
697             boolean deadLink = true;
698             for (LinkTag linkTag : vissimNetworkLaneParser.getLinkTags().values())
699             {
700                 if (realLinkTag.nodeEndTag.name.equals(linkTag.nodeStartTag.name) && !realLinkTag.equals(linkTag))
701                 {
702                     deadLink = false;
703                 }
704             }
705 
706             if (deadLink)
707             {
708                 for (Lane lane : realLinkTag.lanes.values())
709                 {
710                     Double smallest = Math.min(10, lane.getLength().getInUnit(LengthUnit.METER) - 1);
711                     Length beforeEnd = new Length(smallest, LengthUnit.METER);
712                     Length pos = lane.getLength().minus(beforeEnd);
713                     SinkSensor sensor = new SinkSensor(lane, pos, simulator);
714                     lane.getSensors().add(sensor);
715                 }
716             }
717         }
718 
719     }
720 
721 }