View Javadoc
1   package org.opentrafficsim.road.network.factory.xml.parser;
2   
3   import java.lang.reflect.Constructor;
4   import java.lang.reflect.InvocationTargetException;
5   import java.util.ArrayList;
6   import java.util.LinkedHashMap;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.UUID;
10  
11  import javax.vecmath.Point3d;
12  
13  import org.djunits.unit.DirectionUnit;
14  import org.djunits.unit.LengthUnit;
15  import org.djunits.value.vdouble.scalar.Direction;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Speed;
18  import org.djutils.reflection.ClassUtil;
19  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
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.network.LinkType;
27  import org.opentrafficsim.core.network.NetworkException;
28  import org.opentrafficsim.road.network.OTSRoadNetwork;
29  import org.opentrafficsim.road.network.factory.xml.XmlParserException;
30  import org.opentrafficsim.road.network.factory.xml.utils.Cloner;
31  import org.opentrafficsim.road.network.factory.xml.utils.ParseUtil;
32  import org.opentrafficsim.road.network.factory.xml.utils.Transformer;
33  import org.opentrafficsim.road.network.lane.CrossSectionElement;
34  import org.opentrafficsim.road.network.lane.CrossSectionLink;
35  import org.opentrafficsim.road.network.lane.CrossSectionLink.Priority;
36  import org.opentrafficsim.road.network.lane.Lane;
37  import org.opentrafficsim.road.network.lane.LaneType;
38  import org.opentrafficsim.road.network.lane.NoTrafficLane;
39  import org.opentrafficsim.road.network.lane.OTSRoadNode;
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.LaneKeepingPolicy;
44  import org.opentrafficsim.xml.bindings.types.ArcDirection;
45  import org.opentrafficsim.xml.generated.BASICROADLAYOUT;
46  import org.opentrafficsim.xml.generated.CONNECTOR;
47  import org.opentrafficsim.xml.generated.CROSSSECTIONELEMENT;
48  import org.opentrafficsim.xml.generated.CSELANE;
49  import org.opentrafficsim.xml.generated.CSENOTRAFFICLANE;
50  import org.opentrafficsim.xml.generated.CSESHOULDER;
51  import org.opentrafficsim.xml.generated.CSESTRIPE;
52  import org.opentrafficsim.xml.generated.LINK;
53  import org.opentrafficsim.xml.generated.LINK.LANEOVERRIDE;
54  import org.opentrafficsim.xml.generated.NETWORK;
55  import org.opentrafficsim.xml.generated.NODE;
56  import org.opentrafficsim.xml.generated.ROADLAYOUT;
57  import org.opentrafficsim.xml.generated.SPEEDLIMIT;
58  import org.opentrafficsim.xml.generated.TRAFFICLIGHTTYPE;
59  
60  import nl.tudelft.simulation.dsol.SimRuntimeException;
61  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
62  import nl.tudelft.simulation.language.d3.DirectedPoint;
63  
64  /**
65   * NetworkParser parses the NETWORK tag of the OTS network. <br>
66   * <br>
67   * Copyright (c) 2003-2018 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
68   * for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>. The
69   * source code and binary code of this software is proprietary information of Delft University of Technology.
70   * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
71   */
72  public final class NetworkParser
73  {
74      /** */
75      private NetworkParser()
76      {
77          // utility class
78      }
79  
80      /**
81       * Parse the Nodes.
82       * @param otsNetwork OTSRoadNetwork; the network to insert the parsed objects in
83       * @param network NETWORK; the NETWORK tag
84       * @param nodeDirections Map&lt;String,Direction&gt;; a map of the node ids and their default directions
85       * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
86       */
87      public static void parseNodes(final OTSRoadNetwork otsNetwork, final NETWORK network,
88              final Map<String, Direction> nodeDirections) throws NetworkException
89      {
90          for (NODE xmlNode : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), NODE.class))
91          {
92              new OTSRoadNode(otsNetwork, xmlNode.getID(),
93                      new OTSPoint3D(xmlNode.getCOORDINATE().x, xmlNode.getCOORDINATE().y, xmlNode.getCOORDINATE().z),
94                      nodeDirections.get(xmlNode.getID()));
95          }
96      }
97  
98      /**
99       * Calculate the default angles of the Nodes, in case they have not been set. This is based on the STRAIGHT LINK elements in
100      * the XML file.
101      * @param otsNetwork OTSRoadNetwork; the network to insert the parsed objects in
102      * @param network NETWORK; the NETWORK tag
103      * @return a map of nodes and their default direction
104      */
105     public static Map<String, Direction> calculateNodeAngles(final OTSRoadNetwork otsNetwork, final NETWORK network)
106     {
107         Map<String, Direction> nodeDirections = new LinkedHashMap<>();
108         Map<String, Point3d> points = new LinkedHashMap<>();
109         for (NODE xmlNode : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), NODE.class))
110         {
111             if (xmlNode.getDIRECTION() != null)
112             {
113                 nodeDirections.put(xmlNode.getID(), xmlNode.getDIRECTION());
114             }
115             points.put(xmlNode.getID(), xmlNode.getCOORDINATE());
116         }
117 
118         for (LINK xmlLink : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), LINK.class))
119         {
120             if (xmlLink.getSTRAIGHT() != null)
121             {
122                 Point3d startPoint = points.get(xmlLink.getNODESTART());
123                 Point3d endPoint = points.get(xmlLink.getNODEEND());
124                 double direction = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
125                 if (!nodeDirections.containsKey(xmlLink.getNODESTART()))
126                 {
127                     nodeDirections.put(xmlLink.getNODESTART(), new Direction(direction, DirectionUnit.EAST_RADIAN));
128                 }
129                 if (!nodeDirections.containsKey(xmlLink.getNODEEND()))
130                 {
131                     nodeDirections.put(xmlLink.getNODEEND(), new Direction(direction, DirectionUnit.EAST_RADIAN));
132                 }
133             }
134         }
135 
136         for (NODE xmlNode : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), NODE.class))
137         {
138             if (!nodeDirections.containsKey(xmlNode.getID()))
139             {
140                 System.err.println("Warning: Node " + xmlNode.getID() + " does not have a (calculated) direction");
141             }
142         }
143 
144         return nodeDirections;
145     }
146 
147     /**
148      * Build the links with the correct design line.
149      * @param otsNetwork OTSRoadNetwork; the network to insert the parsed objects in
150      * @param network NETWORK; the NETWORK tag
151      * @param nodeDirections Map&lt;String,Direction&gt;; a map of the node ids and their default directions
152      * @param simulator OTSSimulatorInterface; the simulator
153      * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
154      * @throws OTSGeometryException when the design line is invalid
155      */
156     static void parseLinks(final OTSRoadNetwork otsNetwork, final NETWORK network, final Map<String, Direction> nodeDirections,
157             final OTSSimulatorInterface simulator) throws NetworkException, OTSGeometryException
158     {
159         for (CONNECTOR xmlConnector : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), CONNECTOR.class))
160         {
161             OTSRoadNode startNode = (OTSRoadNode) otsNetwork.getNode(xmlConnector.getNODESTART());
162             if (null == startNode)
163             {
164                 simulator.getLogger().always()
165                         .debug("No start node (" + xmlConnector.getNODESTART() + ") for CONNECTOR " + xmlConnector.getID());
166             }
167             OTSRoadNode endNode = (OTSRoadNode) otsNetwork.getNode(xmlConnector.getNODEEND());
168             if (null == endNode)
169             {
170                 simulator.getLogger().always()
171                         .debug("No end node (" + xmlConnector.getNODEEND() + ")for CONNECTOR " + xmlConnector.getID());
172             }
173             String id = xmlConnector.getID();
174             double demandWeight = xmlConnector.getDEMANDWEIGHT();
175             OTSLine3D designLine = new OTSLine3D(startNode.getPoint(), endNode.getPoint());
176             CrossSectionLink link = new CrossSectionLink(otsNetwork, id, startNode, endNode,
177                     otsNetwork.getLinkType(LinkType.DEFAULTS.CONNECTOR), designLine, null);
178             link.setDemandWeight(demandWeight);
179         }
180 
181         for (LINK xmlLink : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), LINK.class))
182         {
183             OTSRoadNode startNode = (OTSRoadNode) otsNetwork.getNode(xmlLink.getNODESTART());
184             OTSRoadNode endNode = (OTSRoadNode) otsNetwork.getNode(xmlLink.getNODEEND());
185             double startDirection =
186                     nodeDirections.containsKey(startNode.getId()) ? nodeDirections.get(startNode.getId()).getSI() : 0.0;
187             double endDirection =
188                     nodeDirections.containsKey(endNode.getId()) ? nodeDirections.get(endNode.getId()).getSI() : 0.0;
189             OTSPoint3D startPoint = new OTSPoint3D(startNode.getPoint());
190             OTSPoint3D endPoint = new OTSPoint3D(endNode.getPoint());
191             OTSPoint3D[] coordinates = null;
192 
193             if (xmlLink.getSTRAIGHT() != null)
194             {
195                 coordinates = new OTSPoint3D[2];
196                 coordinates[0] = startPoint;
197                 coordinates[1] = endPoint;
198             }
199 
200             else if (xmlLink.getPOLYLINE() != null)
201             {
202                 int intermediatePoints = xmlLink.getPOLYLINE().getCOORDINATE().size();
203                 coordinates = new OTSPoint3D[intermediatePoints + 2];
204                 coordinates[0] = startPoint;
205                 coordinates[intermediatePoints + 1] = endPoint;
206                 for (int p = 0; p < intermediatePoints; p++)
207                 {
208                     coordinates[p + 1] = new OTSPoint3D(xmlLink.getPOLYLINE().getCOORDINATE().get(p));
209                 }
210 
211             }
212             else if (xmlLink.getARC() != null)
213             {
214                 // calculate the center position
215                 double radiusSI = xmlLink.getARC().getRADIUS().getSI();
216                 double offsetStart = 0.0;
217                 if (xmlLink.getOFFSETSTART() != null)
218                 {
219                     offsetStart = xmlLink.getOFFSETSTART().si;
220                 }
221                 double offsetEnd = 0.0;
222                 if (xmlLink.getOFFSETEND() != null)
223                 {
224                     offsetEnd = xmlLink.getOFFSETEND().si;
225                 }
226                 List<OTSPoint3D> centerList = OTSPoint3D.circleIntersections(startNode.getPoint(), radiusSI + offsetStart,
227                         endNode.getPoint(), radiusSI + offsetEnd);
228                 OTSPoint3D center =
229                         (xmlLink.getARC().getDIRECTION().equals(ArcDirection.RIGHT)) ? centerList.get(0) : centerList.get(1);
230 
231                 // calculate start angle and end angle
232                 double sa = Math.atan2(startNode.getPoint().y - center.y, startNode.getPoint().x - center.x);
233                 double ea = Math.atan2(endNode.getPoint().y - center.y, endNode.getPoint().x - center.x);
234                 if (xmlLink.getARC().getDIRECTION().equals(ArcDirection.RIGHT))
235                 {
236                     // right -> negative direction, ea should be less than sa
237                     ea = (sa < ea) ? ea + Math.PI * 2.0 : ea;
238                 }
239                 else
240                 {
241                     // left -> positive direction, sa should be less than ea
242                     ea = (ea < sa) ? ea + Math.PI * 2.0 : ea;
243                 }
244 
245                 int numSegments = xmlLink.getARC().getNUMSEGMENTS().intValue();
246                 coordinates = new OTSPoint3D[numSegments];
247                 coordinates[0] = new OTSPoint3D(startNode.getPoint().x + Math.cos(sa) * offsetStart,
248                         startNode.getPoint().y + Math.sin(sa) * offsetStart, startNode.getPoint().z);
249                 coordinates[coordinates.length - 1] = new OTSPoint3D(endNode.getPoint().x + Math.cos(ea) * offsetEnd,
250                         endNode.getPoint().y + Math.sin(ea) * offsetEnd, endNode.getPoint().z);
251                 double angleStep = Math.abs((ea - sa)) / numSegments;
252                 double slopeStep = (endNode.getPoint().z - startNode.getPoint().z) / numSegments;
253 
254                 if (xmlLink.getARC().getDIRECTION().equals(ArcDirection.RIGHT))
255                 {
256                     for (int p = 1; p < numSegments - 1; p++)
257                     {
258                         double dRad = offsetStart + (offsetEnd - offsetStart) * p / numSegments;
259                         coordinates[p] = new OTSPoint3D(center.x + (radiusSI + dRad) * Math.cos(sa - angleStep * p),
260                                 center.y + (radiusSI + dRad) * Math.sin(sa - angleStep * p),
261                                 startNode.getPoint().z + slopeStep * p);
262                     }
263                 }
264                 else
265                 {
266                     for (int p = 1; p < numSegments - 1; p++)
267                     {
268                         double dRad = offsetStart + (offsetEnd - offsetStart) * p / numSegments;
269                         coordinates[p] = new OTSPoint3D(center.x + (radiusSI + dRad) * Math.cos(sa + angleStep * p),
270                                 center.y + (radiusSI + dRad) * Math.sin(sa + angleStep * p),
271                                 startNode.getPoint().z + slopeStep * p);
272                     }
273                 }
274             }
275 
276             else if (xmlLink.getBEZIER() != null)
277             {
278                 int numSegments = xmlLink.getBEZIER().getNUMSEGMENTS().intValue();
279                 double shape = xmlLink.getBEZIER().getSHAPE().doubleValue();
280                 boolean weighted = xmlLink.getBEZIER().isWEIGHTED();
281                 if (xmlLink.getBEZIER().getSTARTDIRECTION() != null)
282                 {
283                     startDirection = xmlLink.getBEZIER().getSTARTDIRECTION().getSI();
284                 }
285                 if (xmlLink.getBEZIER().getENDDIRECTION() != null)
286                 {
287                     endDirection = xmlLink.getBEZIER().getENDDIRECTION().getSI();
288                 }
289                 coordinates = Bezier
290                         .cubic(numSegments, new DirectedPoint(startPoint.x, startPoint.y, startPoint.z, 0, 0, startDirection),
291                                 new DirectedPoint(endPoint.x, endPoint.y, endPoint.z, 0, 0, endDirection), shape, weighted)
292                         .getPoints();
293             }
294 
295             else if (xmlLink.getCLOTHOID() != null)
296             {
297                 // int numSegments = xmlLink.getCLOTHOID().getNUMSEGMENTS().intValue();
298 
299                 // TODO: Clothoid parsing
300             }
301 
302             else
303             {
304                 throw new NetworkException("Making link, but link " + xmlLink.getID()
305                         + " has no filled straight, arc, bezier, polyline, or clothoid definition");
306             }
307 
308             OTSLine3D designLine = OTSLine3D.createAndCleanOTSLine3D(coordinates);
309 
310             // TODO: Directionality has to be added later when the lanes and their direction are known.
311             LaneKeepingPolicy laneKeepingPolicy = LaneKeepingPolicy.valueOf(xmlLink.getLANEKEEPING().name());
312             LinkType linkType = otsNetwork.getLinkType(xmlLink.getTYPE());
313             CrossSectionLink link = new CrossSectionLink(otsNetwork, xmlLink.getID(), startNode, endNode, linkType, designLine,
314                     laneKeepingPolicy);
315 
316             if (xmlLink.getPRIORITY() != null)
317             {
318                 Priority priority = Priority.valueOf(xmlLink.getPRIORITY());
319                 link.setPriority(priority);
320             }
321         }
322     }
323 
324     /**
325      * Build the links with the correct design line.
326      * @param otsNetwork OTSRoadNetwork; the network to insert the parsed objects in
327      * @param network NETWORK; the NETWORK tag
328      * @param simulator OTSSimulatorInterface; the simulator
329      * @param roadLayoutMap the map of the tags of the predefined ROADLAYOUT tags in DEFINITIONS
330      * @param linkTypeSpeedLimitMap map of speed limits per link type
331      * @throws NetworkException when the objects cannot be inserted into the network due to inconsistencies
332      * @throws OTSGeometryException when the design line is invalid
333      * @throws XmlParserException when the stripe type cannot be recognized
334      * @throws SimRuntimeException in case of simulation problems building the car generator
335      * @throws GTUException when construction of the Strategical Planner failed
336      */
337     static void applyRoadLayout(final OTSRoadNetwork otsNetwork, final NETWORK network, final OTSSimulatorInterface simulator,
338             final Map<String, ROADLAYOUT> roadLayoutMap, final Map<LinkType, Map<GTUType, Speed>> linkTypeSpeedLimitMap)
339             throws NetworkException, OTSGeometryException, XmlParserException, SimRuntimeException, GTUException
340     {
341         for (LINK xmlLink : ParseUtil.getObjectsOfType(network.getIncludeOrNODEOrCONNECTOR(), LINK.class))
342         {
343             CrossSectionLink csl = (CrossSectionLink) otsNetwork.getLink(xmlLink.getID());
344             List<CrossSectionElement> cseList = new ArrayList<>();
345             Map<String, Lane> lanes = new LinkedHashMap<>();
346 
347             // CategoryLogger.filter(Cat.PARSER).trace("Parse link: {}", xmlLink.getID());
348 
349             // Get the ROADLAYOUT (either defined here, or via pointer to DEFINITIONS)
350             BASICROADLAYOUT roadLayoutTagBase;
351             if (xmlLink.getDEFINEDLAYOUT() != null)
352             {
353                 if (xmlLink.getROADLAYOUT() != null)
354                 {
355                     throw new XmlParserException(
356                             "Link " + xmlLink.getID() + " Ambiguous RoadLayout; both DEFINEDROADLAYOUT and ROADLAYOUT defined");
357                 }
358                 roadLayoutTagBase = roadLayoutMap.get(xmlLink.getDEFINEDLAYOUT());
359                 if (roadLayoutTagBase == null)
360                 {
361                     throw new XmlParserException(
362                             "Link " + xmlLink.getID() + " Could not find defined RoadLayout " + xmlLink.getDEFINEDLAYOUT());
363                 }
364             }
365             else
366             {
367                 roadLayoutTagBase = xmlLink.getROADLAYOUT();
368                 if (roadLayoutTagBase == null)
369                 {
370                     throw new XmlParserException("Link " + xmlLink.getID() + " No RoadLayout defined");
371                 }
372             }
373 
374             // Process LANEOVERRIDEs
375             BASICROADLAYOUT roadLayoutTag = Cloner.cloneRoadLayout(roadLayoutTagBase);
376             for (LANEOVERRIDE laneOverride : xmlLink.getLANEOVERRIDE())
377             {
378                 for (CSELANE lane : ParseUtil.getObjectsOfType(roadLayoutTag.getLANEOrNOTRAFFICLANEOrSHOULDER(), CSELANE.class))
379                 {
380                     if (lane.getID().equals(laneOverride.getLANE()))
381                     {
382                         if (laneOverride.getSPEEDLIMIT().size() > 0)
383                         {
384                             lane.getSPEEDLIMIT().clear();
385                             lane.getSPEEDLIMIT().addAll(laneOverride.getSPEEDLIMIT());
386                         }
387                     }
388                 }
389             }
390 
391             // calculate for each lane and stripe what the start and end offset is
392             List<CSEData> cseDataList = new ArrayList<>();
393             Map<Object, Integer> cseTagMap = new LinkedHashMap<>();
394             calculateOffsets(roadLayoutTag, xmlLink, cseDataList, cseTagMap);
395             boolean fixGradualLateralOffset = xmlLink.isFIXGRADUALOFFSET();
396 
397             // STRIPE
398             for (CSESTRIPE stripeTag : ParseUtil.getObjectsOfType(roadLayoutTag.getLANEOrNOTRAFFICLANEOrSHOULDER(),
399                     CSESTRIPE.class))
400             {
401                 CSEData cseData = cseDataList.get(cseTagMap.get(stripeTag));
402                 makeStripe(csl, cseData.centerOffsetStart, cseData.centerOffsetEnd, stripeTag, cseList,
403                         fixGradualLateralOffset);
404             }
405 
406             // Other CROSSECTIONELEMENT
407             for (CROSSSECTIONELEMENT cseTag : ParseUtil.getObjectsOfType(roadLayoutTag.getLANEOrNOTRAFFICLANEOrSHOULDER(),
408                     CROSSSECTIONELEMENT.class))
409             {
410                 CSEData cseData = cseDataList.get(cseTagMap.get(cseTag));
411 
412                 // LANE
413                 if (cseTag instanceof CSELANE)
414                 {
415                     CSELANE laneTag = (CSELANE) cseTag;
416                     boolean direction = laneTag.isDESIGNDIRECTION();
417                     LaneType laneType = otsNetwork.getLaneType(laneTag.getLANETYPE());
418                     // TODO: Use the DESIGNDIRECTION
419                     Map<GTUType, Speed> speedLimitMap = new LinkedHashMap<>();
420                     LinkType linkType = csl.getLinkType();
421                     if (!linkTypeSpeedLimitMap.containsKey(linkType))
422                     {
423                         linkTypeSpeedLimitMap.put(linkType, new LinkedHashMap<>());
424                     }
425                     speedLimitMap.putAll(linkTypeSpeedLimitMap.get(linkType));
426                     for (SPEEDLIMIT speedLimitTag : roadLayoutTag.getSPEEDLIMIT())
427                     {
428                         GTUType gtuType = otsNetwork.getGtuType(speedLimitTag.getGTUTYPE());
429                         speedLimitMap.put(gtuType, speedLimitTag.getLEGALSPEEDLIMIT());
430                     }
431                     for (SPEEDLIMIT speedLimitTag : laneTag.getSPEEDLIMIT())
432                     {
433                         GTUType gtuType = otsNetwork.getGtuType(speedLimitTag.getGTUTYPE());
434                         speedLimitMap.put(gtuType, speedLimitTag.getLEGALSPEEDLIMIT());
435                     }
436                     Lane lane = new Lane(csl, laneTag.getID(), cseData.centerOffsetStart, cseData.centerOffsetEnd,
437                             cseData.widthStart, cseData.widthEnd, laneType, speedLimitMap, fixGradualLateralOffset);
438                     cseList.add(lane);
439                     lanes.put(lane.getId(), lane);
440                 }
441 
442                 // NOTRAFFICLANE
443                 else if (cseTag instanceof CSENOTRAFFICLANE)
444                 {
445                     CSENOTRAFFICLANE ntlTag = (CSENOTRAFFICLANE) cseTag;
446                     String id = ntlTag.getID() != null ? ntlTag.getID() : UUID.randomUUID().toString();
447                     Lane lane = new NoTrafficLane(csl, id, cseData.centerOffsetStart, cseData.centerOffsetEnd,
448                             cseData.widthStart, cseData.widthEnd, fixGradualLateralOffset);
449                     cseList.add(lane);
450                 }
451 
452                 // SHOULDER
453                 else if (cseTag instanceof CSESHOULDER)
454                 {
455                     CSESHOULDER shoulderTag = (CSESHOULDER) cseTag;
456                     String id = shoulderTag.getID() != null ? shoulderTag.getID() : UUID.randomUUID().toString();
457                     Shoulder shoulder = new Shoulder(csl, id, cseData.centerOffsetStart, cseData.centerOffsetEnd,
458                             cseData.widthStart, cseData.widthEnd, fixGradualLateralOffset);
459                     cseList.add(shoulder);
460                 }
461             }
462 
463             // TRAFFICLIGHT
464             for (TRAFFICLIGHTTYPE trafficLight : xmlLink.getTRAFFICLIGHT())
465             {
466                 if (!lanes.containsKey(trafficLight.getLANE()))
467                 {
468                     throw new NetworkException("LINK: " + xmlLink.getID() + ", TrafficLight with id " + trafficLight.getID()
469                             + " on Lane " + trafficLight.getLANE() + " - Lane not found");
470                 }
471                 Lane lane = lanes.get(trafficLight.getLANE());
472                 Length position = Transformer.parseLengthBeginEnd(trafficLight.getPOSITION(), lane.getLength());
473                 try
474                 {
475                     Constructor<?> trafficLightConstructor = ClassUtil.resolveConstructor(trafficLight.getCLASS(),
476                             new Class[] {String.class, Lane.class, Length.class, DEVSSimulatorInterface.TimeDoubleUnit.class});
477                     trafficLightConstructor.newInstance(new Object[] {trafficLight.getID(), lane, position, simulator});
478                 }
479                 catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException
480                         | InvocationTargetException exception)
481                 {
482                     throw new NetworkException("TRAFFICLIGHT: CLASS NAME " + trafficLight.getCLASS().getName()
483                             + " for traffic light " + trafficLight.getID() + " on lane " + lane.toString() + " at position "
484                             + position + " -- class not found or constructor not right", exception);
485                     // TODO: this discards too much information; e.g. Network already contains an object with the name ...
486                 }
487             }
488         }
489     }
490 
491     /**
492      * Calculate the offsets for the RoadLlayout. Note that offsets can be different for begin and end, and that they can be
493      * specified from the right, left or center of the lane/stripe. Start width and end width can be different. The overall Link
494      * can have an additional start offset and end offset that has to be added to the already calculated offsets.
495      * @param roadLayoutTag the tag for the road layout containing all lanes and stripes
496      * @param xmlLink the LINK tag containing the overall offsets
497      * @param cseDataList the list of offsets and widths for each tag, in order of definition in the ROADLAYOUT tag
498      * @param cseTagMap the map of the tags to the index in the list, to be able to find them quickly
499      */
500     @SuppressWarnings("checkstyle:methodlength")
501     private static void calculateOffsets(final BASICROADLAYOUT roadLayoutTag, final LINK xmlLink,
502             final List<CSEData> cseDataList, final Map<Object, Integer> cseTagMap)
503     {
504         int nr = 0;
505         Length totalWidthStart = Length.ZERO;
506         Length totalWidthEnd = Length.ZERO;
507         boolean startOffset = false;
508         boolean endOffset = false;
509         for (Object o : roadLayoutTag.getLANEOrNOTRAFFICLANEOrSHOULDER())
510         {
511             if (o instanceof CSESTRIPE)
512             {
513                 CSESTRIPE stripe = (CSESTRIPE) o;
514                 CSEData cseData = new CSEData();
515                 cseData.widthStart = Length.ZERO;
516                 cseData.widthEnd = Length.ZERO;
517                 if (stripe.getCENTEROFFSET() != null)
518                 {
519                     cseData.centerOffsetStart = stripe.getCENTEROFFSET();
520                     cseData.centerOffsetEnd = stripe.getCENTEROFFSET();
521                     startOffset = true;
522                     endOffset = true;
523                 }
524                 else
525                 {
526                     if (stripe.getCENTEROFFSETSTART() != null)
527                     {
528                         cseData.centerOffsetStart = stripe.getCENTEROFFSETSTART();
529                         startOffset = true;
530                     }
531                     if (stripe.getCENTEROFFSETEND() != null)
532                     {
533                         cseData.centerOffsetEnd = stripe.getCENTEROFFSETEND();
534                         endOffset = true;
535                     }
536                 }
537                 cseDataList.add(cseData);
538             }
539             else
540             {
541                 CROSSSECTIONELEMENT cse = (CROSSSECTIONELEMENT) o;
542                 CSEData cseData = new CSEData();
543                 cseData.widthStart = cse.getWIDTH() == null ? cse.getWIDTHSTART() : cse.getWIDTH();
544                 Length halfWidthStart = cseData.widthStart.times(0.5);
545                 totalWidthStart = totalWidthStart.plus(cseData.widthStart);
546                 cseData.widthEnd = cse.getWIDTH() == null ? cse.getWIDTHEND() : cse.getWIDTH();
547                 Length halfWidthEnd = cseData.widthEnd.times(0.5);
548                 totalWidthEnd = totalWidthEnd.plus(cseData.widthStart);
549 
550                 if (cse.getCENTEROFFSET() != null)
551                 {
552                     cseData.centerOffsetStart = cse.getCENTEROFFSET();
553                     cseData.centerOffsetEnd = cse.getCENTEROFFSET();
554                     startOffset = true;
555                     endOffset = true;
556                 }
557                 else if (cse.getLEFTOFFSET() != null)
558                 {
559                     cseData.centerOffsetStart = cse.getLEFTOFFSET().minus(halfWidthStart);
560                     cseData.centerOffsetEnd = cse.getLEFTOFFSET().minus(halfWidthEnd);
561                     startOffset = true;
562                     endOffset = true;
563                 }
564                 else if (cse.getRIGHTOFFSET() != null)
565                 {
566                     cseData.centerOffsetStart = cse.getRIGHTOFFSET().plus(halfWidthStart);
567                     cseData.centerOffsetEnd = cse.getRIGHTOFFSET().plus(halfWidthEnd);
568                     startOffset = true;
569                     endOffset = true;
570                 }
571 
572                 if (cse.getCENTEROFFSETSTART() != null)
573                 {
574                     cseData.centerOffsetStart = cse.getCENTEROFFSETSTART();
575                     startOffset = true;
576                 }
577                 else if (cse.getLEFTOFFSETSTART() != null)
578                 {
579                     cseData.centerOffsetStart = cse.getLEFTOFFSETSTART().minus(halfWidthStart);
580                     startOffset = true;
581                 }
582                 else if (cse.getRIGHTOFFSETSTART() != null)
583                 {
584                     cseData.centerOffsetStart = cse.getRIGHTOFFSETSTART().plus(halfWidthStart);
585                     startOffset = true;
586                 }
587 
588                 if (cse.getCENTEROFFSETEND() != null)
589                 {
590                     cseData.centerOffsetEnd = cse.getCENTEROFFSETEND();
591                     endOffset = true;
592                 }
593                 else if (cse.getLEFTOFFSETEND() != null)
594                 {
595                     cseData.centerOffsetEnd = cse.getLEFTOFFSETEND().minus(halfWidthEnd);
596                     endOffset = true;
597                 }
598                 else if (cse.getRIGHTOFFSETEND() != null)
599                 {
600                     cseData.centerOffsetEnd = cse.getRIGHTOFFSETEND().plus(halfWidthEnd);
601                     endOffset = true;
602                 }
603                 cseDataList.add(cseData);
604             }
605             cseTagMap.put(o, nr);
606             nr++;
607         }
608 
609         if (!startOffset)
610         {
611             cseDataList.get(0).centerOffsetStart = totalWidthStart.times(-0.5).minus(cseDataList.get(0).widthStart.times(-0.5));
612         }
613         if (!endOffset)
614         {
615             cseDataList.get(0).centerOffsetEnd = totalWidthEnd.times(-0.5).minus(cseDataList.get(0).widthEnd.times(-0.5));
616         }
617 
618         // forward pass
619         Length cs = null;
620         Length es = null;
621         for (CSEData cseData : cseDataList)
622         {
623             if (cseData.centerOffsetStart != null)
624             {
625                 cs = cseData.centerOffsetStart.plus(cseData.widthStart.times(0.5));
626             }
627             else
628             {
629                 if (cs != null)
630                 {
631                     cseData.centerOffsetStart = cs.plus(cseData.widthStart.times(0.5));
632                     cs = cs.plus(cseData.widthStart);
633                 }
634             }
635             if (cseData.centerOffsetEnd != null)
636             {
637                 es = cseData.centerOffsetEnd.plus(cseData.widthEnd.times(0.5));
638             }
639             else
640             {
641                 if (es != null)
642                 {
643                     cseData.centerOffsetEnd = es.plus(cseData.widthEnd.times(0.5));
644                     es = es.plus(cseData.widthEnd);
645                 }
646             }
647         }
648 
649         // backward pass
650         cs = null;
651         es = null;
652         for (int i = cseDataList.size() - 1; i >= 0; i--)
653         {
654             CSEData cseData = cseDataList.get(i);
655             if (cseData.centerOffsetStart != null)
656             {
657                 cs = cseData.centerOffsetStart.minus(cseData.widthStart.times(0.5));
658             }
659             else
660             {
661                 if (cs != null)
662                 {
663                     cseData.centerOffsetStart = cs.minus(cseData.widthStart.times(0.5));
664                     cs = cs.minus(cseData.widthStart);
665                 }
666             }
667             if (cseData.centerOffsetEnd != null)
668             {
669                 es = cseData.centerOffsetEnd.minus(cseData.widthEnd.times(0.5));
670             }
671             else
672             {
673                 if (es != null)
674                 {
675                     cseData.centerOffsetEnd = es.minus(cseData.widthEnd.times(0.5));
676                     es = es.minus(cseData.widthEnd);
677                 }
678             }
679         }
680 
681         // add the link offset
682         if (xmlLink.getOFFSETSTART() != null && xmlLink.getOFFSETSTART().ne0())
683         {
684             for (CSEData cseData : cseDataList)
685             {
686                 cseData.centerOffsetStart = cseData.centerOffsetStart.plus(xmlLink.getOFFSETSTART());
687             }
688         }
689         if (xmlLink.getOFFSETEND() != null && xmlLink.getOFFSETEND().ne0())
690         {
691             for (CSEData cseData : cseDataList)
692             {
693                 cseData.centerOffsetEnd = cseData.centerOffsetEnd.plus(xmlLink.getOFFSETEND());
694             }
695         }
696     }
697 
698     /**
699      * Parse a stripe on a road.
700      * @param csl CrossSectionLink; the CrossSectionLine
701      * @param startOffset Length; the offset of the start node
702      * @param endOffset Length; the offset of the end node
703      * @param stripeTag CSESTRIPE; the CSESTRIPE tag in the XML file
704      * @param cseList List&lt;CrossSectionElement&gt;; the list of CrossSectionElements to which the stripes should be added
705      * @param fixGradualLateralOffset boolean; true if gradualLateralOffset needs to be fixed
706      * @throws OTSGeometryException when creation of the center line or contour geometry fails
707      * @throws NetworkException when id of the stripe not unique
708      * @throws XmlParserException when the stripe type cannot be recognized
709      */
710     private static void makeStripe(final CrossSectionLink csl, final Length startOffset, final Length endOffset,
711             final CSESTRIPE stripeTag, final List<CrossSectionElement> cseList, final boolean fixGradualLateralOffset)
712             throws OTSGeometryException, NetworkException, XmlParserException
713     {
714         Length width =
715                 stripeTag.getDRAWINGWIDTH() != null ? stripeTag.getDRAWINGWIDTH() : new Length(20.0, LengthUnit.CENTIMETER);
716         switch (stripeTag.getTYPE())
717         {
718             case BLOCKED:
719                 Stripe blockedLine = new Stripe(csl, startOffset, endOffset, stripeTag.getDRAWINGWIDTH() != null
720                         ? stripeTag.getDRAWINGWIDTH() : new Length(40.0, LengthUnit.CENTIMETER), fixGradualLateralOffset);
721                 blockedLine.addPermeability(csl.getNetwork().getGtuType(GTUType.DEFAULTS.ROAD_USER), Permeable.BOTH);
722                 cseList.add(blockedLine);
723                 break;
724 
725             case DASHED:
726                 Stripe dashedLine = new Stripe(csl, startOffset, endOffset, width, fixGradualLateralOffset);
727                 dashedLine.addPermeability(csl.getNetwork().getGtuType(GTUType.DEFAULTS.ROAD_USER), Permeable.BOTH);
728                 cseList.add(dashedLine);
729                 break;
730 
731             case DOUBLE:
732                 Stripe doubleLine = new Stripe(csl, startOffset, endOffset, width, fixGradualLateralOffset);
733                 cseList.add(doubleLine);
734                 break;
735 
736             case LEFTONLY:
737                 Stripe leftOnlyLine = new Stripe(csl, startOffset, endOffset, width, fixGradualLateralOffset);
738                 leftOnlyLine.addPermeability(csl.getNetwork().getGtuType(GTUType.DEFAULTS.ROAD_USER), Permeable.LEFT);
739                 cseList.add(leftOnlyLine);
740                 break;
741 
742             case RIGHTONLY:
743                 Stripe rightOnlyLine = new Stripe(csl, startOffset, endOffset, width, fixGradualLateralOffset);
744                 rightOnlyLine.addPermeability(csl.getNetwork().getGtuType(GTUType.DEFAULTS.ROAD_USER), Permeable.RIGHT);
745                 cseList.add(rightOnlyLine);
746                 break;
747 
748             case SOLID:
749                 try
750                 {
751                     Stripe solidLine = new Stripe(csl, startOffset, endOffset, width, fixGradualLateralOffset);
752                     cseList.add(solidLine);
753                 }
754                 catch (OTSGeometryException oge)
755                 {
756                     System.out.println("Caught OTSGeometryException constructing a stripe on " + csl);
757                 }
758                 break;
759 
760             default:
761                 throw new XmlParserException("Unknown Stripe type: " + stripeTag.getTYPE().toString());
762         }
763     }
764 
765     /** contains information about the lanes and stripes to calculate the offset. */
766     protected static class CSEData
767     {
768         /** the start width of the element (stripes are defined as 0). */
769         @SuppressWarnings("checkstyle:visibilitymodifier")
770         public Length widthStart;
771 
772         /** the end width of the element (stripes are defined as 0). */
773         @SuppressWarnings("checkstyle:visibilitymodifier")
774         public Length widthEnd;
775 
776         /** the start offset of the element. */
777         @SuppressWarnings("checkstyle:visibilitymodifier")
778         public Length centerOffsetStart;
779 
780         /** the end offset of the element. */
781         @SuppressWarnings("checkstyle:visibilitymodifier")
782         public Length centerOffsetEnd;
783 
784         /** {@inheritDoc} */
785         @Override
786         public String toString()
787         {
788             return "CSEData [widthStart=" + this.widthStart + ", widthEnd=" + this.widthEnd + ", centerOffsetStart="
789                     + this.centerOffsetStart + ", centerOffsetEnd=" + this.centerOffsetEnd + "]";
790         }
791     }
792 
793 }