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