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