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.djutils.reflection.ClassUtil;
21  import org.opentrafficsim.core.animation.DrawingInfoLine;
22  import org.opentrafficsim.core.animation.DrawingInfoStripe;
23  import org.opentrafficsim.core.animation.StripeType;
24  import org.opentrafficsim.core.compatibility.Compatible;
25  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
26  import org.opentrafficsim.core.geometry.Bezier;
27  import org.opentrafficsim.core.geometry.OTSGeometryException;
28  import org.opentrafficsim.core.geometry.OTSLine3D;
29  import org.opentrafficsim.core.geometry.OTSPoint3D;
30  import org.opentrafficsim.core.gtu.GTUException;
31  import org.opentrafficsim.core.gtu.GTUType;
32  import org.opentrafficsim.core.gtu.RelativePosition;
33  import org.opentrafficsim.core.network.LinkType;
34  import org.opentrafficsim.core.network.LongitudinalDirectionality;
35  import org.opentrafficsim.core.network.NetworkException;
36  import org.opentrafficsim.draw.road.LaneAnimation;
37  import org.opentrafficsim.draw.road.ShoulderAnimation;
38  import org.opentrafficsim.road.network.factory.xml.ArcTag.ArcDirection;
39  import org.opentrafficsim.road.network.lane.CrossSectionElement;
40  import org.opentrafficsim.road.network.lane.CrossSectionLink;
41  import org.opentrafficsim.road.network.lane.Lane;
42  import org.opentrafficsim.road.network.lane.LaneType;
43  import org.opentrafficsim.road.network.lane.NoTrafficLane;
44  import org.opentrafficsim.road.network.lane.Shoulder;
45  import org.opentrafficsim.road.network.lane.Stripe;
46  import org.opentrafficsim.road.network.lane.Stripe.Permeable;
47  import org.opentrafficsim.road.network.lane.changing.OvertakingConditions;
48  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
49  import org.xml.sax.SAXException;
50  
51  import nl.tudelft.simulation.dsol.SimRuntimeException;
52  import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
53  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
54  import nl.tudelft.simulation.language.d3.CartesianPoint;
55  import nl.tudelft.simulation.language.d3.DirectedPoint;
56  
57  /**
58   * <p>
59   * Copyright (c) 2013-2019 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 XmlNetworkLaneParser; 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), DirectionUnit.EAST_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), DirectionUnit.EAST_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), DirectionUnit.EAST_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), DirectionUnit.EAST_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().getInUnit();
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, DirectionUnit.EAST_RADIAN);
166                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
167                             nodeTag.slope = new Angle(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().getInUnit();
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, DirectionUnit.EAST_RADIAN);
182                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
183                             nodeTag.slope = new Angle(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.getInUnit();
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().getInUnit();
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 =
213                                         new Direction(AngleUtil.normalize(startAngle + angle), DirectionUnit.EAST_RADIAN);
214                             }
215                             else
216                             {
217                                 linkTag.arcTag.center = new OTSPoint3D(
218                                         linkTag.nodeStartTag.node.getLocation().getX()
219                                                 - radiusSI * Math.cos(startAngle + Math.PI / 2.0),
220                                         linkTag.nodeStartTag.node.getLocation().getY()
221                                                 - radiusSI * Math.sin(startAngle + Math.PI / 2.0),
222                                         0.0);
223                                 linkTag.arcTag.startAngle = startAngle + Math.PI / 2.0;
224                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle - angle);
225                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle - angle);
226                                 nodeTag.angle =
227                                         new Direction(AngleUtil.normalize(startAngle - angle), DirectionUnit.EAST_RADIAN);
228                             }
229                             coordinate.z = linkTag.nodeStartTag.node.getLocation().getZ() + lengthSI * Math.sin(slope);
230                             nodeTag.slope = new Angle(slope, AngleUnit.SI);
231                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
232                             linkTag.nodeEndTag.node = NodeTag.makeOTSNode(nodeTag, parser);
233                             nodeTags.remove(linkTag.nodeEndTag);
234                         }
235 
236                         else if (linkTag.nodeStartTag.node == null)
237                         {
238                             CartesianPoint coordinate = new CartesianPoint(linkTag.nodeEndTag.node.getLocation().getX(),
239                                     linkTag.nodeEndTag.node.getLocation().getY(), linkTag.nodeEndTag.node.getLocation().getZ());
240                             double endAngle = linkTag.nodeEndTag.node.getDirection().getInUnit();
241                             double slope = linkTag.nodeEndTag.node.getSlope().getSI();
242                             double lengthSI = radiusSI * angle;
243                             NodeTag nodeTag = linkTag.nodeStartTag;
244                             if (direction.equals(ArcDirection.LEFT))
245                             {
246                                 linkTag.arcTag.center =
247                                         new OTSPoint3D(coordinate.x + radiusSI * Math.cos(endAngle + Math.PI / 2.0),
248                                                 coordinate.y + radiusSI * Math.sin(endAngle + Math.PI / 2.0), 0.0);
249                                 linkTag.arcTag.startAngle = endAngle - Math.PI / 2.0 - angle;
250                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
251                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
252                                 nodeTag.angle = new Direction(AngleUtil.normalize(linkTag.arcTag.startAngle + Math.PI / 2.0),
253                                         DirectionUnit.EAST_RADIAN);
254                             }
255                             else
256                             {
257                                 linkTag.arcTag.center =
258                                         new OTSPoint3D(coordinate.x + radiusSI * Math.cos(endAngle - Math.PI / 2.0),
259                                                 coordinate.y + radiusSI * Math.sin(endAngle - Math.PI / 2.0), 0.0);
260                                 linkTag.arcTag.startAngle = endAngle + Math.PI / 2.0 + angle;
261                                 coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
262                                 coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
263                                 nodeTag.angle = new Direction(AngleUtil.normalize(linkTag.arcTag.startAngle - Math.PI / 2.0),
264                                         DirectionUnit.EAST_RADIAN);
265                             }
266                             coordinate.z -= lengthSI * Math.sin(slope);
267                             nodeTag.coordinate = new OTSPoint3D(coordinate.x, coordinate.y, coordinate.z);
268                             nodeTag.slope = new Angle(slope, AngleUnit.SI);
269                             linkTag.nodeStartTag.node = NodeTag.makeOTSNode(nodeTag, parser);
270                             nodeTags.remove(linkTag.nodeStartTag);
271                         }
272                     }
273                 }
274             }
275             if (!found)
276             {
277                 throw new NetworkException("Cannot find coordinates of one or more nodes");
278             }
279         }
280 
281         // are there straight tags with nodes without an angle?
282         for (LinkTag linkTag : parser.linkTags.values())
283         {
284             if (linkTag.straightTag != null && linkTag.nodeStartTag.coordinate != null && linkTag.nodeEndTag.coordinate != null)
285             {
286                 if (linkTag.nodeStartTag.angle == null)
287                 {
288                     double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
289                     double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
290                     linkTag.nodeStartTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
291                 }
292                 if (linkTag.nodeEndTag.angle == null)
293                 {
294                     double dx = linkTag.nodeEndTag.coordinate.x - linkTag.nodeStartTag.coordinate.x;
295                     double dy = linkTag.nodeEndTag.coordinate.y - linkTag.nodeStartTag.coordinate.y;
296                     linkTag.nodeEndTag.angle = new Direction(Math.atan2(dy, dx), DirectionUnit.EAST_RADIAN);
297                 }
298             }
299         }
300 
301         // which nodes have not yet been created?
302         for (NodeTag nodeTag : parser.nodeTags.values())
303         {
304             if (nodeTag.coordinate != null && nodeTag.node == null)
305             {
306                 if (nodeTag.angle == null)
307                 {
308                     nodeTag.angle = Direction.ZERO;
309                 }
310                 if (nodeTag.slope == null)
311                 {
312                     nodeTag.slope = Angle.ZERO;
313                 }
314                 nodeTag.node = NodeTag.makeOTSNode(nodeTag, parser);
315             }
316         }
317 
318     }
319 
320     /**
321      * Build connectors.
322      * @param connectorTag ConnectorTag; the connector to process
323      * @param parser XmlNetworkLaneParser; the parser with the lists of information
324      * @param simulator OTSSimulatorInterface; to be able to make the animation
325      * @throws OTSGeometryException when both nodes are null
326      * @throws NamingException when node animation cannot link to the animation context
327      * @throws NetworkException when tag type not filled
328      */
329     static void buildConnector(final ConnectorTag connectorTag, final XmlNetworkLaneParser parser,
330             final OTSSimulatorInterface simulator) throws OTSGeometryException, NamingException, NetworkException
331     {
332         OTSLine3D designLine =
333                 new OTSLine3D(connectorTag.nodeStartTag.node.getPoint(), connectorTag.nodeEndTag.node.getPoint());
334         CrossSectionLink connector = new CrossSectionLink(parser.network, connectorTag.name, connectorTag.nodeStartTag.node,
335                 connectorTag.nodeEndTag.node, LinkType.CONNECTOR, designLine, simulator, null);
336         if (connectorTag.demandWeight != null)
337         {
338             connector.setDemandWeight(connectorTag.demandWeight);
339         }
340         parser.networkAnimation.addDrawingInfoBase(connector, new DrawingInfoLine<CrossSectionLink>(Color.BLACK, 0.5f));
341         connectorTag.connector = connector;
342     }
343 
344     /**
345      * Find the nodes one by one that have one coordinate defined, and one not defined, and try to build the network from there.
346      * @param linkTag LinkTag; the link to process
347      * @param parser XmlNetworkLaneParser; the parser with the lists of information
348      * @param simulator OTSSimulatorInterface; to be able to make the animation
349      * @throws OTSGeometryException when both nodes are null.
350      * @throws NamingException when node animation cannot link to the animation context.
351      * @throws NetworkException when tag type not filled
352      */
353     static void buildLink(final LinkTag linkTag, final XmlNetworkLaneParser parser, final OTSSimulatorInterface simulator)
354             throws OTSGeometryException, NamingException, NetworkException
355     {
356         NodeTag from = linkTag.nodeStartTag;
357         OTSPoint3D startPoint = new OTSPoint3D(from.coordinate);
358         double startAngle = from.angle.getInUnit();
359         if (linkTag.offsetStart != null && linkTag.offsetStart.si != 0.0)
360         {
361             // shift the start point perpendicular to the node direction or read from tag
362             double offset = linkTag.offsetStart.si;
363             startPoint = new OTSPoint3D(startPoint.x + offset * Math.cos(startAngle + Math.PI / 2.0),
364                     startPoint.y + offset * Math.sin(startAngle + Math.PI / 2.0), startPoint.z);
365             System.out
366                     .println("fc = " + from.coordinate + ", sa = " + startAngle + ", so = " + offset + ", sp = " + startPoint);
367         }
368 
369         NodeTag to = linkTag.nodeEndTag;
370         OTSPoint3D endPoint = new OTSPoint3D(to.coordinate);
371         double endAngle = to.angle.getInUnit();
372         if (linkTag.offsetEnd != null && linkTag.offsetEnd.si != 0.0)
373         {
374             // shift the end point perpendicular to the node direction or read from tag
375             double offset = linkTag.offsetEnd.si;
376             endPoint = new OTSPoint3D(endPoint.x + offset * Math.cos(endAngle + Math.PI / 2.0),
377                     endPoint.y + offset * Math.sin(endAngle + Math.PI / 2.0), endPoint.z);
378             System.out.println("tc = " + to.coordinate + ", ea = " + endAngle + ", eo = " + offset + ", ep = " + endPoint);
379         }
380 
381         OTSPoint3D[] coordinates = null;
382 
383         if (linkTag.straightTag != null)
384         {
385             coordinates = new OTSPoint3D[2];
386             coordinates[0] = startPoint;
387             coordinates[1] = endPoint;
388         }
389 
390         else if (linkTag.polyLineTag != null)
391         {
392             int intermediatePoints = linkTag.polyLineTag.coordinates.length;
393             coordinates = new OTSPoint3D[intermediatePoints + 2];
394             coordinates[0] = startPoint;
395             coordinates[intermediatePoints + 1] = endPoint;
396             for (int p = 0; p < intermediatePoints; p++)
397             {
398                 coordinates[p + 1] = linkTag.polyLineTag.coordinates[p];
399             }
400 
401         }
402         else if (linkTag.arcTag != null)
403         {
404             // calculate the center position
405             double radiusSI = linkTag.arcTag.radius.getSI();
406             double offsetStart = 0.0;
407             if (linkTag.offsetStart != null)
408             {
409                 offsetStart = linkTag.offsetStart.si;
410             }
411             double offsetEnd = 0.0;
412             if (linkTag.offsetEnd != null)
413             {
414                 offsetEnd = linkTag.offsetEnd.si;
415             }
416             List<OTSPoint3D> center = OTSPoint3D.circleIntersections(from.coordinate, radiusSI + offsetStart, to.coordinate,
417                     radiusSI + offsetEnd);
418             OTSPoint3D c = linkTag.arcTag.center =
419                     (linkTag.arcTag.direction.equals(ArcTag.ArcDirection.RIGHT)) ? center.get(0) : center.get(1);
420 
421             // calculate start angle and end angle
422             double sa = linkTag.arcTag.startAngle = Math.atan2(from.coordinate.y - c.y, from.coordinate.x - c.x);
423             double ea = Math.atan2(to.coordinate.y - c.y, to.coordinate.x - c.x);
424             if (linkTag.arcTag.direction.equals(ArcDirection.RIGHT))
425             {
426                 // right -> negative direction, ea should be less than sa
427                 ea = (sa < ea) ? ea + Math.PI * 2.0 : ea;
428             }
429             else
430             {
431                 // left -> positive direction, sa should be less than ea
432                 ea = (ea < sa) ? ea + Math.PI * 2.0 : ea;
433             }
434 
435             int points = (AngleUtil.normalize(ea - sa) <= Math.PI / 2.0) ? 64 : 128;
436             coordinates = new OTSPoint3D[points];
437             coordinates[0] = new OTSPoint3D(from.coordinate.x + Math.cos(sa) * offsetStart,
438                     from.coordinate.y + Math.sin(sa) * offsetStart, from.coordinate.z);
439             coordinates[coordinates.length - 1] = new OTSPoint3D(to.coordinate.x + Math.cos(ea) * offsetEnd,
440                     to.coordinate.y + Math.sin(ea) * offsetEnd, to.coordinate.z);
441             double angleStep = linkTag.arcTag.angle.getInUnit() / points;
442             double slopeStep = (to.coordinate.z - from.coordinate.z) / points;
443 
444             if (linkTag.arcTag.direction.equals(ArcDirection.RIGHT))
445             {
446                 for (int p = 1; p < points - 1; p++)
447                 {
448                     double dRad = offsetStart + (offsetEnd - offsetStart) * p / points;
449                     coordinates[p] = new OTSPoint3D(
450                             linkTag.arcTag.center.x + (radiusSI + dRad) * Math.cos(linkTag.arcTag.startAngle - angleStep * p),
451                             linkTag.arcTag.center.y + (radiusSI + dRad) * Math.sin(linkTag.arcTag.startAngle - angleStep * p),
452                             from.coordinate.z + slopeStep * p);
453                 }
454             }
455             else
456             {
457                 for (int p = 1; p < points - 1; p++)
458                 {
459                     double dRad = offsetStart + (offsetEnd - offsetStart) * p / points;
460                     coordinates[p] = new OTSPoint3D(
461                             linkTag.arcTag.center.x + (radiusSI + dRad) * Math.cos(linkTag.arcTag.startAngle + angleStep * p),
462                             linkTag.arcTag.center.y + (radiusSI + dRad) * Math.sin(linkTag.arcTag.startAngle + angleStep * p),
463                             from.coordinate.z + slopeStep * p);
464                 }
465             }
466         }
467 
468         else if (linkTag.bezierTag != null)
469         {
470             coordinates = Bezier.cubic(128, new DirectedPoint(startPoint.x, startPoint.y, startPoint.z, 0, 0, startAngle),
471                     new DirectedPoint(endPoint.x, endPoint.y, endPoint.z, 0, 0, endAngle), linkTag.bezierTag.shape,
472                     linkTag.bezierTag.weighted).getPoints();
473         }
474 
475         else
476         {
477             throw new NetworkException(
478                     "Making link, but link " + linkTag.name + " has no filled straight, arc, or bezier curve");
479         }
480 
481         OTSLine3D designLine = OTSLine3D.createAndCleanOTSLine3D(coordinates);
482 
483         // TODO: Directionality has to be added later when the lanes and their direction are known.
484         CrossSectionLink link = new CrossSectionLink(parser.network, linkTag.name, linkTag.nodeStartTag.node,
485                 linkTag.nodeEndTag.node, LinkType.FREEWAY, designLine, simulator, linkTag.laneKeepingPolicy);
486 
487         if (linkTag.priority != null)
488         {
489             link.setPriority(linkTag.priority);
490         }
491 
492         parser.networkAnimation.addDrawingInfoBase(link, new DrawingInfoLine<CrossSectionLink>(Color.BLACK, 0.5f));
493 
494         linkTag.link = link;
495     }
496 
497     /**
498      * @param linkTag LinkTag; the link to process
499      * @param parser XmlNetworkLaneParser; the parser with the lists of information
500      * @param simulator OTSSimulatorInterface; to be able to make the animation
501      * @throws NetworkException when the stripe cannot be instantiated
502      * @throws NamingException when the /animation/2D tree cannot be found in the context
503      * @throws SAXException when the stripe type cannot be parsed correctly
504      * @throws GTUException when lane block cannot be created
505      * @throws OTSGeometryException when construction of the offset-line or contour fails
506      * @throws SimRuntimeException when construction of the generator fails
507      */
508     @SuppressWarnings({ "checkstyle:needbraces", "checkstyle:methodlength" })
509     static void applyRoadTypeToLink(final LinkTag linkTag, final XmlNetworkLaneParser parser,
510             final OTSSimulatorInterface simulator)
511             throws NetworkException, NamingException, SAXException, GTUException, OTSGeometryException, SimRuntimeException
512     {
513         CrossSectionLink csl = linkTag.link;
514         List<CrossSectionElement> cseList = new ArrayList<>();
515         List<Lane> lanes = new ArrayList<>();
516         // TODO Map<GTUType, LongitudinalDirectionality> linkDirections = new HashMap<>();
517         LongitudinalDirectionality linkDirection = LongitudinalDirectionality.DIR_NONE;
518         for (CrossSectionElementTag cseTag : linkTag.roadLayoutTag.cseTags.values())
519         {
520             LaneOverrideTag laneOverrideTag = null;
521             if (linkTag.laneOverrideTags.containsKey(cseTag.name))
522                 laneOverrideTag = linkTag.laneOverrideTags.get(cseTag.name);
523 
524             Length startOffset = cseTag.offset != null ? cseTag.offset : cseTag.offSetStart;
525             Length endOffset = cseTag.offset != null ? cseTag.offset : cseTag.offSetEnd;
526             switch (cseTag.elementType)
527             {
528                 case STRIPE:
529                     switch (cseTag.stripeType)
530                     {
531                         case BLOCKED:
532                         case DASHED:
533                             Stripe dashedLine = new Stripe(csl, startOffset, endOffset, cseTag.width);
534                             dashedLine.addPermeability(GTUType.VEHICLE, Permeable.BOTH);
535                             parser.networkAnimation.addDrawingInfoBase(dashedLine,
536                                     new DrawingInfoStripe<Stripe>(Color.BLACK, 0.5f, StripeType.DASHED));
537                             cseList.add(dashedLine);
538                             break;
539 
540                         case DOUBLE:
541                             Stripe doubleLine = new Stripe(csl, startOffset, endOffset, cseTag.width);
542                             parser.networkAnimation.addDrawingInfoBase(doubleLine,
543                                     new DrawingInfoStripe<Stripe>(Color.BLACK, 0.5f, StripeType.DOUBLE));
544                             cseList.add(doubleLine);
545                             break;
546 
547                         case LEFTONLY:
548                             Stripe leftOnlyLine = new Stripe(csl, startOffset, endOffset, cseTag.width);
549                             leftOnlyLine.addPermeability(GTUType.VEHICLE, Permeable.LEFT); // TODO correct?
550                             parser.networkAnimation.addDrawingInfoBase(leftOnlyLine,
551                                     new DrawingInfoStripe<Stripe>(Color.BLACK, 0.5f, StripeType.LEFTONLY));
552                             cseList.add(leftOnlyLine);
553                             break;
554 
555                         case RIGHTONLY:
556                             Stripe rightOnlyLine = new Stripe(csl, startOffset, endOffset, cseTag.width);
557                             rightOnlyLine.addPermeability(GTUType.VEHICLE, Permeable.RIGHT); // TODO correct?
558                             parser.networkAnimation.addDrawingInfoBase(rightOnlyLine,
559                                     new DrawingInfoStripe<Stripe>(Color.BLACK, 0.5f, StripeType.RIGHTONLY));
560                             cseList.add(rightOnlyLine);
561                             break;
562 
563                         case SOLID:
564                             Stripe solidLine = new Stripe(csl, startOffset, endOffset, cseTag.width);
565                             parser.networkAnimation.addDrawingInfoBase(solidLine,
566                                     new DrawingInfoStripe<Stripe>(Color.BLACK, 0.5f, StripeType.SOLID));
567                             cseList.add(solidLine);
568                             break;
569 
570                         default:
571                             throw new SAXException("Unknown Stripe type: " + cseTag.stripeType.toString());
572                     }
573                     break;
574 
575                 case LANE:
576                 {
577                     LongitudinalDirectionality direction = cseTag.direction;
578                     Color color = cseTag.color;
579                     OvertakingConditions overtakingConditions = cseTag.overtakingConditions;
580                     if (laneOverrideTag != null)
581                     {
582                         if (laneOverrideTag.overtakingConditions != null)
583                             overtakingConditions = laneOverrideTag.overtakingConditions;
584                         if (laneOverrideTag.color != null)
585                             color = laneOverrideTag.color;
586                         if (laneOverrideTag.direction != null)
587                             direction = laneOverrideTag.direction;
588                     }
589                     if (linkDirection.equals(LongitudinalDirectionality.DIR_NONE))
590                     {
591                         linkDirection = direction;
592                     }
593                     else if (linkDirection.isForward())
594                     {
595                         if (direction.isBackwardOrBoth())
596                         {
597                             linkDirection = LongitudinalDirectionality.DIR_BOTH;
598                         }
599                     }
600                     else if (linkDirection.isBackward())
601                     {
602                         if (direction.isForwardOrBoth())
603                         {
604                             linkDirection = LongitudinalDirectionality.DIR_BOTH;
605                         }
606                     }
607 
608                     // XXX: LaneTypes with compatibilities might have to be defined in a new way -- LaneType.FREEWAY for now...
609                     Lane lane = new Lane(csl, cseTag.name, startOffset, endOffset, cseTag.width, cseTag.width, LaneType.FREEWAY,
610                             cseTag.legalSpeedLimits, overtakingConditions);
611                     // System.out.println(OTSGeometry.printCoordinates("#link design line: \nc1,0,0\n#",
612                     // lane.getParentLink().getDesignLine(), "\n "));
613                     // System.out.println(OTSGeometry.printCoordinates("#lane center line: \nc0,1,0\n#", lane.getCenterLine(),
614                     // "\n "));
615                     // System.out.println(OTSGeometry.printCoordinates("#lane contour: \nc0,0,1\n#", lane.getContour(),
616                     // "\n "));
617                     cseList.add(lane);
618                     lanes.add(lane);
619                     linkTag.lanes.put(cseTag.name, lane);
620                     if (simulator != null && simulator instanceof AnimatorInterface)
621                     {
622                         try
623                         {
624                             new LaneAnimation(lane, simulator, color, false);
625                         }
626                         catch (RemoteException exception)
627                         {
628                             exception.printStackTrace();
629                         }
630                     }
631 
632                     // SINK
633                     if (linkTag.sinkTags.keySet().contains(cseTag.name))
634                     {
635                         SinkTag sinkTag = linkTag.sinkTags.get(cseTag.name);
636                         Length position = LinkTag.parseBeginEndPosition(sinkTag.positionStr, lane);
637                         new SinkSensor(lane, position, simulator);
638                     }
639 
640                     // TRAFFICLIGHT
641                     if (linkTag.trafficLightTags.containsKey(cseTag.name))
642                     {
643                         for (TrafficLightTag trafficLightTag : linkTag.trafficLightTags.get(cseTag.name))
644                         {
645                             try
646                             {
647                                 Class<?> clazz = Class.forName(trafficLightTag.className);
648                                 Constructor<?> trafficLightConstructor = ClassUtil.resolveConstructor(clazz, new Class[] {
649                                         String.class, Lane.class, Length.class, DEVSSimulatorInterface.TimeDoubleUnit.class });
650                                 Length position = LinkTag.parseBeginEndPosition(trafficLightTag.positionStr, lane);
651                                 trafficLightConstructor
652                                         .newInstance(new Object[] { trafficLightTag.name, lane, position, simulator });
653                             }
654                             catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
655                                     | IllegalAccessException | IllegalArgumentException | InvocationTargetException
656                                     | NetworkException exception)
657                             {
658                                 throw new NetworkException("TRAFFICLIGHT: CLASS NAME " + trafficLightTag.className
659                                         + " for traffic light " + trafficLightTag.name + " on lane " + lane.toString()
660                                         + " -- class not found or constructor not right", exception);
661                             }
662                         }
663                     }
664 
665                     // GENERATOR
666                     if (linkTag.generatorTags.containsKey(cseTag.name))
667                     {
668                         GeneratorTag generatorTag = linkTag.generatorTags.get(cseTag.name);
669                         GeneratorTag.makeGenerator(generatorTag, parser, linkTag, simulator);
670                     }
671 
672                     // TODO LISTGENERATOR
673 
674                     // SENSOR
675                     if (linkTag.sensorTags.containsKey(cseTag.name))
676                     {
677                         for (SensorTag sensorTag : linkTag.sensorTags.get(cseTag.name))
678                         {
679                             try
680                             {
681                                 Class<?> clazz = Class.forName(sensorTag.className);
682                                 Constructor<?> sensorConstructor = ClassUtil.resolveConstructor(clazz,
683                                         new Class[] { String.class, Lane.class, Length.class, RelativePosition.TYPE.class,
684                                                 DEVSSimulatorInterface.TimeDoubleUnit.class, Compatible.class });
685                                 Length position = LinkTag.parseBeginEndPosition(sensorTag.positionStr, lane);
686                                 // { String.class, Lane.class, Length.class, RelativePosition.TYPE.class,
687                                 // DEVSSimulatorInterface.TimeDoubleUnit.class }
688                                 sensorConstructor.newInstance(new Object[] { sensorTag.name, lane, position,
689                                         sensorTag.triggerPosition, simulator, Compatible.EVERYTHING });
690                             }
691                             catch (ClassNotFoundException | NoSuchMethodException | InstantiationException
692                                     | IllegalAccessException | IllegalArgumentException | InvocationTargetException
693                                     | NetworkException exception)
694                             {
695                                 throw new NetworkException("SENSOR: CLASS NAME " + sensorTag.className + " for sensor "
696                                         + sensorTag.name + " on lane " + lane.toString()
697                                         + " -- class not found or constructor not right", exception);
698                             }
699                         }
700                     }
701 
702                     // FILL
703                     if (linkTag.fillTags.containsKey(cseTag.name))
704                     {
705                         FillTag fillTag = linkTag.fillTags.get(cseTag.name);
706                         FillTag.makeFill(fillTag, parser, linkTag, simulator);
707                     }
708                     break;
709                 }
710 
711                 case NOTRAFFICLANE:
712                 {
713                     Lane lane = new NoTrafficLane(csl, cseTag.name, startOffset, endOffset, cseTag.width, cseTag.width);
714                     cseList.add(lane);
715                     if (simulator != null && simulator instanceof AnimatorInterface)
716                     {
717                         try
718                         {
719                             Color color = cseTag.color;
720                             if (laneOverrideTag != null)
721                             {
722                                 if (laneOverrideTag.color != null)
723                                     color = laneOverrideTag.color;
724                             }
725                             new LaneAnimation(lane, simulator, color, false);
726                         }
727                         catch (RemoteException exception)
728                         {
729                             exception.printStackTrace();
730                         }
731                     }
732                     break;
733                 }
734 
735                 case SHOULDER:
736                 {
737                     Shoulder shoulder = new Shoulder(csl, cseTag.name, startOffset, endOffset, cseTag.width, cseTag.width);
738                     cseList.add(shoulder);
739                     if (simulator != null && simulator instanceof AnimatorInterface)
740                     {
741                         try
742                         {
743                             Color color = cseTag.color;
744                             if (laneOverrideTag != null)
745                             {
746                                 if (laneOverrideTag.color != null)
747                                     color = laneOverrideTag.color;
748                             }
749                             new ShoulderAnimation(shoulder, simulator, color);
750                         }
751                         catch (RemoteException exception)
752                         {
753                             exception.printStackTrace();
754                         }
755                     }
756                     break;
757                 }
758 
759                 default:
760                     throw new SAXException("Unknown Element type: " + cseTag.elementType.toString());
761             }
762 
763         } // for (CrossSectionElementTag cseTag : roadTypeTag.cseTags.values())
764     }
765 
766 }