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