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