View Javadoc
1   package org.opentrafficsim.core.network.factory;
2   
3   import java.awt.Color;
4   import java.awt.geom.Point2D;
5   import java.io.IOException;
6   import java.io.InputStream;
7   import java.net.URL;
8   import java.rmi.RemoteException;
9   import java.util.ArrayDeque;
10  import java.util.ArrayList;
11  import java.util.Deque;
12  import java.util.HashMap;
13  import java.util.HashSet;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.Set;
17  
18  import javax.naming.NamingException;
19  import javax.vecmath.Point2d;
20  import javax.vecmath.Point3d;
21  import javax.xml.parsers.ParserConfigurationException;
22  import javax.xml.parsers.SAXParser;
23  import javax.xml.parsers.SAXParserFactory;
24  
25  import nl.tudelft.simulation.jstats.distributions.DistBeta;
26  import nl.tudelft.simulation.jstats.distributions.DistConstant;
27  import nl.tudelft.simulation.jstats.distributions.DistContinuous;
28  import nl.tudelft.simulation.jstats.distributions.DistErlang;
29  import nl.tudelft.simulation.jstats.distributions.DistExponential;
30  import nl.tudelft.simulation.jstats.distributions.DistGamma;
31  import nl.tudelft.simulation.jstats.distributions.DistLogNormal;
32  import nl.tudelft.simulation.jstats.distributions.DistNormal;
33  import nl.tudelft.simulation.jstats.distributions.DistPearson5;
34  import nl.tudelft.simulation.jstats.distributions.DistPearson6;
35  import nl.tudelft.simulation.jstats.distributions.DistTriangular;
36  import nl.tudelft.simulation.jstats.distributions.DistUniform;
37  import nl.tudelft.simulation.jstats.distributions.DistWeibull;
38  import nl.tudelft.simulation.jstats.streams.MersenneTwister;
39  import nl.tudelft.simulation.jstats.streams.StreamInterface;
40  import nl.tudelft.simulation.language.io.URLResource;
41  
42  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
43  import org.opentrafficsim.core.gtu.GTUType;
44  import org.opentrafficsim.core.gtu.following.GTUFollowingModel;
45  import org.opentrafficsim.core.gtu.following.IDM;
46  import org.opentrafficsim.core.gtu.following.IDMPlus;
47  import org.opentrafficsim.core.gtu.lane.changing.Altruistic;
48  import org.opentrafficsim.core.gtu.lane.changing.Egoistic;
49  import org.opentrafficsim.core.gtu.lane.changing.LaneChangeModel;
50  import org.opentrafficsim.core.network.Link;
51  import org.opentrafficsim.core.network.LongitudinalDirectionality;
52  import org.opentrafficsim.core.network.Network;
53  import org.opentrafficsim.core.network.NetworkException;
54  import org.opentrafficsim.core.network.Node;
55  import org.opentrafficsim.core.network.animation.LaneAnimation;
56  import org.opentrafficsim.core.network.animation.ShoulderAnimation;
57  import org.opentrafficsim.core.network.geotools.LinearGeometry;
58  import org.opentrafficsim.core.network.geotools.LinkGeotools;
59  import org.opentrafficsim.core.network.geotools.NodeGeotools;
60  import org.opentrafficsim.core.network.lane.CrossSectionElement;
61  import org.opentrafficsim.core.network.lane.CrossSectionLink;
62  import org.opentrafficsim.core.network.lane.Lane;
63  import org.opentrafficsim.core.network.lane.LaneType;
64  import org.opentrafficsim.core.network.lane.NoTrafficLane;
65  import org.opentrafficsim.core.network.lane.RoadMarkerAlong;
66  import org.opentrafficsim.core.network.lane.Shoulder;
67  import org.opentrafficsim.core.network.point2d.NodePoint2D;
68  import org.opentrafficsim.core.unit.AnglePlaneUnit;
69  import org.opentrafficsim.core.unit.AngleSlopeUnit;
70  import org.opentrafficsim.core.unit.FrequencyUnit;
71  import org.opentrafficsim.core.unit.LengthUnit;
72  import org.opentrafficsim.core.unit.SpeedUnit;
73  import org.opentrafficsim.core.unit.TimeUnit;
74  import org.opentrafficsim.core.value.vdouble.scalar.DistContinuousDoubleScalar;
75  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
76  import org.xml.sax.Attributes;
77  import org.xml.sax.SAXException;
78  import org.xml.sax.helpers.DefaultHandler;
79  
80  import com.vividsolutions.jts.geom.Coordinate;
81  import com.vividsolutions.jts.geom.GeometryFactory;
82  import com.vividsolutions.jts.geom.LineString;
83  
84  /**
85   * Parse an XML string with a simple representation of a lane-based network. An example of such a network is:
86   * 
87   * <pre>
88   * &lt;?xml version="1.0" encoding="UTF-8"?&gt;
89   * &lt;NETWORK xmlns="http://www.example.org/ots-infra" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
90   *     xsi:schemaLocation="http://www.example.org/ots-infra ots-infra.xsd "&gt;
91   * 
92   *     &lt;GLOBAL WIDTH="4m" SPEED="80km/h" /&gt;
93   * 
94   *     &lt;NODE NAME="N1" COORDINATE="(0,0)" /&gt;
95   *     &lt;NODE NAME="N2" /&gt;
96   * 
97   *     &lt;LINK NAME="A4_12" FROM="N1" TO="N2" ELEMENTS="X1|V2:V1|D|A1:A2|X2"&gt;
98   *         &lt;STRAIGHT LENGTH="50m" SPEED="80km/h" WIDTH="4m" /&gt;
99   *         &lt;LANE NAME="X1" WIDTH="1m" /&gt;
100  *         &lt;LANE NAME="X2" WIDTH="1m" /&gt;
101  *     &lt;/LINK&gt;
102  * 
103  *     &lt;NODE NAME="N3b2" /&gt;
104  * 
105  *     &lt;LINK NAME="A4_13" FROM="N2" TO="N3 b2" ELEMENTS="X1|V2:V1|D|A1:A2|X2"&gt;
106  *         &lt;ARC RADIUS="100m" ANGLE="90" /&gt;
107  *         &lt;LANE NAME="X1" WIDTH="1m" /&gt;
108  *         &lt;LANE NAME="X2" WIDTH="1m" /&gt;
109  *     &lt;/LINK&gt;
110  * 
111  *     &lt;NODE NAME="N4" /&gt;
112  * 
113  *     &lt;LINK NAME="A4_14" FROM="N3b2" TO="N4" ELEMENTS="X1|V2:V1|D|A1:A2|:A3|X2"&gt;
114  *         &lt;STRAIGHT LENGTH="50m" SPEED="80km/h" WIDTH="4m" /&gt;
115  *         &lt;LANE NAME="X1" WIDTH="1m" /&gt;
116  *         &lt;LANE NAME="X2" WIDTH="1m" /&gt;
117  *         &lt;LANE NAME="A3" SPEED="60km/h" /&gt;
118  *     &lt;/LINK&gt;
119  * 
120  *     &lt;NODE NAME="N5" /&gt;
121  *     &lt;NODE NAME="ENTRY5" /&gt;
122  * 
123  *     &lt;LINK NAME="A4_15" FROM="N4" TO="N5" ELEMENTS="X1|V2:V1|D|A1:A2|X2"&gt;
124  *         &lt;STRAIGHT LENGTH="100m" SPEED="80km/h" WIDTH="4m" /&gt;
125  *         &lt;LANE NAME="X1" WIDTH="1m" /&gt;
126  *         &lt;LANE NAME="X2" WIDTH="1m" /&gt;
127  *     &lt;/LINK&gt;
128  * 
129  *     &lt;LINK NAME="LE1" FROM="ENTRY5" TO="N3b2(A3)" ELEMENTS="|AD|"&gt;
130  *         &lt;ARC RADIUS="100m" ANGLE="-45" SPEED="60km/h" /&gt;
131  *     &lt;/LINK&gt;
132  * 
133  *     &lt;NODE NAME="ENTRY6" /&gt;
134  *     &lt;LINK NAME="LE2" FROM="ENTRY6" TO="ENTRY5" ELEMENTS="|AD|"&gt;
135  *         &lt;ARC RADIUS="100m" ANGLE="45" SPEED="60km/h" /&gt;
136  *     &lt;/LINK&gt;
137  * 
138  * &lt;/NETWORK&gt;
139  * 
140  * </pre>
141  * <p>
142  * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
143  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
144  * <p>
145  * @version Feb 6, 2015 <br>
146  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
147  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
148  */
149 public class XmlNetworkLaneParser
150 {
151     /** the ID class of the Network. */
152     @SuppressWarnings("visibilitymodifier")
153     protected final Class<?> networkIdClass;
154 
155     /** the class of the Node. */
156     @SuppressWarnings("visibilitymodifier")
157     protected final Class<?> nodeClass;
158 
159     /** the ID class of the Node. */
160     @SuppressWarnings("visibilitymodifier")
161     protected final Class<?> nodeIdClass;
162 
163     /** the Point class of the Node. */
164     @SuppressWarnings("visibilitymodifier")
165     protected final Class<?> nodePointClass;
166 
167     /** the class of the Link. */
168     @SuppressWarnings("visibilitymodifier")
169     protected final Class<?> linkClass;
170 
171     /** the ID class of the Link. */
172     @SuppressWarnings("visibilitymodifier")
173     protected final Class<?> linkIdClass;
174 
175     /** the generated network. */
176     private Network<?, ?> network;
177 
178     /** the speed units. */
179     private static final Map<String, SpeedUnit> SPEED_UNITS = new HashMap<>();
180 
181     /** the length units. */
182     private static final Map<String, LengthUnit> LENGTH_UNITS = new HashMap<>();
183 
184     /** the time units. */
185     private static final Map<String, TimeUnit> TIME_UNITS = new HashMap<>();
186 
187     /** the processed nodes for further reference. */
188     @SuppressWarnings({"rawtypes", "visibilitymodifier"})
189     protected Map<String, Node> nodes = new HashMap<>();
190 
191     /** the UNprocessed nodes for further reference. */
192     @SuppressWarnings("visibilitymodifier")
193     protected Map<String, NodeTag> nodeTags = new HashMap<>();
194 
195     /** the links for further reference. */
196     @SuppressWarnings({"rawtypes", "visibilitymodifier"})
197     protected Map<String, Link> links = new HashMap<>();
198 
199     /** the gtu tags for further reference. */
200     @SuppressWarnings("visibilitymodifier")
201     protected Map<String, GTUTag> gtuTags = new HashMap<>();
202 
203     /** the GTUTypes that have been created. */
204     @SuppressWarnings("visibilitymodifier")
205     protected Map<String, GTUType<String>> gtuTypes = new HashMap<>();
206 
207     /** TODO incorporate into grammar. */
208     private final LaneType<String> laneType = new LaneType<String>("CarLane");
209 
210     /** the simulator for creating the animation. Null if no animation needed. */
211     private OTSSimulatorInterface simulator;
212 
213     static
214     {
215         SPEED_UNITS.put("km/h", SpeedUnit.KM_PER_HOUR);
216         SPEED_UNITS.put("mi/h", SpeedUnit.MILE_PER_HOUR);
217         SPEED_UNITS.put("m/s", SpeedUnit.METER_PER_SECOND);
218         SPEED_UNITS.put("ft/s", SpeedUnit.FOOT_PER_SECOND);
219 
220         LENGTH_UNITS.put("mm", LengthUnit.MILLIMETER);
221         LENGTH_UNITS.put("cm", LengthUnit.CENTIMETER);
222         LENGTH_UNITS.put("dm", LengthUnit.DECIMETER);
223         LENGTH_UNITS.put("dam", LengthUnit.DEKAMETER);
224         LENGTH_UNITS.put("hm", LengthUnit.HECTOMETER);
225         LENGTH_UNITS.put("m", LengthUnit.METER);
226         LENGTH_UNITS.put("km", LengthUnit.KILOMETER);
227         LENGTH_UNITS.put("mi", LengthUnit.MILE);
228         LENGTH_UNITS.put("y", LengthUnit.YARD);
229         LENGTH_UNITS.put("ft", LengthUnit.FOOT);
230 
231         TIME_UNITS.put("ms", TimeUnit.MILLISECOND);
232         TIME_UNITS.put("s", TimeUnit.SECOND);
233         TIME_UNITS.put("m", TimeUnit.MINUTE);
234         TIME_UNITS.put("min", TimeUnit.MINUTE);
235         TIME_UNITS.put("h", TimeUnit.HOUR);
236         TIME_UNITS.put("hr", TimeUnit.HOUR);
237         TIME_UNITS.put("d", TimeUnit.DAY);
238         TIME_UNITS.put("day", TimeUnit.DAY);
239         TIME_UNITS.put("wk", TimeUnit.WEEK);
240         TIME_UNITS.put("week", TimeUnit.WEEK);
241     }
242 
243     /**
244      * @param networkIdClass the ID class of the Network.
245      * @param nodeClass the class of the Node.
246      * @param nodeIdClass the ID class of the Node.
247      * @param nodePointClass the Point class of the Node.
248      * @param linkClass the class of the Link.
249      * @param linkIdClass the ID class of the Link.
250      * @param simulator the simulator for creating the animation. Null if no animation needed.
251      */
252     public XmlNetworkLaneParser(final Class<?> networkIdClass, final Class<?> nodeClass, final Class<?> nodeIdClass,
253         final Class<?> nodePointClass, final Class<?> linkClass, final Class<?> linkIdClass,
254         final OTSSimulatorInterface simulator)
255     {
256         this.networkIdClass = networkIdClass;
257         this.nodeClass = nodeClass;
258         this.nodeIdClass = nodeIdClass;
259         this.nodePointClass = nodePointClass;
260         this.linkClass = linkClass;
261         this.linkIdClass = linkIdClass;
262         this.simulator = simulator;
263     }
264 
265     /**
266      * @param is the file with the network in the agreed xml-grammar.
267      * @return the network with Nodes, Links, and Lanes.
268      * @throws NetworkException in case of parsing problems.
269      * @throws SAXException in case of parsing problems.
270      * @throws ParserConfigurationException in case of parsing problems.
271      * @throws IOException in case of file reading problems.
272      */
273     public final Network<?, ?> build(final InputStream is) throws NetworkException, ParserConfigurationException,
274         SAXException, IOException
275     {
276         // clear storage.
277         this.nodes.clear();
278 
279         SAXParserFactory parserFactor = SAXParserFactory.newInstance();
280         SAXParser parser = parserFactor.newSAXParser();
281         SAXHandler handler = new SAXHandler();
282         parser.parse(is, handler);
283 
284         // this.network = new Network(makeId(this.networkIdClass, networkId));
285         return this.network;
286     }
287 
288     /**
289      * The Handler for SAX Events.
290      */
291     class SAXHandler extends DefaultHandler
292     {
293         /** local storage. */
294         // private String content = null;
295 
296         /** depth list. */
297         private Deque<String> stack = new ArrayDeque<String>();
298 
299         /** global values from the GLOBAL tag. */
300         private GlobalTag globalTag;
301 
302         /** link values from the LINK tag. */
303         private LinkTag linkTag;
304 
305         /** link values from the NODE tag. */
306         private NodeTag nodeTag;
307 
308         @Override
309         @SuppressWarnings("checkstyle:methodlength")
310         public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
311             throws SAXException
312         {
313             System.out.println("start: " + qName);
314             try
315             {
316                 if (!qName.equals("NETWORK"))
317                 {
318                     switch (this.stack.getLast())
319                     {
320                         case "NETWORK":
321                             switch (qName)
322                             {
323                                 case "GLOBAL":
324                                     parseGlobalTag(attributes);
325                                     break;
326 
327                                 case "NODE":
328                                     parseNodeTag(attributes);
329                                     break;
330 
331                                 case "LINK":
332                                     parseLinkTag(attributes);
333                                     break;
334 
335                                 case "GTU":
336                                     parseGTUTag(attributes);
337                                     break;
338 
339                                 default:
340                                     throw new SAXException("NETWORK: Received start tag " + qName + ", but stack contains: "
341                                         + this.stack);
342                             }
343                             break;
344 
345                         case "LINK":
346                             switch (qName)
347                             {
348                                 case "STRAIGHT":
349                                     parseStraightTag(attributes);
350                                     break;
351 
352                                 case "ARC":
353                                     parseArcTag(attributes);
354                                     break;
355 
356                                 case "LANE":
357                                     parseLaneTag(attributes);
358                                     break;
359 
360                                 case "GENERATOR":
361                                     parseGeneratorTag(attributes);
362                                     break;
363 
364                                 case "FILL":
365                                     parseFillTag(attributes);
366                                     break;
367 
368                                 default:
369                                     throw new SAXException("LINK: Received start tag " + qName + ", but stack contains: "
370                                         + this.stack);
371                             }
372                             break;
373 
374                         default:
375                             throw new SAXException("startElement: Received start tag " + qName + ", but stack contains: "
376                                 + this.stack);
377                     }
378                 }
379                 this.stack.addLast(qName);
380             }
381             catch (Exception e)
382             {
383                 throw new SAXException(e);
384             }
385         }
386 
387         @Override
388         public void endElement(final String uri, final String localName, final String qName) throws SAXException
389         {
390             System.out.println("end  : " + qName);
391             if (!this.stack.getLast().equals(qName))
392             {
393                 throw new SAXException("endElement: Received /" + qName + ", but stack contains: " + this.stack);
394             }
395             this.stack.removeLast();
396 
397             try
398             {
399                 if (!qName.equals("NETWORK"))
400                 {
401                     switch (this.stack.getLast())
402                     {
403                         case "NETWORK":
404                             switch (qName)
405                             {
406                                 case "GLOBAL":
407                                     break;
408 
409                                 case "NODE":
410                                     XmlNetworkLaneParser.this.nodeTags.put(this.nodeTag.name, this.nodeTag);
411                                     if (this.nodeTag.coordinate != null)
412                                     {
413                                         // only make a node if we know the coordinate. Otherwise, wait till we can calculate it.
414                                         @SuppressWarnings("rawtypes")
415                                         Node node = makeNode(XmlNetworkLaneParser.this.nodeClass, this.nodeTag);
416                                         XmlNetworkLaneParser.this.nodes.put(node.getId().toString(), node);
417                                     }
418                                     break;
419 
420                                 case "LINK":
421                                     calculateNodeCoordinates(this.linkTag);
422                                     @SuppressWarnings("rawtypes")
423                                     CrossSectionLink link = makeLink(this.linkTag);
424                                     parseElements(this.linkTag.elements, link, this.linkTag, this.globalTag);
425                                     XmlNetworkLaneParser.this.links.put(link.getId().toString(), link);
426                                     break;
427 
428                                 case "GTU":
429                                     break;
430 
431                                 default:
432                                     throw new SAXException("NETWORK: Received end tag " + qName + ", but stack contains: "
433                                         + this.stack);
434                             }
435                             break;
436 
437                         case "LINK":
438                             switch (qName)
439                             {
440                                 case "STRAIGHT":
441                                     break;
442                                 case "ARC":
443                                     break;
444                                 case "LANE":
445                                     break;
446                                 case "GENERATOR":
447                                     break;
448                                 case "FILL":
449                                     break;
450                                 default:
451                                     throw new SAXException("LINK: Received end tag " + qName + ", but stack contains: "
452                                         + this.stack);
453                             }
454                             break;
455 
456                         default:
457                             throw new SAXException("Received end tag " + qName + ", but stack contains: " + this.stack);
458                     }
459                 }
460             }
461             catch (Exception e)
462             {
463                 throw new SAXException(e);
464             }
465         }
466 
467         /**
468          * Parse the GLOBAL tag with global values.
469          * @param attributes the attributes of the XML-tag.
470          * @throws NetworkException in case of OTS logic error.
471          */
472         @SuppressWarnings("checkstyle:needbraces")
473         private void parseGlobalTag(final Attributes attributes) throws NetworkException
474         {
475             this.globalTag = new GlobalTag();
476             if (attributes.getValue("SPEED") != null)
477                 this.globalTag.speed = parseSpeedAbs(attributes.getValue("SPEED"));
478             if (attributes.getValue("WIDTH") != null)
479                 this.globalTag.width = parseLengthRel(attributes.getValue("WIDTH"));
480         }
481 
482         /**
483          * Parse the NODE tag with attributes of a Node.
484          * @param attributes the attributes of the XML-tag.
485          * @throws NetworkException in case of OTS logic error.
486          * @throws SAXException in case of parse error.
487          */
488         @SuppressWarnings("checkstyle:needbraces")
489         private void parseNodeTag(final Attributes attributes) throws NetworkException, SAXException
490         {
491             this.nodeTag = new NodeTag();
492 
493             String name = attributes.getValue("NAME");
494             if (name == null)
495                 throw new SAXException("NODE: missing attribute NAME");
496             this.nodeTag.name = name;
497 
498             if (attributes.getValue("COORDINATE") != null)
499                 this.nodeTag.coordinate = parseCoordinate(attributes.getValue("COORDINATE"));
500 
501             if (attributes.getValue("ANGLE") != null)
502                 this.nodeTag.angle =
503                     new DoubleScalar.Abs<AnglePlaneUnit>(Double.parseDouble(attributes.getValue("ANGLE")),
504                         AnglePlaneUnit.DEGREE);
505         }
506 
507         /**
508          * Parse the LINK tag with attributes of a Link.
509          * @param attributes the attributes of the XML-tag.
510          * @throws NetworkException in case of OTS logic error.
511          * @throws SAXException in case of parse error.
512          */
513         @SuppressWarnings("checkstyle:needbraces")
514         private void parseLinkTag(final Attributes attributes) throws NetworkException, SAXException
515         {
516             this.linkTag = new LinkTag();
517 
518             String name = attributes.getValue("NAME");
519             if (name == null)
520                 throw new SAXException("NODE: missing attribute NAME");
521             this.linkTag.name = name;
522 
523             String elements = attributes.getValue("ELEMENTS");
524             if (elements == null)
525                 throw new SAXException("NODE: missing attribute ELEMENTS");
526             this.linkTag.elements = attributes.getValue("ELEMENTS");
527             String[] nameStrings = elements.split("(\\-)|(\\|)|(\\:)|(\\<)|(\\>)|(\\#)");
528             for (String laneName : nameStrings)
529             {
530                 if (laneName.length() > 0 && !laneName.equals("D"))
531                 {
532                     LaneTag laneTag = new LaneTag();
533                     laneTag.name = laneName;
534                     this.linkTag.laneTags.put(laneName, laneTag);
535                 }
536             }
537 
538             String fromNodeStr = attributes.getValue("FROM");
539             if (fromNodeStr == null)
540                 throw new SAXException("NODE: missing attribute FROM for link " + name);
541             this.linkTag.nodeFromName = fromNodeStr;
542             @SuppressWarnings("rawtypes")
543             Node fromNode = XmlNetworkLaneParser.this.nodes.get(fromNodeStr);
544             this.linkTag.nodeFrom = fromNode;
545 
546             String toNodeStr = attributes.getValue("TO");
547             if (toNodeStr == null)
548                 throw new SAXException("NODE: missing attribute TO for link " + name);
549             this.linkTag.nodeToName = toNodeStr;
550             @SuppressWarnings("rawtypes")
551             Node toNode = XmlNetworkLaneParser.this.nodes.get(toNodeStr);
552             this.linkTag.nodeTo = toNode;
553 
554             if (attributes.getValue("SPEED") != null)
555                 this.linkTag.speed = parseSpeedAbs(attributes.getValue("SPEED"));
556 
557             if (attributes.getValue("WIDTH") != null)
558                 this.linkTag.width = parseLengthRel(attributes.getValue("WIDTH"));
559         }
560 
561         /**
562          * Parse the GTU tag with attributes of a GTU.
563          * @param attributes the attributes of the XML-tag.
564          * @throws NetworkException in case of OTS logic error.
565          * @throws SAXException in case of parse error.
566          */
567         @SuppressWarnings("checkstyle:needbraces")
568         private void parseGTUTag(final Attributes attributes) throws NetworkException, SAXException
569         {
570             GTUTag gtuTag = new GTUTag();
571 
572             String name = attributes.getValue("NAME");
573             if (name == null)
574                 throw new SAXException("GTU: missing attribute NAME");
575             gtuTag.name = name;
576 
577             String gtuType = attributes.getValue("GTUTYPE");
578             if (gtuType == null)
579                 throw new SAXException("GTU: missing attribute GTUTYPE");
580             gtuTag.gtuType = parseGTUType(attributes.getValue("GTUTYPE"));
581 
582             String length = attributes.getValue("LENGTH");
583             if (length == null)
584                 throw new SAXException("GTU: missing attribute LENGTH");
585             gtuTag.lengthDist = parseLengthDistRel(attributes.getValue("LENGTH"));
586 
587             String width = attributes.getValue("WIDTH");
588             if (width == null)
589                 throw new SAXException("GTU: missing attribute WIDTH");
590             gtuTag.widthDist = parseLengthDistRel(attributes.getValue("WIDTH"));
591 
592             String following = attributes.getValue("FOLLOWING");
593             if (following == null)
594                 throw new SAXException("GTU: missing attribute FOLLOWING");
595             gtuTag.followingModel = parseFollowingModel(attributes.getValue("FOLLOWING"));
596 
597             String laneChange = attributes.getValue("LANECHANGE");
598             if (laneChange == null)
599                 throw new SAXException("GTU: missing attribute LANECHANGE");
600             gtuTag.laneChangeModel = parseLaneChangeModel(attributes.getValue("LANECHANGE"));
601 
602             String maxSpeed = attributes.getValue("MAXSPEED");
603             if (maxSpeed == null)
604                 throw new SAXException("GTU: missing attribute LENGTH");
605             gtuTag.maxSpeedDist = parseSpeedDistAbs(attributes.getValue("MAXSPEED"));
606 
607             XmlNetworkLaneParser.this.gtuTags.put(gtuTag.name, gtuTag);
608         }
609 
610         /**
611          * Parse the STRAIGHT tag with straight attributes for a Link.
612          * @param attributes the attributes of the XML-tag.
613          * @throws NetworkException in case of OTS logic error.
614          * @throws SAXException in case of parse error.
615          */
616         @SuppressWarnings("checkstyle:needbraces")
617         private void parseStraightTag(final Attributes attributes) throws NetworkException, SAXException
618         {
619             this.linkTag.straightTag = new StraightTag();
620             String length = attributes.getValue("LENGTH");
621             if (length == null)
622                 throw new SAXException("STRAIGHT: missing attribute LENGTH");
623             this.linkTag.straightTag.length = parseLengthRel(length);
624         }
625 
626         /**
627          * Parse the ARC tag with arc attributes for a Link.
628          * @param attributes the attributes of the XML-tag.
629          * @throws NetworkException in case of OTS logic error.
630          * @throws SAXException in case of parse error.
631          */
632         @SuppressWarnings("checkstyle:needbraces")
633         private void parseArcTag(final Attributes attributes) throws NetworkException, SAXException
634         {
635             this.linkTag.arcTag = new ArcTag();
636 
637             String radius = attributes.getValue("RADIUS");
638             if (radius == null)
639                 throw new SAXException("ARC: missing attribute RADIUS");
640             this.linkTag.arcTag.radius = parseLengthRel(radius);
641 
642             String angle = attributes.getValue("ANGLE");
643             if (angle == null)
644                 throw new SAXException("ARC: missing attribute ANGLE");
645             this.linkTag.arcTag.angle =
646                 new DoubleScalar.Abs<AnglePlaneUnit>(Double.parseDouble(angle), AnglePlaneUnit.DEGREE);
647 
648             String dir = attributes.getValue("DIRECTION");
649             if (dir == null)
650                 throw new SAXException("ARC: missing attribute ANGLE");
651             this.linkTag.arcTag.direction =
652                 (dir.equals("L") || dir.equals("LEFT") || dir.equals("COUNTERCLOCKWISE")) ? ArcDirection.LEFT
653                     : ArcDirection.RIGHT;
654         }
655 
656         /**
657          * Parse the LANE tag with lane attributes for a Link.
658          * @param attributes the attributes of the XML-tag.
659          * @throws NetworkException in case of OTS logic error.
660          * @throws SAXException in case of parse error.
661          */
662         @SuppressWarnings("checkstyle:needbraces")
663         private void parseLaneTag(final Attributes attributes) throws NetworkException, SAXException
664         {
665             String name = attributes.getValue("NAME");
666             if (name == null)
667                 throw new SAXException("LANE: missing attribute NAME");
668             LaneTag laneTag = this.linkTag.laneTags.get(name);
669             if (laneTag == null)
670                 throw new NetworkException("LANE: Lane with NAME " + name + "not found in elements of link "
671                     + this.linkTag.name);
672 
673             if (attributes.getValue("SPEED") != null)
674                 laneTag.speed = parseSpeedAbs(attributes.getValue("SPEED"));
675 
676             if (attributes.getValue("WIDTH") != null)
677                 laneTag.width = parseLengthRel(attributes.getValue("WIDTH"));
678         }
679 
680         /**
681          * Parse the GENERATOR tag with GTU generation attributes for a Lane.
682          * @param attributes the attributes of the XML-tag.
683          * @throws NetworkException in case of OTS logic error.
684          * @throws SAXException in case of parse error.
685          */
686         @SuppressWarnings("checkstyle:needbraces")
687         private void parseGeneratorTag(final Attributes attributes) throws NetworkException, SAXException
688         {
689             GeneratorTag generatorTag = new GeneratorTag();
690 
691             String laneName = attributes.getValue("LANE");
692             if (laneName == null)
693                 throw new SAXException("GENERATOR: missing attribute LANE");
694             LaneTag laneTag = this.linkTag.laneTags.get(laneName);
695             if (laneTag == null)
696                 throw new NetworkException("LANE: Lane with NAME " + laneName + "not found in elements of link "
697                     + this.linkTag.name);
698             generatorTag.laneTag = laneTag;
699 
700             String gtuName = attributes.getValue("GTU");
701             if (gtuName == null)
702                 throw new SAXException("GENERATOR: missing attribute GTU");
703             if (!XmlNetworkLaneParser.this.gtuTags.containsKey(gtuName))
704                 throw new NetworkException("GENERATOR: LANE " + laneName + " GTU " + gtuName + " not defined in link "
705                     + this.linkTag.name);
706             generatorTag.gtuTag = XmlNetworkLaneParser.this.gtuTags.get(gtuName);
707 
708             String iat = attributes.getValue("IAT");
709             if (iat == null)
710                 throw new SAXException("GENERATOR: missing attribute IAT");
711             generatorTag.iatDist = parseTimeDistRel(iat);
712 
713             String initialSpeed = attributes.getValue("INITIALSPEED");
714             if (initialSpeed == null)
715                 throw new SAXException("GENERATOR: missing attribute INITIALSPEED");
716             generatorTag.initialSpeedDist = parseSpeedDistAbs(initialSpeed);
717 
718             String maxGTU = attributes.getValue("MAXGTU");
719             generatorTag.maxGTUs = maxGTU == null ? Integer.MAX_VALUE : Integer.parseInt(maxGTU);
720 
721             if (attributes.getValue("STARTTIME") != null)
722                 generatorTag.startTime = parseTimeAbs(attributes.getValue("STARTTIME"));
723 
724             if (attributes.getValue("ENDTIME") != null)
725                 generatorTag.endTime = parseTimeAbs(attributes.getValue("ENDTIME"));
726 
727             generatorTag.laneTag.generatorTags.add(generatorTag);
728         }
729 
730         /**
731          * Parse the FILL tag with GTU fill attributes for a Lane.
732          * @param attributes the attributes of the XML-tag.
733          * @throws NetworkException in case of OTS logic error.
734          * @throws SAXException in case of parse error.
735          */
736         @SuppressWarnings("checkstyle:needbraces")
737         private void parseFillTag(final Attributes attributes) throws NetworkException, SAXException
738         {
739             FillTag fillTag = new FillTag();
740 
741             String laneName = attributes.getValue("LANE");
742             if (laneName == null)
743                 throw new SAXException("FILL: missing attribute LANE");
744             if (!this.linkTag.laneTags.containsKey(laneName))
745                 throw new NetworkException("FILL: LANE " + laneName + " not defined in link " + this.linkTag.name);
746             fillTag.laneTag = this.linkTag.laneTags.get(laneName);
747 
748             String gtuName = attributes.getValue("GTU");
749             if (gtuName == null)
750                 throw new SAXException("FILL: missing attribute GTU");
751             if (!XmlNetworkLaneParser.this.gtuTags.containsKey(gtuName))
752                 throw new NetworkException("FILL: LANE " + laneName + " GTU " + gtuName + " not defined in link "
753                     + this.linkTag.name);
754             fillTag.gtuTag = XmlNetworkLaneParser.this.gtuTags.get(gtuName);
755 
756             String distance = attributes.getValue("DISTANCE");
757             if (distance == null)
758                 throw new SAXException("FILL: missing attribute DISTANCE");
759             fillTag.distanceDist = parseLengthDistRel(distance);
760 
761             String initialSpeed = attributes.getValue("INITIALSPEED");
762             if (initialSpeed == null)
763                 throw new SAXException("FILL: missing attribute INITIALSPEED");
764             fillTag.initialSpeedDist = parseSpeedDistAbs(initialSpeed);
765 
766             String maxGTU = attributes.getValue("MAXGTU");
767             fillTag.maxGTUs = maxGTU == null ? Integer.MAX_VALUE : Integer.parseInt(maxGTU);
768 
769             fillTag.laneTag.fillTags.add(fillTag);
770         }
771 
772     }
773 
774     /*************************************************************************************************/
775     /****************************************** PARSING CLASSES **************************************/
776     /*************************************************************************************************/
777 
778     /**
779      * Generate an ID of the right type.
780      * @param clazz the class to instantiate.
781      * @param ids the id as a String.
782      * @return the object as an instance of the right class.
783      * @throws NetworkException when id cannot be instantiated
784      */
785     protected final Object makeId(final Class<?> clazz, final String ids) throws NetworkException
786     {
787         Object id = null;
788         try
789         {
790             if (String.class.isAssignableFrom(clazz))
791             {
792                 id = new String(ids);
793             }
794             else if (int.class.isAssignableFrom(clazz))
795             {
796                 id = Integer.valueOf(ids);
797             }
798             else if (long.class.isAssignableFrom(clazz))
799             {
800                 id = Long.valueOf(ids);
801             }
802             else
803             {
804                 throw new NetworkException("Parsing network. ID class " + clazz.getName() + ": cannot instantiate.");
805             }
806         }
807         catch (NumberFormatException nfe)
808         {
809             throw new NetworkException("Parsing network. ID class " + clazz.getName() + ": cannot instantiate number: "
810                 + ids, nfe);
811         }
812         return id;
813     }
814 
815     /**
816      * Generate an ID of the right type.
817      * @param clazz the class to instantiate.
818      * @param p the point as a String.
819      * @return the object as an instance of the right class.
820      * @throws NetworkException when point cannot be instantiated
821      */
822     protected final Object makePoint(final Class<?> clazz, final Point3d p) throws NetworkException
823     {
824         Object point = null;
825         if (Point3d.class.isAssignableFrom(clazz))
826         {
827             point = p;
828         }
829         else if (Point2D.class.isAssignableFrom(clazz))
830         {
831             point = new Point2D.Double(p.x, p.y);
832         }
833         else if (Point2d.class.isAssignableFrom(clazz))
834         {
835             point = new Point2d(new double[] {p.x, p.y});
836         }
837         else if (Coordinate.class.isAssignableFrom(clazz))
838         {
839             point = new Coordinate(p.x, p.y, p.z);
840         }
841         else
842         {
843             throw new NetworkException("Parsing network. Point class " + clazz.getName() + ": cannot instantiate.");
844         }
845         return point;
846     }
847 
848     /**
849      * @param clazz the node class
850      * @param nodeTag the tag with the infor for the node.
851      * @return a constructed node
852      * @throws NetworkException when point cannot be instantiated
853      * @throws NamingException when animation context cannot be found.
854      * @throws RemoteException when communication error occurs when trying to find animation context.
855      */
856     @SuppressWarnings({"unchecked", "rawtypes"})
857     protected final Node makeNode(final Class<?> clazz, final NodeTag nodeTag) throws NetworkException, RemoteException,
858         NamingException
859     {
860         Object id = makeId(this.nodeIdClass, nodeTag.name);
861         Object point = makePoint(this.nodePointClass, nodeTag.coordinate);
862         DoubleScalar.Abs<AnglePlaneUnit> angle =
863             nodeTag.angle == null ? new DoubleScalar.Abs<AnglePlaneUnit>(0.0, AnglePlaneUnit.SI) : nodeTag.angle;
864         DoubleScalar.Abs<AngleSlopeUnit> slope =
865             nodeTag.slope == null ? new DoubleScalar.Abs<AngleSlopeUnit>(0.0, AngleSlopeUnit.SI) : nodeTag.slope;
866         if (NodeGeotools.class.isAssignableFrom(clazz))
867         {
868             if (point instanceof Coordinate)
869             {
870                 Node node = new NodeGeotools(id, (Coordinate) point, angle, slope);
871                 this.nodes.put(id.toString(), node);
872                 return node;
873             }
874             throw new NetworkException("Parsing network. Node class " + clazz.getName()
875                 + ": cannot instantiate. Wrong Coordinate type: " + point.getClass() + ", coordinate: " + point);
876         }
877         else if (NodePoint2D.class.isAssignableFrom(clazz))
878         {
879             if (point instanceof Point2D)
880             {
881                 Node node = new NodePoint2D(id, (Point2D) point, angle, slope);
882                 this.nodes.put(id.toString(), node);
883                 return node;
884             }
885             throw new NetworkException("Parsing network. Node class " + clazz.getName()
886                 + ": cannot instantiate. Wrong Point2D type: " + point.getClass() + ", coordinate: " + point);
887         }
888         else
889         {
890             throw new NetworkException("Parsing network. Node class " + clazz.getName() + ": cannot instantiate.");
891         }
892     }
893 
894     /**
895      * One of the nodes probably has a coordinate and the other not. Calculate the other coordinate and save the Node.
896      * @param linkTag the parsed information from the XML file.
897      * @throws NetworkException when both nodes are null.
898      * @throws RemoteException when coordinate cannot be reached.
899      * @throws NamingException when node animation cannot link to the animation context.
900      */
901     @SuppressWarnings("methodlength")
902     protected final void calculateNodeCoordinates(final LinkTag linkTag) throws RemoteException, NetworkException,
903         NamingException
904     {
905         // calculate dx, dy and dz for the straight or the arc.
906         if (linkTag.nodeFrom != null && linkTag.nodeTo != null)
907         {
908             if (linkTag.arcTag != null)
909             {
910                 double radiusSI = linkTag.arcTag.radius.getSI();
911                 ArcDirection direction = linkTag.arcTag.direction;
912                 Point3d coordinate =
913                     new Point3d(linkTag.nodeFrom.getLocation().getX(), linkTag.nodeFrom.getLocation().getY(),
914                         linkTag.nodeFrom.getLocation().getZ());
915                 double startAngle = linkTag.nodeFrom.getDirection().getSI();
916                 if (direction.equals(ArcDirection.LEFT))
917                 {
918                     linkTag.arcTag.center =
919                         new Point3d(coordinate.x + radiusSI * Math.cos(startAngle + Math.PI / 2.0), coordinate.y + radiusSI
920                             * Math.sin(startAngle + Math.PI / 2.0), 0.0);
921                     linkTag.arcTag.startAngle = startAngle - Math.PI / 2.0;
922                 }
923                 else
924                 {
925                     linkTag.arcTag.center =
926                         new Point3d(coordinate.x + radiusSI * Math.cos(startAngle - Math.PI / 2.0), coordinate.y + radiusSI
927                             * Math.sin(startAngle - Math.PI / 2.0), 0.0);
928                     linkTag.arcTag.startAngle = startAngle + Math.PI / 2.0;
929                 }
930             }
931             return;
932         }
933 
934         if (linkTag.nodeFrom == null && linkTag.nodeTo == null)
935         {
936             throw new NetworkException("Parsing network. Link: " + linkTag.name + ", both From-node and To-node are null");
937         }
938 
939         if (linkTag.straightTag != null)
940         {
941             double lengthSI = linkTag.straightTag.length.getSI();
942             if (linkTag.nodeTo == null)
943             {
944                 Point3d coordinate =
945                     new Point3d(linkTag.nodeFrom.getLocation().getX(), linkTag.nodeFrom.getLocation().getY(),
946                         linkTag.nodeFrom.getLocation().getZ());
947                 double angle = linkTag.nodeFrom.getDirection().getSI();
948                 double slope = linkTag.nodeFrom.getSlope().getSI();
949                 coordinate.x += lengthSI * Math.cos(angle);
950                 coordinate.y += lengthSI * Math.sin(angle);
951                 coordinate.z += lengthSI * Math.sin(slope);
952                 NodeTag nodeTag = this.nodeTags.get(linkTag.nodeToName);
953                 nodeTag.angle = new DoubleScalar.Abs<AnglePlaneUnit>(angle, AnglePlaneUnit.SI);
954                 nodeTag.coordinate = coordinate;
955                 nodeTag.slope = new DoubleScalar.Abs<AngleSlopeUnit>(slope, AngleSlopeUnit.SI);
956                 @SuppressWarnings("rawtypes")
957                 Node node = makeNode(this.nodeClass, nodeTag);
958                 linkTag.nodeTo = node;
959             }
960             else if (linkTag.nodeFrom == null)
961             {
962                 Point3d coordinate =
963                     new Point3d(linkTag.nodeTo.getLocation().getX(), linkTag.nodeTo.getLocation().getY(), linkTag.nodeTo
964                         .getLocation().getZ());
965                 double angle = linkTag.nodeTo.getDirection().getSI();
966                 double slope = linkTag.nodeTo.getSlope().getSI();
967                 coordinate.x -= lengthSI * Math.cos(angle);
968                 coordinate.y -= lengthSI * Math.sin(angle);
969                 coordinate.z -= lengthSI * Math.sin(slope);
970                 NodeTag nodeTag = this.nodeTags.get(linkTag.nodeFromName);
971                 nodeTag.angle = new DoubleScalar.Abs<AnglePlaneUnit>(angle, AnglePlaneUnit.SI);
972                 nodeTag.coordinate = coordinate;
973                 nodeTag.slope = new DoubleScalar.Abs<AngleSlopeUnit>(slope, AngleSlopeUnit.SI);
974                 @SuppressWarnings("rawtypes")
975                 Node node = makeNode(this.nodeClass, nodeTag);
976                 linkTag.nodeFrom = node;
977             }
978         }
979         else if (linkTag.arcTag != null)
980         {
981             double radiusSI = linkTag.arcTag.radius.getSI();
982             double angle = linkTag.arcTag.angle.getSI();
983             ArcDirection direction = linkTag.arcTag.direction;
984             if (linkTag.nodeTo == null)
985             {
986                 Point3d coordinate =
987                     new Point3d(linkTag.nodeFrom.getLocation().getX(), linkTag.nodeFrom.getLocation().getY(),
988                         linkTag.nodeFrom.getLocation().getZ());
989                 double startAngle = linkTag.nodeFrom.getDirection().getSI();
990                 double slope = linkTag.nodeFrom.getSlope().getSI();
991                 double lengthSI = radiusSI * angle;
992                 if (direction.equals(ArcDirection.LEFT))
993                 {
994                     linkTag.arcTag.center =
995                         new Point3d(coordinate.x + radiusSI * Math.cos(startAngle + Math.PI / 2.0), coordinate.y + radiusSI
996                             * Math.sin(startAngle + Math.PI / 2.0), 0.0);
997                     linkTag.arcTag.startAngle = startAngle - Math.PI / 2.0;
998                     coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle + angle);
999                     coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle + angle);
1000                 }
1001                 else
1002                 {
1003                     linkTag.arcTag.center =
1004                         new Point3d(coordinate.x + radiusSI * Math.cos(startAngle - Math.PI / 2.0), coordinate.y + radiusSI
1005                             * Math.sin(startAngle - Math.PI / 2.0), 0.0);
1006                     linkTag.arcTag.startAngle = startAngle + Math.PI / 2.0;
1007                     coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle - angle);
1008                     coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle - angle);
1009                 }
1010                 coordinate.z += lengthSI * Math.sin(slope);
1011                 NodeTag nodeTag = this.nodeTags.get(linkTag.nodeToName);
1012                 nodeTag.angle = new DoubleScalar.Abs<AnglePlaneUnit>(norm(startAngle - angle), AnglePlaneUnit.SI);
1013                 nodeTag.coordinate = coordinate;
1014                 nodeTag.slope = new DoubleScalar.Abs<AngleSlopeUnit>(slope, AngleSlopeUnit.SI);
1015                 @SuppressWarnings("rawtypes")
1016                 Node node = makeNode(this.nodeClass, nodeTag);
1017                 linkTag.nodeTo = node;
1018             }
1019 
1020             else if (linkTag.nodeFrom == null)
1021             {
1022                 Point3d coordinate =
1023                     new Point3d(linkTag.nodeTo.getLocation().getX(), linkTag.nodeTo.getLocation().getY(), linkTag.nodeTo
1024                         .getLocation().getZ());
1025                 double endAngle = linkTag.nodeTo.getDirection().getSI();
1026                 double slope = linkTag.nodeTo.getSlope().getSI();
1027                 double lengthSI = radiusSI * angle;
1028                 NodeTag nodeTag = this.nodeTags.get(linkTag.nodeFromName);
1029                 if (direction.equals(ArcDirection.LEFT))
1030                 {
1031                     linkTag.arcTag.center =
1032                         new Point3d(coordinate.x + radiusSI + Math.cos(endAngle + Math.PI / 2.0), coordinate.y + radiusSI
1033                             * Math.sin(endAngle + Math.PI / 2.0), 0.0);
1034                     linkTag.arcTag.startAngle = endAngle - Math.PI / 2.0 - angle;
1035                     coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
1036                     coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
1037                     nodeTag.angle =
1038                         new DoubleScalar.Abs<AnglePlaneUnit>(norm(linkTag.arcTag.startAngle + Math.PI / 2.0),
1039                             AnglePlaneUnit.SI);
1040                 }
1041                 else
1042                 {
1043                     linkTag.arcTag.center =
1044                         new Point3d(coordinate.x + radiusSI * Math.cos(endAngle - Math.PI / 2.0), coordinate.y + radiusSI
1045                             * Math.sin(endAngle - Math.PI / 2.0), 0.0);
1046                     linkTag.arcTag.startAngle = endAngle + Math.PI / 2.0 + angle;
1047                     coordinate.x = linkTag.arcTag.center.x + radiusSI * Math.cos(linkTag.arcTag.startAngle);
1048                     coordinate.y = linkTag.arcTag.center.y + radiusSI * Math.sin(linkTag.arcTag.startAngle);
1049                     nodeTag.angle =
1050                         new DoubleScalar.Abs<AnglePlaneUnit>(norm(linkTag.arcTag.startAngle - Math.PI / 2.0),
1051                             AnglePlaneUnit.SI);
1052                 }
1053                 coordinate.z -= lengthSI * Math.sin(slope);
1054                 nodeTag.coordinate = coordinate;
1055                 nodeTag.slope = new DoubleScalar.Abs<AngleSlopeUnit>(slope, AngleSlopeUnit.SI);
1056                 @SuppressWarnings("rawtypes")
1057                 Node node = makeNode(this.nodeClass, nodeTag);
1058                 linkTag.nodeFrom = node;
1059             }
1060         }
1061 
1062     }
1063 
1064     // FIXME put in utility class. Also exists in CrossSectionElement.
1065     /**
1066      * normalize an angle between 0 and 2 * PI.
1067      * @param angle original angle.
1068      * @return angle between 0 and 2 * PI.
1069      */
1070     private double norm(final double angle)
1071     {
1072         double normalized = angle % (2 * Math.PI);
1073         if (normalized < 0.0)
1074         {
1075             normalized += 2 * Math.PI;
1076         }
1077         return normalized;
1078     }
1079 
1080     /**
1081      * FIXME LinkGeotools should extend CrossSectionLink and not the other way around.
1082      * @param linkTag the link information from XML.
1083      * @return a constructed link
1084      * @throws SAXException when point cannot be instantiated
1085      * @throws NamingException when animation context cannot be found.
1086      * @throws RemoteException when communication error occurs when reaching animation context.
1087      */
1088     @SuppressWarnings({"unchecked", "rawtypes"})
1089     protected final CrossSectionLink makeLink(final LinkTag linkTag) throws SAXException, RemoteException, NamingException
1090     {
1091         try
1092         {
1093             if (LinkGeotools.class.isAssignableFrom(this.linkClass))
1094             {
1095                 Object id = makeId(this.linkIdClass, linkTag.name);
1096                 DoubleScalar.Rel<LengthUnit> length = null;
1097                 LinearGeometry geometry = null;
1098                 int points = 2;
1099                 if (linkTag.arcTag != null)
1100                 {
1101                     points = (Math.abs(linkTag.arcTag.angle.getSI()) <= Math.PI / 2.0) ? 32 : 64;
1102                 }
1103                 NodeTag from = this.nodeTags.get(linkTag.nodeFromName);
1104                 NodeTag to = this.nodeTags.get(linkTag.nodeToName);
1105                 Coordinate[] coordinates = new Coordinate[points];
1106                 coordinates[0] = new Coordinate(from.coordinate.x, from.coordinate.y, from.coordinate.z);
1107                 coordinates[coordinates.length - 1] = new Coordinate(to.coordinate.x, to.coordinate.y, to.coordinate.z);
1108                 if (linkTag.straightTag != null)
1109                 {
1110                     length = linkTag.straightTag.length;
1111                 }
1112                 else if (linkTag.arcTag != null)
1113                 {
1114                     length =
1115                         new DoubleScalar.Rel<LengthUnit>(linkTag.arcTag.radius.getInUnit() * linkTag.arcTag.angle.getSI(),
1116                             linkTag.arcTag.radius.getUnit());
1117                     double angleStep = linkTag.arcTag.angle.getSI() / points;
1118                     double slopeStep = (to.coordinate.z - from.coordinate.z) / points;
1119                     double radiusSI = linkTag.arcTag.radius.getSI();
1120                     if (linkTag.arcTag.direction.equals(ArcDirection.RIGHT))
1121                     {
1122                         for (int p = 1; p < points - 1; p++)
1123                         {
1124                             coordinates[p] =
1125                                 new Coordinate(linkTag.arcTag.center.x + radiusSI
1126                                     * Math.cos(linkTag.arcTag.startAngle - angleStep * p), linkTag.arcTag.center.y
1127                                     + radiusSI * Math.sin(linkTag.arcTag.startAngle - angleStep * p), from.coordinate.z
1128                                     + slopeStep * p);
1129                         }
1130                     }
1131                     else
1132                     {
1133                         for (int p = 1; p < points - 1; p++)
1134                         {
1135                             coordinates[p] =
1136                                 new Coordinate(linkTag.arcTag.center.x + radiusSI
1137                                     * Math.cos(linkTag.arcTag.startAngle + angleStep * p), linkTag.arcTag.center.y
1138                                     + radiusSI * Math.sin(linkTag.arcTag.startAngle + angleStep * p), from.coordinate.z
1139                                     + slopeStep * p);
1140                         }
1141                     }
1142                 }
1143                 CrossSectionLink link =
1144                     new CrossSectionLink(id, (NodeGeotools) linkTag.nodeFrom, (NodeGeotools) linkTag.nodeTo, length);
1145                 GeometryFactory factory = new GeometryFactory();
1146                 LineString lineString = factory.createLineString(coordinates);
1147                 geometry = new LinearGeometry(link, lineString, null);
1148                 link.setGeometry(geometry);
1149                 return link;
1150             }
1151             else
1152             {
1153                 throw new SAXException("Parsing network. Link class " + this.linkClass.getName() + ": cannot instantiate.");
1154             }
1155         }
1156         catch (NetworkException ne)
1157         {
1158             throw new SAXException("Error building Link", ne);
1159         }
1160     }
1161 
1162     /**
1163      * @param elements the string such as "X1|V2:V1|D|A1:A2|:A3|X2"
1164      * @param csl the cross-section link to which the cross-section elements belong.
1165      * @param linkTag the link with possible information about speed and width.
1166      * @param globalTag the global tag with possible information about speed and width.
1167      * @return a list of cross-section elements
1168      * @throws SAXException for unknown lane type or other inconsistencies.
1169      * @throws NamingException when animation context cannot be found.
1170      * @throws RemoteException when animation context cannot be reached.
1171      */
1172     @SuppressWarnings({"rawtypes", "checkstyle:methodlength"})
1173     protected final List<CrossSectionElement> parseElements(final String elements, final CrossSectionLink csl,
1174         final LinkTag linkTag, final GlobalTag globalTag) throws SAXException, RemoteException, NamingException
1175     {
1176         List<CrossSectionElement> cseList = new ArrayList<>();
1177         List<RoadMarkerAlong> roadMarkers = new ArrayList<>();
1178         String[] nameStrings = elements.split("(\\-)|(\\|)|(\\:)|(\\<)|(\\>)|(\\#)");
1179         List<String> names = new ArrayList<>();
1180         for (String s : nameStrings)
1181         {
1182             if (s.length() > 0) // to take out potential empty strings at the start and end.
1183             {
1184                 names.add(s);
1185             }
1186         }
1187         List<Double> widthsSI = new ArrayList<>();
1188         int designIndex = -1;
1189         int i = -1;
1190         for (String name : names)
1191         {
1192             i++;
1193             if (name.equals("D")) // TODO design line in the middle of a lane (AD, XD, VD, SD)
1194             {
1195                 widthsSI.add(0.0);
1196                 designIndex = i;
1197             }
1198             else
1199             {
1200                 LaneTag laneTag = linkTag.laneTags.get(name);
1201                 if (laneTag == null)
1202                 {
1203                     laneTag = new LaneTag();
1204                     laneTag.name = name;
1205                     linkTag.laneTags.put(laneTag.name, laneTag);
1206                 }
1207                 if (laneTag.width != null)
1208                 {
1209                     widthsSI.add(laneTag.width.getSI());
1210                 }
1211                 else if (linkTag.width != null)
1212                 {
1213                     widthsSI.add(linkTag.width.getSI());
1214                 }
1215                 else if (globalTag != null && globalTag.width != null)
1216                 {
1217                     widthsSI.add(globalTag.width.getSI());
1218                 }
1219                 else
1220                 {
1221                     throw new SAXException("width not set for lane type in " + elements + ": " + name.charAt(0));
1222                 }
1223             }
1224         }
1225 
1226         // TODO tapered and design line offset changes.
1227         double[] offsetSI = new double[widthsSI.size()];
1228         double cumSI = 0.0;
1229         for (int j = designIndex; j >= 0; j--)
1230         {
1231             offsetSI[j] = cumSI;
1232             cumSI = cumSI - widthsSI.get(j) / 2.0 - ((j > 0) ? widthsSI.get(j - 1) / 2.0 : 0.0);
1233         }
1234         cumSI = 0.0;
1235         for (int j = designIndex; j < widthsSI.size(); j++)
1236         {
1237             offsetSI[j] = cumSI;
1238             cumSI = cumSI + widthsSI.get(j) / 2.0 + ((j < widthsSI.size() - 1) ? widthsSI.get(j + 1) / 2.0 : 0.0);
1239         }
1240 
1241         i = -1;
1242         for (String name : names)
1243         {
1244             if (name.length() > 0)
1245             {
1246                 i++;
1247                 LongitudinalDirectionality ld = null;
1248                 if (name.startsWith("A")) // lane going in the design direction
1249                 {
1250                     ld = LongitudinalDirectionality.FORWARD;
1251                 }
1252                 else if (name.startsWith("V")) // lane going in the opposite direction
1253                 {
1254                     ld = LongitudinalDirectionality.BACKWARD;
1255                 }
1256                 else if (name.startsWith("B")) // lane going in both directions
1257                 {
1258                     ld = LongitudinalDirectionality.BOTH;
1259                 }
1260                 else if (name.startsWith("X")) // forbidden lane (e.g., emergency lane)
1261                 {
1262                     ld = LongitudinalDirectionality.NONE;
1263                 }
1264                 else if (name.startsWith("S")) // forbidden lane (e.g., grass)
1265                 {
1266                     ld = LongitudinalDirectionality.NONE;
1267                 }
1268                 else if (name.equals("D")) // design line
1269                 {
1270                     ld = LongitudinalDirectionality.NONE;
1271                 }
1272                 else
1273                 {
1274                     throw new SAXException("unknown lane type in " + elements + ": " + name.charAt(0));
1275                 }
1276 
1277                 try
1278                 {
1279                     if (ld.equals(LongitudinalDirectionality.NONE))
1280                     {
1281                         if (name.startsWith("S"))
1282                         {
1283                             Shoulder shoulder =
1284                                 new Shoulder(csl, new DoubleScalar.Rel<LengthUnit>(offsetSI[i], LengthUnit.SI),
1285                                     new DoubleScalar.Rel<LengthUnit>(widthsSI.get(i), LengthUnit.SI),
1286                                     new DoubleScalar.Rel<LengthUnit>(widthsSI.get(i), LengthUnit.SI));
1287                             linkTag.laneTags.get(name).cse = shoulder;
1288                             cseList.add(shoulder);
1289                             if (this.simulator != null)
1290                             {
1291                                 new ShoulderAnimation(shoulder, this.simulator);
1292                             }
1293                         }
1294                         else if (name.startsWith("X"))
1295                         {
1296                             Lane lane =
1297                                 new NoTrafficLane(csl, new DoubleScalar.Rel<LengthUnit>(offsetSI[i], LengthUnit.SI),
1298                                     new DoubleScalar.Rel<LengthUnit>(offsetSI[i], LengthUnit.SI),
1299                                     new DoubleScalar.Rel<LengthUnit>(widthsSI.get(i), LengthUnit.SI),
1300                                     new DoubleScalar.Rel<LengthUnit>(widthsSI.get(i), LengthUnit.SI), this.laneType, ld,
1301                                     new DoubleScalar.Abs<FrequencyUnit>(0.0, FrequencyUnit.PER_HOUR));
1302                             linkTag.laneTags.get(name).cse = lane;
1303                             cseList.add(lane);
1304                             if (this.simulator != null)
1305                             {
1306                                 new LaneAnimation(lane, this.simulator, Color.LIGHT_GRAY);
1307                             }
1308                         }
1309                     }
1310                     else
1311                     {
1312                         Lane lane =
1313                             new Lane(csl, new DoubleScalar.Rel<LengthUnit>(offsetSI[i], LengthUnit.SI),
1314                                 new DoubleScalar.Rel<LengthUnit>(offsetSI[i], LengthUnit.SI),
1315                                 new DoubleScalar.Rel<LengthUnit>(widthsSI.get(i), LengthUnit.SI),
1316                                 new DoubleScalar.Rel<LengthUnit>(widthsSI.get(i), LengthUnit.SI), this.laneType, ld,
1317                                 new DoubleScalar.Abs<FrequencyUnit>(Double.MAX_VALUE, FrequencyUnit.PER_HOUR));
1318                         linkTag.laneTags.get(name).cse = lane;
1319                         cseList.add(lane);
1320                         if (this.simulator != null)
1321                         {
1322                             new LaneAnimation(lane, this.simulator, Color.GRAY);
1323                         }
1324                     }
1325                 }
1326                 catch (NetworkException ne)
1327                 {
1328                     throw new SAXException(ne);
1329                 }
1330             }
1331         }
1332         return cseList;
1333     }
1334 
1335     /**
1336      * @param typeName the name of the GTU type.
1337      * @return the GTUType that was retrieved or created.
1338      */
1339     protected final GTUType<String> parseGTUType(final String typeName)
1340     {
1341         if (!this.gtuTypes.containsKey(typeName))
1342         {
1343             GTUType<String> gtuType = new GTUType<>(typeName);
1344             this.gtuTypes.put(typeName, gtuType);
1345         }
1346         return this.gtuTypes.get(typeName);
1347     }
1348 
1349     /**
1350      * XXX probably ok to generate a new model for each GTU 'type'.
1351      * @param modelName the name of the GTU following model.
1352      * @return the model.
1353      * @throws NetworkException in case of unknown model.
1354      */
1355     protected final GTUFollowingModel parseFollowingModel(final String modelName) throws NetworkException
1356     {
1357         if (modelName.equals("IDM"))
1358         {
1359             return new IDM();
1360         }
1361         else if (modelName.equals("IDM+"))
1362         {
1363             return new IDMPlus();
1364         }
1365         throw new NetworkException("Unknown GTU following model: " + modelName);
1366     }
1367 
1368     /**
1369      * XXX probably ok to generate a new model for each GTU 'type'.
1370      * @param modelName the name of the lane change model.
1371      * @return the model.
1372      * @throws NetworkException in case of unknown model.
1373      */
1374     protected final LaneChangeModel parseLaneChangeModel(final String modelName) throws NetworkException
1375     {
1376         if (modelName.equals("EGOISTIC"))
1377         {
1378             return new Egoistic();
1379         }
1380         else if (modelName.equals("ALTRUISTIC"))
1381         {
1382             return new Altruistic();
1383         }
1384         throw new NetworkException("Unknown lane change model: " + modelName);
1385     }
1386 
1387     /**
1388      * Parse a coordinate with (x,y) or (x,y,z).
1389      * @param cs the string containing the coordinate.
1390      * @return a Point3d contaiing the x,y or x,y,z values.
1391      */
1392     protected final Point3d parseCoordinate(final String cs)
1393     {
1394         String c = cs.replace("(", "");
1395         c = c.replace(")", "");
1396         String[] cc = c.split(",");
1397         double x = Double.parseDouble(cc[0]);
1398         double y = Double.parseDouble(cc[1]);
1399         double z = cc.length > 2 ? Double.parseDouble(cc[1]) : 0.0;
1400         return new Point3d(x, y, z);
1401     }
1402 
1403     /**
1404      * @param s the string to parse
1405      * @return the unit as a String in the Map.
1406      * @throws NetworkException when parsing fails
1407      */
1408     private String parseSpeedUnit(final String s) throws NetworkException
1409     {
1410         String u = null;
1411         for (String us : SPEED_UNITS.keySet())
1412         {
1413             if (s.toString().contains(us))
1414             {
1415                 u = us;
1416             }
1417         }
1418         if (u == null)
1419         {
1420             throw new NetworkException("Parsing network: cannot instantiate speed unit in: " + s);
1421         }
1422         return u;
1423     }
1424 
1425     /**
1426      * @param s the string to parse
1427      * @return the next value.
1428      * @throws NetworkException when parsing fails
1429      */
1430     protected final DoubleScalar.Abs<SpeedUnit> parseSpeedAbs(final String s) throws NetworkException
1431     {
1432         String us = parseSpeedUnit(s);
1433         SpeedUnit u = SPEED_UNITS.get(us);
1434         String sv = s.substring(0, s.indexOf(us));
1435         try
1436         {
1437             double value = Double.parseDouble(sv);
1438             return new DoubleScalar.Abs<SpeedUnit>(value, u);
1439         }
1440         catch (NumberFormatException nfe)
1441         {
1442             throw new NetworkException("Parsing network: cannot instantiate scalar: " + s, nfe);
1443         }
1444     }
1445 
1446     /**
1447      * @param s the string to parse
1448      * @return the next value.
1449      * @throws NetworkException when parsing fails
1450      */
1451     protected final DoubleScalar.Rel<SpeedUnit> parseSpeedRel(final String s) throws NetworkException
1452     {
1453         String us = parseSpeedUnit(s);
1454         SpeedUnit u = SPEED_UNITS.get(us);
1455         String sv = s.substring(0, s.indexOf(us));
1456         try
1457         {
1458             double value = Double.parseDouble(sv);
1459             return new DoubleScalar.Rel<SpeedUnit>(value, u);
1460         }
1461         catch (NumberFormatException nfe)
1462         {
1463             throw new NetworkException("Parsing network: cannot instantiate scalar: " + s, nfe);
1464         }
1465     }
1466 
1467     /**
1468      * @param s the string to parse
1469      * @return the unit as a String in the Map.
1470      * @throws NetworkException when parsing fails
1471      */
1472     private String parseLengthUnit(final String s) throws NetworkException
1473     {
1474         String u = null;
1475         for (String us : LENGTH_UNITS.keySet())
1476         {
1477             if (s.toString().contains(us))
1478             {
1479                 u = us;
1480             }
1481         }
1482         if (u == null)
1483         {
1484             throw new NetworkException("Parsing network: cannot instantiate length unit in: " + s);
1485         }
1486         return u;
1487     }
1488 
1489     /**
1490      * @param s the string to parse
1491      * @return the next value.
1492      * @throws NetworkException when parsing fails
1493      */
1494     protected final DoubleScalar.Abs<LengthUnit> parseLengthAbs(final String s) throws NetworkException
1495     {
1496         String us = parseLengthUnit(s);
1497         LengthUnit u = LENGTH_UNITS.get(us);
1498         String sv = s.substring(0, s.indexOf(us));
1499         try
1500         {
1501             double value = Double.parseDouble(sv);
1502             return new DoubleScalar.Abs<LengthUnit>(value, u);
1503         }
1504         catch (NumberFormatException nfe)
1505         {
1506             throw new NetworkException("Parsing network: cannot instantiate scalar: " + s, nfe);
1507         }
1508     }
1509 
1510     /**
1511      * @param s the string to parse
1512      * @return the next value.
1513      * @throws NetworkException when parsing fails
1514      */
1515     protected final DoubleScalar.Rel<LengthUnit> parseLengthRel(final String s) throws NetworkException
1516     {
1517         String us = parseLengthUnit(s);
1518         LengthUnit u = LENGTH_UNITS.get(us);
1519         String sv = s.substring(0, s.indexOf(us));
1520         try
1521         {
1522             double value = Double.parseDouble(sv);
1523             return new DoubleScalar.Rel<LengthUnit>(value, u);
1524         }
1525         catch (NumberFormatException nfe)
1526         {
1527             throw new NetworkException("Parsing network: cannot instantiate scalar: " + s, nfe);
1528         }
1529     }
1530 
1531     /**
1532      * @param s the string to parse
1533      * @return the unit as a String in the Map.
1534      * @throws NetworkException when parsing fails
1535      */
1536     private String parseTimeUnit(final String s) throws NetworkException
1537     {
1538         String u = null;
1539         for (String us : TIME_UNITS.keySet())
1540         {
1541             if (s.toString().contains(us))
1542             {
1543                 u = us;
1544             }
1545         }
1546         if (u == null)
1547         {
1548             throw new NetworkException("Parsing network: cannot instantiate time unit in: " + s);
1549         }
1550         return u;
1551     }
1552 
1553     /**
1554      * @param s the string to parse
1555      * @return the next value.
1556      * @throws NetworkException when parsing fails
1557      */
1558     protected final DoubleScalar.Abs<TimeUnit> parseTimeAbs(final String s) throws NetworkException
1559     {
1560         String us = parseTimeUnit(s);
1561         TimeUnit u = TIME_UNITS.get(us);
1562         String sv = s.substring(0, s.indexOf(us));
1563         try
1564         {
1565             double value = Double.parseDouble(sv);
1566             return new DoubleScalar.Abs<TimeUnit>(value, u);
1567         }
1568         catch (NumberFormatException nfe)
1569         {
1570             throw new NetworkException("Parsing network: cannot instantiate scalar: " + s, nfe);
1571         }
1572     }
1573 
1574     /**
1575      * @param s the string to parse
1576      * @return the next value.
1577      * @throws NetworkException when parsing fails
1578      */
1579     protected final DoubleScalar.Rel<TimeUnit> parseTimeRel(final String s) throws NetworkException
1580     {
1581         String us = parseTimeUnit(s);
1582         TimeUnit u = TIME_UNITS.get(us);
1583         String sv = s.substring(0, s.indexOf(us));
1584         try
1585         {
1586             double value = Double.parseDouble(sv);
1587             return new DoubleScalar.Rel<TimeUnit>(value, u);
1588         }
1589         catch (NumberFormatException nfe)
1590         {
1591             throw new NetworkException("Parsing network: cannot instantiate scalar: " + s, nfe);
1592         }
1593     }
1594 
1595     /**
1596      * parse a set of comma-separated values, e.g., <code>10.0, 4, 5.23</code>.
1597      * @param s the string to parse.
1598      * @return array of double values.
1599      */
1600     private double[] parseDoubleArgs(final String s)
1601     {
1602         String[] ss = s.split(",");
1603         double[] d = new double[ss.length];
1604         for (int i = 0; i < ss.length; i++)
1605         {
1606             d[i] = Double.parseDouble(ss[i]);
1607         }
1608         return d;
1609     }
1610 
1611     /** TODO include in GLOBAL tag. */
1612     private static final StreamInterface STREAM = new MersenneTwister();
1613 
1614     /**
1615      * Parse a continuous distribution.
1616      * @param ds the name of the distribution, e.g. UNIF.
1617      * @param args the parameters of the distribution, e.g. {1.0, 2.0}.
1618      * @return the generated distribution.
1619      * @throws NetworkException in case distribution unknown or parameter number does not match.
1620      */
1621     private DistContinuous makeDistContinuous(final String ds, final double[] args) throws NetworkException
1622     {
1623         try
1624         {
1625             switch (ds)
1626             {
1627                 case "CONST":
1628                 case "CONSTANT":
1629                     return new DistConstant(STREAM, args[0]);
1630 
1631                 case "EXPO":
1632                 case "EXPONENTIAL":
1633                     return new DistExponential(STREAM, args[0]);
1634 
1635                 case "TRIA":
1636                 case "TRIANGULAR":
1637                     return new DistTriangular(STREAM, args[0], args[1], args[2]);
1638 
1639                 case "NORM":
1640                 case "NORMAL":
1641                     return new DistNormal(STREAM, args[0], args[1]);
1642 
1643                 case "BETA":
1644                     return new DistBeta(STREAM, args[0], args[1]);
1645 
1646                 case "ERLANG":
1647                     return new DistErlang(STREAM, (int) args[0], args[1]);
1648 
1649                 case "GAMMA":
1650                     return new DistGamma(STREAM, args[0], args[1]);
1651 
1652                 case "LOGN":
1653                 case "LOGNORMAL":
1654                     return new DistLogNormal(STREAM, args[0], args[1]);
1655 
1656                 case "PEARSON5":
1657                     return new DistPearson5(STREAM, args[0], args[1]);
1658 
1659                 case "PEARSON6":
1660                     return new DistPearson6(STREAM, args[0], args[1], args[2]);
1661 
1662                 case "UNIF":
1663                 case "UNIFORM":
1664                     return new DistUniform(STREAM, args[0], args[1]);
1665 
1666                 case "WEIB":
1667                 case "WEIBULL":
1668                     return new DistWeibull(STREAM, args[0], args[1]);
1669 
1670                 default:
1671                     throw new NetworkException("makeDistContinuous - unknown distribution function " + ds);
1672             }
1673         }
1674         catch (IndexOutOfBoundsException e)
1675         {
1676             throw new NetworkException("makeDistContinuous - wrong number of parameters for distribution function " + ds);
1677         }
1678     }
1679 
1680     /**
1681      * Parse a relative length distribution, e.g. <code>UNIFORM(1, 3) m</code>.
1682      * @param s the string to be parsed.
1683      * @return a typed continuous random distribution.
1684      * @throws NetworkException in case of a parse error.
1685      */
1686     protected final DistContinuousDoubleScalar.Rel<LengthUnit> parseLengthDistRel(final String s) throws NetworkException
1687     {
1688         String[] s1 = s.split("\\(");
1689         String ds = s1[0];
1690         String[] s2 = s1[1].split("\\)");
1691         String unit = parseLengthUnit(s2[1]);
1692         double[] args = parseDoubleArgs(s2[0]);
1693         DistContinuous dist = makeDistContinuous(ds, args);
1694         return new DistContinuousDoubleScalar.Rel<LengthUnit>(dist, LENGTH_UNITS.get(unit));
1695     }
1696 
1697     /**
1698      * Parse an absolute length distribution, e.g. <code>UNIFORM(1, 3) m</code>.
1699      * @param s the string to be parsed.
1700      * @return a typed continuous random distribution.
1701      * @throws NetworkException in case of a parse error.
1702      */
1703     protected final DistContinuousDoubleScalar.Abs<LengthUnit> parseLengthDistAbs(final String s) throws NetworkException
1704     {
1705         String[] s1 = s.split("\\(");
1706         String ds = s1[0];
1707         String[] s2 = s1[1].split("\\)");
1708         String unit = parseLengthUnit(s2[1]);
1709         double[] args = parseDoubleArgs(s2[0]);
1710         DistContinuous dist = makeDistContinuous(ds, args);
1711         return new DistContinuousDoubleScalar.Abs<LengthUnit>(dist, LENGTH_UNITS.get(unit));
1712     }
1713 
1714     /**
1715      * Parse a relative time distribution, e.g. <code>UNIFORM(1, 3) s</code>.
1716      * @param s the string to be parsed.
1717      * @return a typed continuous random distribution.
1718      * @throws NetworkException in case of a parse error.
1719      */
1720     protected final DistContinuousDoubleScalar.Rel<TimeUnit> parseTimeDistRel(final String s) throws NetworkException
1721     {
1722         String[] s1 = s.split("\\(");
1723         String ds = s1[0];
1724         String[] s2 = s1[1].split("\\)");
1725         String unit = parseTimeUnit(s2[1]);
1726         double[] args = parseDoubleArgs(s2[0]);
1727         DistContinuous dist = makeDistContinuous(ds, args);
1728         return new DistContinuousDoubleScalar.Rel<TimeUnit>(dist, TIME_UNITS.get(unit));
1729     }
1730 
1731     /**
1732      * Parse an absolute time distribution, e.g. <code>UNIFORM(1, 3) s</code>.
1733      * @param s the string to be parsed.
1734      * @return a typed continuous random distribution.
1735      * @throws NetworkException in case of a parse error.
1736      */
1737     protected final DistContinuousDoubleScalar.Abs<TimeUnit> parseTimeDistAbs(final String s) throws NetworkException
1738     {
1739         String[] s1 = s.split("\\(");
1740         String ds = s1[0];
1741         String[] s2 = s1[1].split("\\)");
1742         String unit = parseTimeUnit(s2[1]);
1743         double[] args = parseDoubleArgs(s2[0]);
1744         DistContinuous dist = makeDistContinuous(ds, args);
1745         return new DistContinuousDoubleScalar.Abs<TimeUnit>(dist, TIME_UNITS.get(unit));
1746     }
1747 
1748     /**
1749      * Parse a relative speed distribution, e.g. <code>TRIANGULAR(80, 90, 110) km/h</code>.
1750      * @param s the string to be parsed.
1751      * @return a typed continuous random distribution.
1752      * @throws NetworkException in case of a parse error.
1753      */
1754     protected final DistContinuousDoubleScalar.Rel<SpeedUnit> parseSpeedDistRel(final String s) throws NetworkException
1755     {
1756         String[] s1 = s.split("\\(");
1757         String ds = s1[0];
1758         String[] s2 = s1[1].split("\\)");
1759         String unit = parseSpeedUnit(s2[1]);
1760         double[] args = parseDoubleArgs(s2[0]);
1761         DistContinuous dist = makeDistContinuous(ds, args);
1762         return new DistContinuousDoubleScalar.Rel<SpeedUnit>(dist, SPEED_UNITS.get(unit));
1763     }
1764 
1765     /**
1766      * Parse an absolute speed distribution, e.g. <code>TRIANGULAR(80, 90, 110) km/h</code>.
1767      * @param s the string to be parsed.
1768      * @return a typed continuous random distribution.
1769      * @throws NetworkException in case of a parse error.
1770      */
1771     protected final DistContinuousDoubleScalar.Abs<SpeedUnit> parseSpeedDistAbs(final String s) throws NetworkException
1772     {
1773         String[] s1 = s.split("\\(");
1774         String ds = s1[0];
1775         String[] s2 = s1[1].split("\\)");
1776         String unit = parseSpeedUnit(s2[1]);
1777         double[] args = parseDoubleArgs(s2[0]);
1778         DistContinuous dist = makeDistContinuous(ds, args);
1779         return new DistContinuousDoubleScalar.Abs<SpeedUnit>(dist, SPEED_UNITS.get(unit));
1780     }
1781 
1782     /*************************************************************************************************/
1783     /****************************** TAG CLASSES TO KEEP THE XML INFORMATION **************************/
1784     /*************************************************************************************************/
1785 
1786     /** GLOBAL element. */
1787     @SuppressWarnings("checkstyle:visibilitymodifier")
1788     protected class GlobalTag
1789     {
1790         /** default speed. */
1791         protected DoubleScalar.Abs<SpeedUnit> speed = null;
1792 
1793         /** default lane width. */
1794         protected DoubleScalar.Rel<LengthUnit> width = null;
1795     }
1796 
1797     /** LINK element. */
1798     @SuppressWarnings("checkstyle:visibilitymodifier")
1799     protected class LinkTag
1800     {
1801         /** name. */
1802         protected String name;
1803 
1804         /** default speed. */
1805         protected DoubleScalar.Abs<SpeedUnit> speed = null;
1806 
1807         /** default lane width on this link. */
1808         protected DoubleScalar.Rel<LengthUnit> width = null;
1809 
1810         /** from node. */
1811         @SuppressWarnings("rawtypes")
1812         protected Node nodeFrom = null;
1813 
1814         /** to node. */
1815         @SuppressWarnings("rawtypes")
1816         protected Node nodeTo = null;
1817 
1818         /** from node name. */
1819         protected String nodeFromName = null;
1820 
1821         /** to node name. */
1822         protected String nodeToName = null;
1823 
1824         /** elements. */
1825         protected String elements = null;
1826 
1827         /** lane info. */
1828         protected Map<String, LaneTag> laneTags = new HashMap<>();
1829 
1830         /** straight. */
1831         protected StraightTag straightTag = null;
1832 
1833         /** arc. */
1834         protected ArcTag arcTag = null;
1835     }
1836 
1837     /** LANE element. */
1838     @SuppressWarnings("checkstyle:visibilitymodifier")
1839     protected class LaneTag
1840     {
1841         /** name. */
1842         protected String name;
1843 
1844         /** lane speed. */
1845         protected DoubleScalar.Abs<SpeedUnit> speed = null;
1846 
1847         /** lane width. */
1848         protected DoubleScalar.Rel<LengthUnit> width = null;
1849 
1850         /** generators. */
1851         protected Set<GeneratorTag> generatorTags = new HashSet<>();
1852 
1853         /** fill at t=0. */
1854         protected Set<FillTag> fillTags = new HashSet<>();
1855 
1856         /** the lane that was created. */
1857         protected CrossSectionElement cse = null;
1858     }
1859 
1860     /** ARC element. */
1861     @SuppressWarnings("checkstyle:visibilitymodifier")
1862     protected class ArcTag
1863     {
1864         /** angle. */
1865         protected DoubleScalar.Abs<AnglePlaneUnit> angle = null;
1866 
1867         /** radius. */
1868         protected DoubleScalar.Rel<LengthUnit> radius = null;
1869 
1870         /** direction. */
1871         protected ArcDirection direction = null;
1872 
1873         /** the center coordinate of the arc. Will be filled after parsing. */
1874         protected Point3d center;
1875 
1876         /** the startAngle in radians compared to the center coordinate. Will be filled after parsing. */
1877         protected double startAngle;
1878     }
1879 
1880     /** STRAIGHT element. */
1881     @SuppressWarnings("checkstyle:visibilitymodifier")
1882     protected class StraightTag
1883     {
1884         /** length. */
1885         protected DoubleScalar.Rel<LengthUnit> length = null;
1886     }
1887 
1888     /** NODE element. */
1889     @SuppressWarnings("checkstyle:visibilitymodifier")
1890     protected class NodeTag
1891     {
1892         /** name. */
1893         String name = null;
1894 
1895         /** coordinate (null at first, can be calculated later when connected to a link. */
1896         Point3d coordinate = null;
1897 
1898         /** absolute angle of the node. 0 is "East", pi/2 = "North". */
1899         DoubleScalar.Abs<AnglePlaneUnit> angle = null;
1900 
1901         /** slope as an angle. */
1902         DoubleScalar.Abs<AngleSlopeUnit> slope = null;
1903     }
1904 
1905     /** direction of the arc; LEFT or RIGHT. */
1906     protected enum ArcDirection
1907     {
1908         /** Left = counter-clockwise. */
1909         LEFT,
1910         /** Right = clockwise. */
1911         RIGHT;
1912     }
1913 
1914     /** GTU element. */
1915     @SuppressWarnings("checkstyle:visibilitymodifier")
1916     protected class GTUTag
1917     {
1918         /** name. */
1919         protected String name;
1920 
1921         /** type. */
1922         protected GTUType<String> gtuType = null;
1923 
1924         /** GTU length. */
1925         protected DistContinuousDoubleScalar.Rel<LengthUnit> lengthDist = null;
1926 
1927         /** GTU width. */
1928         protected DistContinuousDoubleScalar.Rel<LengthUnit> widthDist = null;
1929 
1930         /** GTU following model. */
1931         protected GTUFollowingModel followingModel = null;
1932 
1933         /** lane change model. */
1934         protected LaneChangeModel laneChangeModel = null;
1935 
1936         /** max speed. */
1937         protected DistContinuousDoubleScalar.Abs<SpeedUnit> maxSpeedDist = null;
1938     }
1939 
1940     /** Generator element. */
1941     @SuppressWarnings("checkstyle:visibilitymodifier")
1942     protected class GeneratorTag
1943     {
1944         /** lane. */
1945         protected LaneTag laneTag = null;
1946 
1947         /** GTU tag. */
1948         protected GTUTag gtuTag = null;
1949 
1950         /** interarrival time. */
1951         protected DistContinuousDoubleScalar.Rel<TimeUnit> iatDist = null;
1952 
1953         /** initial speed. */
1954         protected DistContinuousDoubleScalar.Abs<SpeedUnit> initialSpeedDist = null;
1955 
1956         /** max number of generated GTUs. */
1957         protected int maxGTUs = Integer.MAX_VALUE;
1958 
1959         /** start time of generation. */
1960         protected DoubleScalar.Abs<TimeUnit> startTime = null;
1961 
1962         /** end time of generation. */
1963         protected DoubleScalar.Abs<TimeUnit> endTime = null;
1964     }
1965 
1966     /** Fill element. */
1967     @SuppressWarnings("checkstyle:visibilitymodifier")
1968     protected class FillTag
1969     {
1970         /** lane. */
1971         protected LaneTag laneTag = null;
1972 
1973         /** GTU tag. */
1974         protected GTUTag gtuTag = null;
1975 
1976         /** inter-vehicle distance. */
1977         protected DistContinuousDoubleScalar.Rel<LengthUnit> distanceDist = null;
1978 
1979         /** initial speed. */
1980         protected DistContinuousDoubleScalar.Abs<SpeedUnit> initialSpeedDist = null;
1981 
1982         /** max number of generated GTUs. */
1983         protected int maxGTUs = Integer.MAX_VALUE;
1984     }
1985 
1986     /**
1987      * Test.
1988      * @param args none.
1989      * @throws NetworkException
1990      * @throws IOException
1991      * @throws SAXException
1992      * @throws ParserConfigurationException
1993      */
1994     public static void main(final String[] args) throws NetworkException, ParserConfigurationException, SAXException,
1995         IOException
1996     {
1997         URL url = URLResource.getResource("/ots-infra-example.xml");
1998         XmlNetworkLaneParser nlp =
1999             new XmlNetworkLaneParser(String.class, NodeGeotools.class, String.class, Coordinate.class, LinkGeotools.class,
2000                 String.class, null);
2001         Network n = nlp.build(url.openStream());
2002     }
2003 }