View Javadoc
1   package org.opentrafficsim.road.network.factory.xml;
2   
3   import java.awt.Color;
4   import java.lang.reflect.Constructor;
5   import java.lang.reflect.InvocationTargetException;
6   import java.rmi.RemoteException;
7   import java.util.ArrayList;
8   import java.util.HashMap;
9   import java.util.HashSet;
10  import java.util.LinkedHashMap;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  
15  import javax.naming.NamingException;
16  
17  import org.djunits.unit.AngleUnit;
18  import org.djunits.value.AngleUtil;
19  import org.djunits.value.vdouble.scalar.Direction;
20  import org.djunits.value.vdouble.scalar.Length;
21  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
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.GTUType;
28  import org.opentrafficsim.core.gtu.RelativePosition;
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.core.network.animation.LinkAnimation;
33  import org.opentrafficsim.road.network.animation.LaneAnimation;
34  import org.opentrafficsim.road.network.animation.ShoulderAnimation;
35  import org.opentrafficsim.road.network.animation.StripeAnimation;
36  import org.opentrafficsim.road.network.factory.xml.ArcTag.ArcDirection;
37  import org.opentrafficsim.road.network.lane.CrossSectionElement;
38  import org.opentrafficsim.road.network.lane.CrossSectionLink;
39  import org.opentrafficsim.road.network.lane.Lane;
40  import org.opentrafficsim.road.network.lane.LaneType;
41  import org.opentrafficsim.road.network.lane.NoTrafficLane;
42  import org.opentrafficsim.road.network.lane.Shoulder;
43  import org.opentrafficsim.road.network.lane.Stripe;
44  import org.opentrafficsim.road.network.lane.Stripe.Permeable;
45  import org.opentrafficsim.road.network.lane.changing.OvertakingConditions;
46  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
47  import org.opentrafficsim.road.network.lane.object.trafficlight.SimpleTrafficLight;
48  import org.xml.sax.SAXException;
49  
50  import nl.tudelft.simulation.dsol.SimRuntimeException;
51  import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
52  import nl.tudelft.simulation.language.d2.Angle;
53  import nl.tudelft.simulation.language.d3.CartesianPoint;
54  import nl.tudelft.simulation.language.d3.DirectedPoint;
55  import nl.tudelft.simulation.language.reflection.ClassUtil;
56  
57  /**
58   * <p>
59   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
60   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
61   * <p>
62   * LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
63   * initial version Jul 25, 2015 <br>
64   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
65   */
66  final class Links
67  {
68      /** Utility class. */
69      private Links()
70      {
71          // do not instantiate
72      }
73  
74      /**
75       * Find the nodes one by one that have one coordinate defined, and one not defined, and try to build the network from there.
76       * @param parser the parser with the lists of information
77       * @throws NetworkException when both nodes are null.
78       * @throws NamingException when node animation cannot link to the animation context.
79       */
80      @SuppressWarnings("methodlength")
81      static void calculateNodeCoordinates(final XmlNetworkLaneParser parser) throws NetworkException, NamingException
82      {
83          // are there straight tags with nodes without an angle?
84          for (LinkTag linkTag : parser.linkTags.values())
85          {
86              if (linkTag.straightTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
87              {
88                  if (linkTag.nodeStartTag.angle == null)
89                  {
90                      double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
91                      double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
92                      linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), AngleUnit.RADIAN);
93                  }
94                  if (linkTag.nodeEndTag.angle == null)
95                  {
96                      double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
97                      double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
98                      linkTag.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), AngleUnit.RADIAN);
99                  }
100             }
101         }
102 
103         // are there polyline tags with nodes without an angle?
104         for (LinkTag linkTag : parser.linkTags.values())
105         {
106             if (linkTag.polyLineTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
107             {
108                 if (linkTag.nodeStartTag.angle == null)
109                 {
110                     double dx = linkTag.polyLineTag.coordinates[0].x - linkTag.nodeStartTag.coordinate.x;
111                     double dy = linkTag.polyLineTag.coordinates[0].y - linkTag.nodeStartTag.coordinate.y;
112                     linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), AngleUnit.RADIAN);
113                 }
114                 if (linkTag.nodeEndTag.angle == null)
115                 {
116                     double dx = linkTag.nodeEndTag.coordinate.x
117                             - linkTag.polyLineTag.coordinates[linkTag.polyLineTag.coordinates.length - 1].x;
118                     double dy = linkTag.nodeEndTag.coordinate.y
119                             - linkTag.polyLineTag.coordinates[linkTag.polyLineTag.coordinates.length - 1].y;
120                     linkTag.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), AngleUnit.RADIAN);
121                 }
122             }
123         }
124 
125         // see if we can find the coordinates of the nodes that have not yet been fixed.
126         Set<NodeTag> nodeTags = new HashSet<>();
127         for (LinkTag linkTag : parser.linkTags.values())
128         {
129             if (linkTag.nodeStartTag.coordinate == null)
130             {
131                 nodeTags.add(linkTag.nodeStartTag);
132             }
133             if (linkTag.nodeEndTag.coordinate == null)
134             {
135                 nodeTags.add(linkTag.nodeEndTag);
136             }
137         }
138 
139         while (nodeTags.size() > 0)
140         {
141             boolean found = false;
142             for (LinkTag linkTag : parser.linkTags.values())
143             {
144                 if (linkTag.straightTag != null || linkTag.arcTag != null)
145                 {
146                     if (nodeTags.contains(linkTag.nodeStartTag) == nodeTags.contains(linkTag.nodeEndTag))
147                     {
148                         continue;
149                     }
150 
151                     if (linkTag.straightTag != null)
152                     {
153                         double lengthSI = linkTag.straightTag.length.getSI();
154                         if (linkTag.nodeEndTag.node == null)
155                         {
156                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeStartTag.node.getLocation().getX(),
157                                     linkTag.nodeStartTag.node.getLocation().getY(),
158                                     linkTag.nodeStartTag.node.getLocation().getZ());
159                             double angle = linkTag.nodeStartTag.node.getDirection().getSI();
160                             double slope = linkTag.nodeStartTag.node.getSlope().getSI();
161                             coordinate.x += lengthSI * Math.cos(angle);
162                             coordinate.y += lengthSI * Math.sin(angle);
163                             coordinate.z += lengthSI * Math.sin(slope);
164                             NodeTag nodeTag = linkTag.nodeEndTag;
165                             nodeTag.angle = new Direction(angle, AngleUnit.SI);
166                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
167                             nodeTag.slope = new Direction(slope, AngleUnit.SI);
168                             linkTag.nodeEndTag.node = NodeTag.makeOTSNode(nodeTag, parser);
169                             nodeTags.remove(linkTag.nodeEndTag);
170                         }
171                         else if (linkTag.nodeStartTag.node == null)
172                         {
173                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeEndTag.node.getLocation().getX(),
174                                     linkTag.nodeEndTag.node.getLocation().getY(), linkTag.nodeEndTag.node.getLocation().getZ());
175                             double angle = linkTag.nodeEndTag.node.getDirection().getSI();
176                             double slope = linkTag.nodeEndTag.node.getSlope().getSI();
177                             coordinate.x -= lengthSI * Math.cos(angle);
178                             coordinate.y -= lengthSI * Math.sin(angle);
179                             coordinate.z -= lengthSI * Math.sin(slope);
180                             NodeTag nodeTag = linkTag.nodeStartTag;
181                             nodeTag.angle = new Direction(angle, AngleUnit.SI);
182                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
183                             nodeTag.slope = new Direction(slope, AngleUnit.SI);
184                             linkTag.nodeStartTag.node = NodeTag.makeOTSNode(nodeTag, parser);
185                             nodeTags.remove(linkTag.nodeStartTag);
186                         }
187                     }
188                     else if (linkTag.arcTag != null)
189                     {
190                         double radiusSI = linkTag.arcTag.radius.getSI();
191                         double angle = linkTag.arcTag.angle.getSI();
192                         ArcDirection direction = linkTag.arcTag.direction;
193 
194                         if (linkTag.nodeEndTag.node == null)
195                         {
196                             CartesianPoint coordinate = new CartesianPoint(0.0, 0.0, 0.0);
197                             double startAngle = linkTag.nodeStartTag.node.getDirection().getSI();
198                             double slope = linkTag.nodeStartTag.node.getSlope().getSI();
199                             double lengthSI = radiusSI * angle;
200                             NodeTag nodeTag = linkTag.nodeEndTag;
201                             if (direction.equals(ArcDirection.LEFT))
202                             {
203                                 linkTag.arcTag.center = new OTSPoint3D(
204                                         linkTag.nodeStartTag.node.getLocation().getX()
205                                                 + radiusSI * Math.cos(startAngle + Math.PI / 2.0),
206                                         linkTag.nodeStartTag.node.getLocation().getY()
207                                                 + radiusSI * Math.sin(startAngle + Math.PI / 2.0),
208                                         0.0);
209                                 linkTag.arcTag.startAngle = startAngle - Math.PI / 2.0;
210                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle + angle);
211                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle + angle);
212                                 nodeTag.angle = new Direction(AngleUtil.normalize(startAngle + angle), AngleUnit.SI);
213                             }
214                             else
215                             {
216                                 linkTag.arcTag.center = new OTSPoint3D(
217                                         linkTag.nodeStartTag.node.getLocation().getX()
218                                                 - radiusSI * Math.cos(startAngle + Math.PI / 2.0),
219                                         linkTag.nodeStartTag.node.getLocation().getY()
220                                                 - radiusSI * Math.sin(startAngle + Math.PI / 2.0),
221                                         0.0);
222                                 linkTag.arcTag.startAngle = startAngle + Math.PI / 2.0;
223                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle - angle);
224                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle - angle);
225                                 nodeTag.angle = new Direction(AngleUtil.normalize(startAngle - angle), AngleUnit.SI);
226                             }
227                             coordinate.z = linkTag.nodeStartTag.node.getLocation().getZ() + lengthSI * Math.sin(slope);
228                             nodeTag.slope = new Direction(slope, AngleUnit.SI);
229                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
230                             linkTag.nodeEndTag.node = NodeTag.makeOTSNode(nodeTag, parser);
231                             nodeTags.remove(linkTag.nodeEndTag);
232                         }
233 
234                         else if (linkTag.nodeStartTag.node == null)
235                         {
236                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeEndTag.node.getLocation().getX(),
237                                     linkTag.nodeEndTag.node.getLocation().getY(), linkTag.nodeEndTag.node.getLocation().getZ());
238                             double endAngle = linkTag.nodeEndTag.node.getDirection().getSI();
239                             double slope = linkTag.nodeEndTag.node.getSlope().getSI();
240                             double lengthSI = radiusSI * angle;
241                             NodeTag nodeTag = linkTag.nodeStartTag;
242                             if (direction.equals(ArcDirection.LEFT))
243                             {
244                                 linkTag.arcTag.center =
245                                         new OTSPoint3D(coordinate.x + radiusSI * Math.cos(endAngle + Math.PI / 2.0),
246                                                 coordinate.y + radiusSI * Math.sin(endAngle + Math.PI / 2.0), 0.0);
247                                 linkTag.arcTag.startAngle = endAngle - Math.PI / 2.0 - angle;
248                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
249                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
250                                 nodeTag.angle = new Direction(AngleUtil.normalize(linkTag.arcTag.startAngle + Math.PI / 2.0),
251                                         AngleUnit.SI);
252                             }
253                             else
254                             {
255                                 linkTag.arcTag.center =
256                                         new OTSPoint3D(coordinate.x + radiusSI * Math.cos(endAngle - Math.PI / 2.0),
257                                                 coordinate.y + radiusSI * Math.sin(endAngle - Math.PI / 2.0), 0.0);
258                                 linkTag.arcTag.startAngle = endAngle + Math.PI / 2.0 + angle;
259                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
260                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
261                                 nodeTag.angle = new Direction(AngleUtil.normalize(linkTag.arcTag.startAngle - Math.PI / 2.0),
262                                         AngleUnit.SI);
263                             }
264                             coordinate.z -= lengthSI * Math.sin(slope);
265                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
266                             nodeTag.slope = new Direction(slope, AngleUnit.SI);
267                             linkTag.nodeStartTag.node = NodeTag.makeOTSNode(nodeTag, parser);
268                             nodeTags.remove(linkTag.nodeStartTag);
269                         }
270                     }
271                 }
272             }
273             if (!found)
274             {
275                 throw new NetworkException("Cannot find coordinates of one or more nodes");
276             }
277         }
278 
279         // are there straight tags with nodes without an angle?
280         for (LinkTag linkTag : parser.linkTags.values())
281         {
282             if (linkTag.straightTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
283             {
284                 if (linkTag.nodeStartTag.angle == null)
285                 {
286                     double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
287                     double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
288                     linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), AngleUnit.RADIAN);
289                 }
290                 if (linkTag.nodeEndTag.angle == 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.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), AngleUnit.RADIAN);
295                 }
296             }
297         }
298 
299         // which nodes have not yet been created?
300         for (NodeTag nodeTag : parser.nodeTags.values())
301         {
302             if (nodeTag.coordinate != null && nodeTag.node == null)
303             {
304                 if (nodeTag.angle == null)
305                 {
306                     nodeTag.angle = Direction.ZERO;
307                 }
308                 if (nodeTag.slope == null)
309                 {
310                     nodeTag.slope = Direction.ZERO;
311                 }
312                 nodeTag.node = NodeTag.makeOTSNode(nodeTag, parser);
313             }
314         }
315 
316     }
317 
318     /**
319      * Find the nodes one by one that have one coordinate defined, and one not defined, and try to build the network from there.
320      * @param linkTag the link to process
321      * @param parser the parser with the lists of information
322      * @param simulator to be able to make the animation
323      * @throws OTSGeometryException when both nodes are null.
324      * @throws NamingException when node animation cannot link to the animation context.
325      * @throws NetworkException when tag type not filled
326      */
327     static void buildLink(final LinkTag linkTag, final XmlNetworkLaneParser parser, final OTSDEVSSimulatorInterface simulator)
328             throws OTSGeometryException, NamingException, NetworkException
329     {
330         NodeTag from = linkTag.nodeStartTag;
331         OTSPoint3D startPoint = new OTSPoint3D(from.coordinate);
332         double startAngle = from.angle.si;
333         if (linkTag.offsetStart != null && linkTag.offsetStart.si != 0.0)
334         {
335             // shift the start point perpendicular to the node direction or read from tag
336             double offset = linkTag.offsetStart.si;
337             startPoint = new OTSPoint3D(startPoint.x + offset * Math.cos(startAngle + Math.PI / 2.0),
338                     startPoint.y + offset * Math.sin(startAngle + Math.PI / 2.0), startPoint.z);
339             System.out
340                     .println("fc = " + from.coordinate + ", sa = " + startAngle + ", so = " + offset + ", sp = " + startPoint);
341         }
342 
343         NodeTag to = linkTag.nodeEndTag;
344         OTSPoint3D endPoint = new OTSPoint3D(to.coordinate);
345         double endAngle = to.angle.si;
346         if (linkTag.offsetEnd != null && linkTag.offsetEnd.si != 0.0)
347         {
348             // shift the start point perpendicular to the node direction or read from tag
349             double offset = linkTag.offsetEnd.si;
350             endPoint = new OTSPoint3D(endPoint.x + offset * Math.cos(endAngle + Math.PI / 2.0),
351                     endPoint.y + offset * Math.sin(endAngle + Math.PI / 2.0), endPoint.z);
352             System.out.println("tc = " + to.coordinate + ", ea = " + endAngle + ", eo = " + offset + ", ep = " + endPoint);
353         }
354 
355         OTSPoint3D[] coordinates = null;
356 
357         if (linkTag.straightTag != null)
358         {
359             coordinates = new OTSPoint3D[2];
360             coordinates[0] = startPoint;
361             coordinates[1] = endPoint;
362         }
363 
364         else if (linkTag.polyLineTag != null)
365         {
366             int intermediatePoints = linkTag.polyLineTag.coordinates.length;
367             coordinates = new OTSPoint3D[intermediatePoints + 2];
368             coordinates[0] = startPoint;
369             coordinates[intermediatePoints + 1] = endPoint;
370             for (int p = 0; p < intermediatePoints; p++)
371             {
372                 coordinates[p + 1] = linkTag.polyLineTag.coordinates[p];
373             }
374 
375         }
376         else if (linkTag.arcTag != null)
377         {
378             // calculate the center position
379             double radiusSI = linkTag.arcTag.radius.getSI();
380             double offsetStart = 0.0;
381             if (linkTag.offsetStart != null)
382             {
383                 offsetStart = linkTag.offsetStart.si;
384             }
385             double offsetEnd = 0.0;
386             if (linkTag.offsetEnd != null)
387             {
388                 offsetEnd = linkTag.offsetEnd.si;
389             }
390             List<OTSPoint3D> center = OTSPoint3D.circleIntersections(from.coordinate, radiusSI + offsetStart, to.coordinate,
391                     radiusSI + offsetEnd);
392             OTSPoint3D c = linkTag.arcTag.center =
393                     (linkTag.arcTag.direction.equals(ArcTag.ArcDirection.RIGHT)) ? center.get(0) : center.get(1);
394 
395             // calculate start angle and end angle
396             double sa = linkTag.arcTag.startAngle = Math.atan2(from.coordinate.y - c.y, from.coordinate.x - c.x);
397             double ea = Math.atan2(to.coordinate.y - c.y, to.coordinate.x - c.x);
398             if (linkTag.arcTag.direction.equals(ArcDirection.RIGHT))
399             {
400                 // right -> negative direction, ea should be less than sa
401                 ea = (sa < ea) ? ea + Math.PI * 2.0 : ea;
402             }
403             else
404             {
405                 // left -> positive direction, sa should be less than ea
406                 ea = (ea < sa) ? ea + Math.PI * 2.0 : ea;
407             }
408 
409             int points = (Angle.normalize2Pi(ea - sa) <= Math.PI / 2.0) ? 64 : 128;
410             coordinates = new OTSPoint3D[points];
411             coordinates[0] = new OTSPoint3D(from.coordinate.x + Math.cos(sa) * offsetStart,
412                     from.coordinate.y + Math.sin(sa) * offsetStart, from.coordinate.z);
413             coordinates[coordinates.length - 1] = new OTSPoint3D(to.coordinate.x + Math.cos(ea) * offsetEnd,
414                     to.coordinate.y + Math.sin(ea) * offsetEnd, to.coordinate.z);
415             double angleStep = linkTag.arcTag.angle.getSI() / points;
416             double slopeStep = (to.coordinate.z - from.coordinate.z) / points;
417 
418             if (linkTag.arcTag.direction.equals(ArcDirection.RIGHT))
419             {
420                 for (int p = 1; p < points - 1; p++)
421                 {
422                     double dRad = offsetStart + (offsetEnd - offsetStart) * p / points;
423                     coordinates[p] = new OTSPoint3D(
424                             linkTag.arcTag.center.x + (radiusSI + dRad) * Math.cos(linkTag.arcTag.startAngle - angleStep * p),
425                             linkTag.arcTag.center.y + (radiusSI + dRad) * Math.sin(linkTag.arcTag.startAngle - angleStep * p),
426                             from.coordinate.z + slopeStep * p);
427                 }
428             }
429             else
430             {
431                 for (int p = 1; p < points - 1; p++)
432                 {
433                     double dRad = offsetStart + (offsetEnd - offsetStart) * p / points;
434                     coordinates[p] = new OTSPoint3D(
435                             linkTag.arcTag.center.x + (radiusSI + dRad) * Math.cos(linkTag.arcTag.startAngle + angleStep * p),
436                             linkTag.arcTag.center.y + (radiusSI + dRad) * Math.sin(linkTag.arcTag.startAngle + angleStep * p),
437                             from.coordinate.z + slopeStep * p);
438                 }
439             }
440         }
441 
442         else if (linkTag.bezierTag != null)
443         {
444             coordinates = Bezier
445                     .cubic(128, new DirectedPoint(startPoint.x, startPoint.y, startPoint.z, 0, 0, startAngle),
446                             new DirectedPoint(endPoint.x, endPoint.y, endPoint.z, 0, 0, endAngle), linkTag.bezierTag.shape)
447                     .getPoints();
448         }
449 
450         else
451         {
452             throw new NetworkException(
453                     "Making link, but link " + linkTag.name + " has no filled straight, arc, or bezier curve");
454         }
455 
456         OTSLine3D designLine = OTSLine3D.createAndCleanOTSLine3D(coordinates);
457 
458         // Directionality has to be added later when the lanes and their direction are known.
459         CrossSectionLink link = new CrossSectionLink(parser.network, linkTag.name, linkTag.nodeStartTag.node,
460                 linkTag.nodeEndTag.node, LinkType.ALL, designLine, simulator,
461                 new HashMap<GTUType, LongitudinalDirectionality>(), linkTag.laneKeepingPolicy);
462         
463         if (linkTag.priority != null)
464         {
465             link.setPriority(linkTag.priority);
466         }
467         
468         try
469         {
470             new LinkAnimation(link, simulator, 0.5f);
471         }
472         catch (RemoteException exception)
473         {
474             exception.printStackTrace();
475         }
476         linkTag.link = link;
477     }
478 
479     /**
480      * @param linkTag the link to process
481      * @param parser the parser with the lists of information
482      * @param simulator to be able to make the animation
483      * @throws NetworkException when the stripe cannot be instantiated
484      * @throws NamingException when the /animation/2D tree cannot be found in the context
485      * @throws SAXException when the stripe type cannot be parsed correctly
486      * @throws GTUException when lane block cannot be created
487      * @throws OTSGeometryException when construction of the offset-line or contour fails
488      * @throws SimRuntimeException when construction of the generator fails
489      */
490     @SuppressWarnings({ "checkstyle:needbraces", "checkstyle:methodlength" })
491     static void applyRoadTypeToLink(final LinkTag linkTag, final XmlNetworkLaneParser parser,
492             final OTSDEVSSimulatorInterface simulator)
493             throws NetworkException, NamingException, SAXException, GTUException, OTSGeometryException, SimRuntimeException
494     {
495         CrossSectionLink csl = linkTag.link;
496         List<CrossSectionElement> cseList = new ArrayList<>();
497         List<Lane> lanes = new ArrayList<>();
498         // TODO Map<GTUType, LongitudinalDirectionality> linkDirections = new HashMap<>();
499         LongitudinalDirectionality linkDirection = LongitudinalDirectionality.DIR_NONE;
500         for (CrossSectionElementTag cseTag : linkTag.roadLayoutTag.cseTags.values())
501         {
502             LaneOverrideTag laneOverrideTag = null;
503             if (linkTag.laneOverrideTags.containsKey(cseTag.name))
504                 laneOverrideTag = linkTag.laneOverrideTags.get(cseTag.name);
505 
506             switch (cseTag.elementType)
507             {
508                 case STRIPE:
509                     switch (cseTag.stripeType)
510                     {
511                         case BLOCKED:
512                         case DASHED:
513                             Stripe dashedLine = new Stripe(csl, cseTag.offset, cseTag.width);
514                             dashedLine.addPermeability(GTUType.ALL, Permeable.BOTH);
515                             if (simulator != null && simulator instanceof AnimatorInterface)
516                             {
517                                 try
518                                 {
519                                     new StripeAnimation(dashedLine, simulator, StripeAnimation.TYPE.DASHED);
520                                 }
521                                 catch (RemoteException exception)
522                                 {
523                                     exception.printStackTrace();
524                                 }
525                             }
526                             cseList.add(dashedLine);
527                             break;
528 
529                         case DOUBLE:
530                             Stripe doubleLine = new Stripe(csl, cseTag.offset, cseTag.width);
531                             if (simulator != null && simulator instanceof AnimatorInterface)
532                             {
533                                 try
534                                 {
535                                     new StripeAnimation(doubleLine, simulator, StripeAnimation.TYPE.DOUBLE);
536                                 }
537                                 catch (RemoteException exception)
538                                 {
539                                     exception.printStackTrace();
540                                 }
541                             }
542                             cseList.add(doubleLine);
543                             break;
544 
545                         case LEFTONLY:
546                             Stripe leftOnlyLine = new Stripe(csl, cseTag.offset, cseTag.width);
547                             leftOnlyLine.addPermeability(GTUType.ALL, Permeable.LEFT); // TODO correct?
548                             if (simulator != null && simulator instanceof AnimatorInterface)
549                             {
550                                 try
551                                 {
552                                     new StripeAnimation(leftOnlyLine, simulator, StripeAnimation.TYPE.LEFTONLY);
553                                 }
554                                 catch (RemoteException exception)
555                                 {
556                                     exception.printStackTrace();
557                                 }
558                             }
559                             cseList.add(leftOnlyLine);
560                             break;
561 
562                         case RIGHTONLY:
563                             Stripe rightOnlyLine = new Stripe(csl, cseTag.offset, cseTag.width);
564                             rightOnlyLine.addPermeability(GTUType.ALL, Permeable.RIGHT); // TODO correct?
565                             if (simulator != null && simulator instanceof AnimatorInterface)
566                             {
567                                 try
568                                 {
569                                     new StripeAnimation(rightOnlyLine, simulator, StripeAnimation.TYPE.RIGHTONLY);
570                                 }
571                                 catch (RemoteException exception)
572                                 {
573                                     exception.printStackTrace();
574                                 }
575                             }
576                             cseList.add(rightOnlyLine);
577                             break;
578 
579                         case SOLID:
580                             Stripe solidLine = new Stripe(csl, cseTag.offset, cseTag.width);
581                             if (simulator != null && simulator instanceof AnimatorInterface)
582                             {
583                                 try
584                                 {
585                                     new StripeAnimation(solidLine, simulator, StripeAnimation.TYPE.SOLID);
586                                 }
587                                 catch (RemoteException exception)
588                                 {
589                                     exception.printStackTrace();
590                                 }
591                             }
592                             cseList.add(solidLine);
593                             break;
594 
595                         default:
596                             throw new SAXException("Unknown Stripe type: " + cseTag.stripeType.toString());
597                     }
598                     break;
599 
600                 case LANE:
601                 {
602                     LongitudinalDirectionality direction = cseTag.direction;
603                     Color color = cseTag.color;
604                     OvertakingConditions overtakingConditions = cseTag.overtakingConditions;
605                     if (laneOverrideTag != null)
606                     {
607                         if (laneOverrideTag.overtakingConditions != null)
608                             overtakingConditions = laneOverrideTag.overtakingConditions;
609                         if (laneOverrideTag.color != null)
610                             color = laneOverrideTag.color;
611                         if (laneOverrideTag.direction != null)
612                             direction = laneOverrideTag.direction;
613                     }
614                     Map<GTUType, LongitudinalDirectionality> directionality = new LinkedHashMap<>();
615                     directionality.put(GTUType.ALL, direction);
616                     if (linkDirection.equals(LongitudinalDirectionality.DIR_NONE))
617                     {
618                         linkDirection = direction;
619                     }
620                     else if (linkDirection.isForward())
621                     {
622                         if (direction.isBackwardOrBoth())
623                         {
624                             linkDirection = LongitudinalDirectionality.DIR_BOTH;
625                         }
626                     }
627                     else if (linkDirection.isBackward())
628                     {
629                         if (direction.isForwardOrBoth())
630                         {
631                             linkDirection = LongitudinalDirectionality.DIR_BOTH;
632                         }
633                     }
634 
635                     // XXX: Quick hack to solve the error that the lane directionality has not (yet) been registered at the link
636                     csl.addDirectionality(GTUType.ALL, linkDirection);
637 
638                     // XXX: LaneTypes with compatibilities might have to be defined in a new way -- LaneType.ALL for now...
639                     Lane lane = new Lane(csl, cseTag.name, cseTag.offset, cseTag.offset, cseTag.width, cseTag.width,
640                             LaneType.ALL, directionality, cseTag.legalSpeedLimits, overtakingConditions);
641                     // System.out.println(OTSGeometry.printCoordinates("#link design line: \nc1,0,0\n#",
642                     // lane.getParentLink().getDesignLine(), "\n "));
643                     // System.out.println(OTSGeometry.printCoordinates("#lane center line: \nc0,1,0\n#", lane.getCenterLine(),
644                     // "\n "));
645                     // System.out.println(OTSGeometry.printCoordinates("#lane contour: \nc0,0,1\n#", lane.getContour(),
646                     // "\n "));
647                     cseList.add(lane);
648                     lanes.add(lane);
649                     linkTag.lanes.put(cseTag.name, lane);
650                     if (simulator != null && simulator instanceof AnimatorInterface)
651                     {
652                         try
653                         {
654                             new LaneAnimation(lane, simulator, color, false);
655                         }
656                         catch (RemoteException exception)
657                         {
658                             exception.printStackTrace();
659                         }
660                     }
661 
662                     // SINK
663                     if (linkTag.sinkTags.keySet().contains(cseTag.name))
664                     {
665                         SinkTag sinkTag = linkTag.sinkTags.get(cseTag.name);
666                         Length position = LinkTag.parseBeginEndPosition(sinkTag.positionStr, lane);
667                         new SinkSensor(lane, position, simulator);
668                     }
669 
670                     // TRAFFICLIGHT
671                     if (linkTag.trafficLightTags.containsKey(cseTag.name))
672                     {
673                         for (TrafficLightTag trafficLightTag : linkTag.trafficLightTags.get(cseTag.name))
674                         {
675                             try
676                             {
677                                 Class<?> clazz = Class.forName(trafficLightTag.className);
678                                 Constructor<?> trafficLightConstructor = ClassUtil.resolveConstructor(clazz, new Class[] {
679                                         String.class, Lane.class, Length.class, OTSDEVSSimulatorInterface.class });
680                                 Length position = LinkTag.parseBeginEndPosition(trafficLightTag.positionStr, lane);
681                                 SimpleTrafficLight trafficLight = (SimpleTrafficLight) trafficLightConstructor
682                                         .newInstance(new Object[] { trafficLightTag.name, lane, position, simulator });
683                             }
684                             catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
685                                     | IllegalAccessException | IllegalArgumentException | InvocationTargetException
686                                     | NetworkException exception)
687                             {
688                                 throw new NetworkException("TRAFFICLIGHT: CLASS NAME " + trafficLightTag.className
689                                         + " for traffic light " + trafficLightTag.name + " on lane " + lane.toString()
690                                         + " -- class not found or constructor not right", exception);
691                             }
692                         }
693                     }
694 
695                     // GENERATOR
696                     if (linkTag.generatorTags.containsKey(cseTag.name))
697                     {
698                         GeneratorTag generatorTag = linkTag.generatorTags.get(cseTag.name);
699                         GeneratorTag.makeGenerator(generatorTag, parser, linkTag, simulator);
700                     }
701 
702                     // TODO LISTGENERATOR
703 
704                     // SENSOR
705                     if (linkTag.sensorTags.containsKey(cseTag.name))
706                     {
707                         for (SensorTag sensorTag : linkTag.sensorTags.get(cseTag.name))
708                         {
709                             try
710                             {
711                                 Class<?> clazz = Class.forName(sensorTag.className);
712                                 Constructor<?> sensorConstructor =
713                                         ClassUtil.resolveConstructor(clazz, new Class[] { String.class, Lane.class,
714                                                 Length.class, RelativePosition.TYPE.class, OTSDEVSSimulatorInterface.class });
715                                 Length position = LinkTag.parseBeginEndPosition(sensorTag.positionStr, lane);
716                                 // { String.class, Lane.class, Length.class, RelativePosition.TYPE.class,
717                                 // OTSDEVSSimulatorInterface.class }
718                                 sensorConstructor.newInstance(
719                                         new Object[] { sensorTag.name, lane, position, sensorTag.triggerPosition, simulator });
720                             }
721                             catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
722                                     | IllegalAccessException | IllegalArgumentException | InvocationTargetException
723                                     | NetworkException exception)
724                             {
725                                 throw new NetworkException("SENSOR: CLASS NAME " + sensorTag.className + " for sensor "
726                                         + sensorTag.name + " on lane " + lane.toString()
727                                         + " -- class not found or constructor not right", exception);
728                             }
729                         }
730                     }
731 
732                     // FILL
733                     if (linkTag.fillTags.containsKey(cseTag.name))
734                     {
735                         FillTag fillTag = linkTag.fillTags.get(cseTag.name);
736                         FillTag.makeFill(fillTag, parser, linkTag, simulator);
737                     }
738                     break;
739                 }
740 
741                 case NOTRAFFICLANE:
742                 {
743                     Lane lane = new NoTrafficLane(csl, cseTag.name, cseTag.offset, cseTag.offset, cseTag.width, cseTag.width);
744                     cseList.add(lane);
745                     if (simulator != null && simulator instanceof AnimatorInterface)
746                     {
747                         try
748                         {
749                             Color color = cseTag.color;
750                             if (laneOverrideTag != null)
751                             {
752                                 if (laneOverrideTag.color != null)
753                                     color = laneOverrideTag.color;
754                             }
755                             new LaneAnimation(lane, simulator, color, false);
756                         }
757                         catch (RemoteException exception)
758                         {
759                             exception.printStackTrace();
760                         }
761                     }
762                     break;
763                 }
764 
765                 case SHOULDER:
766                 {
767                     Shoulder shoulder = new Shoulder(csl, cseTag.name, cseTag.offset, cseTag.width);
768                     cseList.add(shoulder);
769                     if (simulator != null && simulator instanceof AnimatorInterface)
770                     {
771                         try
772                         {
773                             Color color = cseTag.color;
774                             if (laneOverrideTag != null)
775                             {
776                                 if (laneOverrideTag.color != null)
777                                     color = laneOverrideTag.color;
778                             }
779                             new ShoulderAnimation(shoulder, simulator, color);
780                         }
781                         catch (RemoteException exception)
782                         {
783                             exception.printStackTrace();
784                         }
785                     }
786                     break;
787                 }
788 
789                 default:
790                     throw new SAXException("Unknown Element type: " + cseTag.elementType.toString());
791             }
792 
793         } // for (CrossSectionElementTag cseTag : roadTypeTag.cseTags.values())
794 
795         // add the calculated direction to the link
796         csl.addDirectionality(GTUType.ALL, linkDirection);
797     }
798 }