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