View Javadoc
1   package org.opentrafficsim.road.network.factory.vissim;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.HashMap;
6   import java.util.Iterator;
7   import java.util.List;
8   import java.util.Map;
9   
10  import javax.naming.NamingException;
11  
12  import org.djunits.unit.LengthUnit;
13  import org.djunits.value.vdouble.scalar.Angle;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.opentrafficsim.core.geometry.OTSGeometryException;
16  import org.opentrafficsim.core.geometry.OTSLine3D;
17  import org.opentrafficsim.core.geometry.OTSPoint3D;
18  import org.opentrafficsim.core.network.NetworkException;
19  import org.opentrafficsim.core.network.factory.xml.units.Coordinates;
20  import org.opentrafficsim.core.network.factory.xml.units.LengthUnits;
21  import org.opentrafficsim.road.network.lane.CrossSectionElement;
22  import org.opentrafficsim.road.network.lane.CrossSectionLink;
23  import org.opentrafficsim.road.network.lane.Lane;
24  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
25  import org.w3c.dom.NamedNodeMap;
26  import org.w3c.dom.Node;
27  import org.w3c.dom.NodeList;
28  import org.xml.sax.SAXException;
29  
30  import com.vividsolutions.jts.geom.Coordinate;
31  import com.vividsolutions.jts.geom.LineString;
32  
33  /**
34   * <p>
35   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
36   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
37   * <p>
38   * $LastChangedDate: 2017-04-29 13:29:16 +0200 (Sat, 29 Apr 2017) $, @version $Revision: 3573 $, by $Author: averbraeck $,
39   * initial version Jul 23, 2015 <br>
40   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
41   */
42  final class LinkTag implements Serializable
43  {
44      /** */
45      private static final long serialVersionUID = 20150723L;
46  
47      /** Name. */
48      @SuppressWarnings("checkstyle:visibilitymodifier")
49      String name = null;
50  
51      /** Name. */
52      @SuppressWarnings("checkstyle:visibilitymodifier")
53      String legalSpeed = null;
54  
55      /** From node tag. */
56      @SuppressWarnings("checkstyle:visibilitymodifier")
57      NodeTag nodeStartTag = null;
58  
59      /** To node tag. */
60      @SuppressWarnings("checkstyle:visibilitymodifier")
61      NodeTag nodeEndTag = null;
62  
63      /** Offset for the link at the start node. */
64      @SuppressWarnings("checkstyle:visibilitymodifier")
65      Length offsetStart = null;
66  
67      /** Offset for the link at the end node. */
68      @SuppressWarnings("checkstyle:visibilitymodifier")
69      Length offsetEnd = null;
70  
71      /** Extra rotation for the link at the start node. */
72      @SuppressWarnings("checkstyle:visibilitymodifier")
73      Angle rotationStart = null;
74  
75      /** Extra rotation for the link at the end node. */
76      @SuppressWarnings("checkstyle:visibilitymodifier")
77      Angle rotationEnd = null;
78  
79      /** Straight. */
80      @SuppressWarnings("checkstyle:visibilitymodifier")
81      StraightTag straightTag = null;
82  
83      //
84      /** PolyLine. */
85      @SuppressWarnings("checkstyle:visibilitymodifier")
86      PolyLineTag polyLineTag = null;
87  
88      /** Map of lane name to lane override. */
89      @SuppressWarnings("checkstyle:visibilitymodifier")
90      Map<String, LaneOverrideTag> laneOverrideTags = new HashMap<>();
91  
92      /** Map of lane name to generators. */
93      @SuppressWarnings("checkstyle:visibilitymodifier")
94      Map<String, GeneratorTag> generatorTags = new HashMap<>();
95  
96      /** Map of lane name to list generators. */
97      @SuppressWarnings("checkstyle:visibilitymodifier")
98      Map<String, ListGeneratorTag> listGeneratorTags = new HashMap<>();
99  
100     /** Map of lane name to list of sensors. */
101     @SuppressWarnings("checkstyle:visibilitymodifier")
102     Map<String, List<SensorTag>> sensorTags = new HashMap<>();
103 
104     /** Map of lane name to fill at t=0. */
105     @SuppressWarnings("checkstyle:visibilitymodifier")
106     Map<String, FillTag> fillTags = new HashMap<>();
107 
108     /** Map of lane name to sink tags. */
109     @SuppressWarnings("checkstyle:visibilitymodifier")
110     Map<String, SinkTag> sinkTags = new HashMap<>();
111 
112     /** Map of lane name to generated lanes. */
113     @SuppressWarnings("checkstyle:visibilitymodifier")
114     Map<String, Lane> lanes = new HashMap<>();
115 
116     /** Map of lane name to generated lanes. */
117     @SuppressWarnings("checkstyle:visibilitymodifier")
118     Map<String, LaneTag> laneTags = new HashMap<>();
119 
120     ConnectorTag connectorTag = null;
121 
122     /** The calculated Link. */
123     @SuppressWarnings("checkstyle:visibilitymodifier")
124     CrossSectionLink link = null;
125 
126     /** The lane keeping policy, i.e., keep left, keep right or keep lane. */
127     @SuppressWarnings("checkstyle:visibilitymodifier")
128     LaneKeepingPolicy laneKeepingPolicy = null;
129 
130     List<SignalHeadTag> signalHeads = new ArrayList<>();
131 
132     List<SensorTag> sensors = new ArrayList<>();
133 
134     List<SignalHeadTag> signalHeadsToRemove = new ArrayList<>();
135 
136     List<SensorTag> sensorTagsToRemove = new ArrayList<>();
137 
138     // a link (false) or a connector (true)
139     boolean connector = false;
140 
141     ArcTag arcTag;
142 
143     BezierTag bezierTag;
144 
145     public RoadLayoutTag roadLayoutTag;
146 
147     /**
148      * @param linkTag LinkTag; link Info from XML
149      */
150     public LinkTag(LinkTag linkTag)
151     {
152         this.connectorTag = linkTag.connectorTag;
153         this.connector = linkTag.connector;
154         this.laneKeepingPolicy = linkTag.laneKeepingPolicy;
155         this.lanes.putAll(linkTag.lanes);
156         this.laneTags.putAll(linkTag.laneTags);
157         this.legalSpeed = linkTag.legalSpeed;
158         if (linkTag.straightTag != null)
159         {
160             this.straightTag = new StraightTag(linkTag.straightTag);
161         }
162         if (linkTag.polyLineTag != null)
163         {
164             this.polyLineTag = new PolyLineTag(linkTag.polyLineTag);
165         }
166         this.nodeEndTag = linkTag.nodeEndTag;
167         this.nodeStartTag = linkTag.nodeStartTag;
168     }
169 
170     /**
171      *
172      */
173     public LinkTag()
174     {
175     }
176 
177     /**
178      * Parse the LINK tags from Vissim fiels (.inpx) .
179      * @param nodeList nodeList the top-level nodes of the XML-file
180      * @param parser the parser with the lists of information
181      * @throws SAXException when parsing of the tag fails
182      * @throws NetworkException when parsing of the tag fails
183      */
184     @SuppressWarnings("checkstyle:needbraces")
185     static void parseLinks(final NodeList nodeList, final VissimNetworkLaneParser parser) throws SAXException, NetworkException
186     {
187 
188         for (Node linksNode : XMLParser.getNodes(nodeList, "links"))
189         {
190 
191             for (Node node : XMLParser.getNodes(linksNode.getChildNodes(), "link"))
192             {
193                 NamedNodeMap attributes = node.getAttributes();
194 
195                 // make a linkTag with the link attributes
196                 LinkTag linkTag = new LinkTag();
197 
198                 // Lane keepingĀ± currently with this default behaviour
199                 // TODO: differentiate by road type
200                 linkTag.laneKeepingPolicy = LaneKeepingPolicy.KEEP_LANE;
201 
202                 if (attributes.getNamedItem("no") == null)
203                 {
204                     throw new SAXException("LINK: missing attribute: no");
205                 }
206                 linkTag.name = attributes.getNamedItem("no").getNodeValue().trim();
207                 Integer linkNr = Integer.parseInt(linkTag.name);
208                 if (linkNr > parser.getUpperLinkNr())
209                 {
210                     parser.setUpperLinkNr(linkNr);
211                 }
212 
213                 if (attributes.getNamedItem("assumSpeedOncom") == null)
214                 {
215                     throw new SAXException("LINK: missing attribute assumSpeedOncom");
216                 }
217                 linkTag.legalSpeed = attributes.getNamedItem("assumSpeedOncom").getNodeValue().trim();
218 
219                 // parse the geometry (coordinates of the link (nodes/vertices)) and add them to the LinkTag
220                 OTSPoint3D[] nodeCoords = parseLinkGeometry(parser, node, linkTag);
221 
222                 // create a pair of nodes for every link. They will later be corrected (remove duplicate nodes)
223                 createNodesForLink(parser, linkTag, nodeCoords);
224 
225                 // Parse the lanes. They are part of the link description in Vissim files
226                 List<Node> laneNodes = XMLParser.getNodes(node.getChildNodes(), "lanes");
227 
228                 // the lanes are added as an attribute of the LinkTag
229                 createLanes(linkTag, laneNodes);
230 
231                 // additional info from connectors
232                 List<Node> connectorFromNode = XMLParser.getNodes(node.getChildNodes(), "fromLinkEndPt");
233                 if (connectorFromNode.size() > 0)
234                 {
235                     linkTag.connector = true;
236                     createConnectorInfoFrom(linkTag, connectorFromNode);
237                 }
238 
239                 List<Node> connectorToNode = XMLParser.getNodes(node.getChildNodes(), "toLinkEndPt");
240                 if (connectorFromNode.size() > 0)
241                 {
242                     linkTag.connector = true;
243                     createConnectorInfoTo(linkTag, connectorToNode);
244                 }
245 
246                 // put this link in the map with LinkTags
247                 parser.getLinkTags().put(linkTag.name, linkTag);
248             }
249 
250         }
251 
252     }
253 
254     private static void createConnectorInfoTo(LinkTag linkTag, List<Node> connectorToNode) throws SAXException
255     {
256         NamedNodeMap attributes;
257         if (linkTag.connectorTag == null)
258         {
259             linkTag.connectorTag = new ConnectorTag();
260         }
261         attributes = connectorToNode.get(0).getAttributes();
262         if (attributes.getNamedItem("lane") == null)
263         {
264             throw new SAXException("Connector: missing attribute: link/lane info");
265         }
266         String connect = attributes.getNamedItem("lane").getNodeValue().trim();
267         String[] connectInfo = connect.split("\\s+");
268         linkTag.connectorTag.toLinkNo = connectInfo[0];
269         linkTag.connectorTag.toLaneNo = connectInfo[1];
270         if (attributes.getNamedItem("pos") == null)
271         {
272             throw new SAXException("Connector: missing attribute: pos (position info)");
273         }
274         linkTag.connectorTag.toPositionStr = attributes.getNamedItem("pos").getNodeValue().trim();
275     }
276 
277     private static void createConnectorInfoFrom(LinkTag linkTag, List<Node> connectorFromNode) throws SAXException
278     {
279         NamedNodeMap attributes;
280         if (linkTag.connectorTag == null)
281         {
282             linkTag.connectorTag = new ConnectorTag();
283         }
284         attributes = connectorFromNode.get(0).getAttributes();
285         if (attributes.getNamedItem("lane") == null)
286         {
287             throw new SAXException("Connector: missing attribute: link/lane info");
288         }
289         String connect = attributes.getNamedItem("lane").getNodeValue().trim();
290         String[] connectInfo = connect.split("\\s+");
291         linkTag.connectorTag.fromLinkNo = connectInfo[0];
292         linkTag.connectorTag.fromLaneNo = connectInfo[1];
293 
294         if (attributes.getNamedItem("pos") == null)
295         {
296             throw new SAXException("Connector: missing attribute: pos (position info)");
297         }
298         linkTag.connectorTag.fromPositionStr = attributes.getNamedItem("pos").getNodeValue().trim();
299     }
300 
301     private static void createLanes(LinkTag linkTag, List<Node> laneNodes)
302     {
303         NamedNodeMap attributes;
304         int laneNo = 1;
305         for (Node laneNode : XMLParser.getNodes(laneNodes.get(0).getChildNodes(), "lane"))
306         {
307 
308             attributes = laneNode.getAttributes();
309             LaneTag laneTag = new LaneTag();
310             if (attributes.getLength() > 0)
311             {
312                 laneTag.width = attributes.getNamedItem("width").getNodeValue().trim();
313             }
314             else
315             {
316                 laneTag.width = "3.5";
317                 // must be a connector without lane attributes
318                 // the lane width is determined by its predecessor and successor
319             }
320             laneTag.linkNo = linkTag.name;
321             laneTag.laneNo = "" + laneNo;
322             laneNo++;
323             linkTag.laneTags.put(laneTag.laneNo, laneTag);
324         }
325     }
326 
327     private static void createNodesForLink(final VissimNetworkLaneParser parser, LinkTag linkTag, OTSPoint3D[] nodeCoords)
328             throws SAXException, NetworkException
329     {
330         // generate nodes from every Vissim link/connector
331         String fromNodeStr = "" + parser.getUpperNodeNr();
332         parser.setUpperNodeNr(parser.getUpperNodeNr() + 1);
333         String toNodeStr = "" + parser.getUpperNodeNr();
334         parser.setUpperNodeNr(parser.getUpperNodeNr() + 1);
335 
336         // parse the NODES, and add them to a nodelist directly
337         NodeTag.parseNodes(parser, fromNodeStr, toNodeStr, nodeCoords);
338 
339         linkTag.nodeStartTag = parser.getNodeTags().get(fromNodeStr);
340         linkTag.nodeEndTag = parser.getNodeTags().get(toNodeStr);
341     }
342 
343     private static OTSPoint3D[] parseLinkGeometry(final VissimNetworkLaneParser parser, Node node, LinkTag linkTag)
344             throws SAXException, NetworkException
345     {
346         List<Node> geometry = XMLParser.getNodes(node.getChildNodes(), "geometry");
347         List<Node> pointsNodes = XMLParser.getNodes(geometry.get(0).getChildNodes(), "points3D");
348         String coords = "";
349         int numberOfPoints = 0;
350         for (Node pointNode : XMLParser.getNodes(pointsNodes.get(0).getChildNodes(), "point3D"))
351         {
352             NamedNodeMap polyLineAttributes = pointNode.getAttributes();
353             coords += "(" + polyLineAttributes.getNamedItem("x").getNodeValue() + ", "
354                     + polyLineAttributes.getNamedItem("y").getNodeValue() + ")";
355             numberOfPoints++;
356         }
357 
358         OTSPoint3D[] nodeCoords = null;
359         if (numberOfPoints > 2)
360         {
361             // process the intermediate vertices only
362             PolyLineTag.parsePolyLine(coords, parser, linkTag);
363         }
364         else
365         {
366             // parse the STRAIGHT tag
367             StraightTag.parseStraight(coords, parser, linkTag);
368             // add coordinates to the nodes and vertices
369         }
370         // coords of begin and end Node
371         nodeCoords = Coordinates.parseCoordinates(coords);
372         return nodeCoords;
373     }
374 
375     /**
376      * Split the links at a certain point along the link.
377      * @param splitNodeTag NodeTag; Node located at the intersection point of the connector and the link
378      * @param linkTag LinkTag; Tag of the link that meets the connector
379      * @param parser VissimNetworkLaneParser; the VissimParser with info to create a network
380      * @param splitPosition Double; position at the link where the split is expected
381      * @param margin Double; if the splitPosition is at the start or end of the LinkTag, the connector is supposed to be a chain
382      *            and not a split
383      * @param isConnectorToLink boolean; is this is a connector towards a link (true) or starting from a link (false)
384      * @return Mat&lt;String, LinkTab&gt;
385      * @throws OTSGeometryException ...
386      */
387     public static Map<String, LinkTag> splitLink(final NodeTag splitNodeTag, final LinkTag linkTag,
388             final VissimNetworkLaneParser parser, final Double splitPosition, final Double margin,
389             final boolean isConnectorToLink) throws OTSGeometryException
390     {
391 
392         // generate a LineString of the "real" Link
393         OTSLine3D designLineOTS = createLineString(linkTag);
394         LineString designLine = designLineOTS.getLineString();
395 
396         // only split if the splitPosition is not:
397         // (1) at or very near the start of a link
398         // (2) at or very near the end of a link
399         if (splitPosition > margin && splitPosition < designLine.getLength() - margin)
400         {
401             // split the geometry in two parts (cut by the connector)
402             LineString designLineStart = SubstringLine.getSubstring(designLine, 0.0, splitPosition);
403             LineString designLineEnd = SubstringLine.getSubstring(designLine, splitPosition, designLine.getLength());
404 
405             // the linkTag is split in two parts....
406             // first create a copy of the current toLink
407             LinkTag endLinkTag = new LinkTag(linkTag);
408             // add a unique name
409             String linkName = "" + parser.getUpperLinkNr();
410             parser.setUpperLinkNr(parser.getUpperLinkNr() + 1);
411             endLinkTag.name = linkName;
412 
413             // the first link has the same characteristics as the old link, but implements a new endNode (the endNode of the
414             // Connector) and an updated geometry.
415             // the Second link (endLinkTag) copies the characteristics from the old link, but creates a new startNode, endNode
416             // and geometry.
417             createGeometryStartLink(linkTag, designLineStart, splitNodeTag);
418             createGeometryEndLink(endLinkTag, designLineEnd, splitNodeTag);
419 
420             // Furthermore, the signalHeads and sensors are moved to one of the new links
421             // First, relocate the signalheads over the links that are split
422             Iterator<SignalHeadTag> signalHeads = linkTag.signalHeads.iterator();
423             while (signalHeads.hasNext())
424             {
425                 SignalHeadTag signalHeadTag = signalHeads.next();
426                 Double position = Double.parseDouble(signalHeadTag.positionStr);
427                 if (position > splitPosition)
428                 {
429                     // update the position of the signalHead!
430                     Double newPosition = position - splitPosition;
431                     signalHeadTag.positionStr = newPosition.toString();
432                     // add to the newly constructed link
433                     endLinkTag.signalHeads.add(new SignalHeadTag(signalHeadTag));
434                     // remove from the other part of the link
435                     signalHeadTag.activeOnThisLink = false;
436                     linkTag.signalHeadsToRemove.add(signalHeadTag);
437                 }
438             }
439 
440             // relocate the signalheads over the links that are split
441             Iterator<SensorTag> sensors = linkTag.sensors.iterator();
442             while (sensors.hasNext())
443             {
444                 SensorTag sensorTag = sensors.next();
445                 Double position = Double.parseDouble(sensorTag.positionStr);
446                 if (position > splitPosition)
447                 {
448                     // update the position of the Sensor!
449                     Double newPosition = position - splitPosition;
450                     sensorTag.positionStr = newPosition.toString();
451                     // add to the newly constructed link
452                     endLinkTag.sensors.add(new SensorTag(sensorTag));
453                     // remove from the other part of the link
454                     sensorTag.activeOnThisLink = false;
455                     linkTag.sensorTagsToRemove.add(sensorTag);
456                     // sensors.remove();
457                 }
458             }
459 
460             // put this link in the map with LinkTags
461             Map<String, LinkTag> newLinkTags = new HashMap<>();
462             newLinkTags.put(endLinkTag.name, endLinkTag);
463             return newLinkTags;
464 
465         }
466         return null;
467 
468     }
469 
470     /**
471      * Creates the Node info for start and end node and intermediate vertices of the StartLink
472      * @param linkTag
473      * @param designLine
474      * @param nodeTag
475      */
476     private static void createGeometryStartLink(LinkTag linkTag, LineString designLine, NodeTag nodeTag)
477     {
478         Coordinate[] coords = designLine.getCoordinates();
479         OTSPoint3D[] vertices = new OTSPoint3D[coords.length - 2];
480         int i = 0;
481 
482         for (Coordinate coord : coords)
483         {
484             // startNode point
485             if (i == 0)
486             {
487                 linkTag.nodeStartTag.coordinate = new OTSPoint3D(coord);
488             }
489             // endNode point
490             if (i == coords.length - 1)
491             {
492                 nodeTag.coordinate = new OTSPoint3D(coord);
493                 linkTag.nodeEndTag = nodeTag;
494             }
495             if (coords.length > 2 && (i > 0 && i < coords.length - 1))
496             {
497                 vertices[i - 1] = new OTSPoint3D(coord);
498             }
499             i++;
500         }
501         if (linkTag.polyLineTag != null)
502         {
503             if (coords.length <= 2)
504             {
505                 linkTag.polyLineTag = null;
506                 linkTag.straightTag = new StraightTag();
507             }
508             else
509             {
510                 linkTag.polyLineTag.vertices = vertices;
511             }
512         }
513     }
514 
515     /**
516      * Creates the Node info for start and end node and intermediate vertices of the EndLink
517      * @param linkTag
518      * @param designLine
519      * @param nodeTag
520      */
521     private static void createGeometryEndLink(LinkTag linkTag, LineString designLine, NodeTag nodeTag)
522     {
523         Coordinate[] coords = designLine.getCoordinates();
524         OTSPoint3D[] vertices = new OTSPoint3D[coords.length - 2];
525         int i = 0;
526         for (Coordinate coord : coords)
527         {
528             if (i == 0)
529             {
530                 nodeTag.coordinate = new OTSPoint3D(coord);
531                 linkTag.nodeStartTag = nodeTag;
532             }
533             if (i == coords.length - 1)
534             {
535                 linkTag.nodeEndTag.coordinate = new OTSPoint3D(coord);
536             }
537             if (coords.length > 2 && (i > 0 && i < coords.length - 1))
538             {
539                 vertices[i - 1] = new OTSPoint3D(coord);
540             }
541             i++;
542         }
543         if (linkTag.polyLineTag != null)
544         {
545             if (coords.length <= 2)
546             {
547                 linkTag.polyLineTag = null;
548                 linkTag.straightTag = new StraightTag();
549             }
550             else
551             {
552                 linkTag.polyLineTag.vertices = vertices;
553             }
554         }
555     }
556 
557     private static void createGeometryShortenedLink(LinkTag linkTag, LineString designLine)
558     {
559         Coordinate[] coords = designLine.getCoordinates();
560         OTSPoint3D[] vertices = new OTSPoint3D[coords.length - 2];
561         int i = 0;
562         for (Coordinate coord : coords)
563         {
564             if (i == 0)
565             {
566                 linkTag.nodeStartTag.coordinate = new OTSPoint3D(coord);
567             }
568             if (i == coords.length - 1)
569             {
570                 linkTag.nodeEndTag.coordinate = new OTSPoint3D(coord);
571             }
572             if (coords.length > 2 && (i > 0 && i < coords.length - 1))
573             {
574                 vertices[i - 1] = new OTSPoint3D(coord);
575             }
576             i++;
577         }
578         if (linkTag.polyLineTag != null)
579         {
580             if (coords.length <= 2)
581             {
582                 linkTag.polyLineTag = null;
583                 linkTag.straightTag = new StraightTag();
584             }
585             else
586             {
587                 linkTag.polyLineTag.vertices = vertices;
588             }
589         }
590     }
591 
592     public static OTSLine3D createLineString(LinkTag linkTag) throws OTSGeometryException
593     {
594         OTSPoint3D[] coordinates = null;
595         if (linkTag.straightTag != null)
596         {
597             coordinates = new OTSPoint3D[2];
598             coordinates[0] = linkTag.nodeStartTag.coordinate;
599             coordinates[1] = linkTag.nodeEndTag.coordinate;
600         }
601         else if (linkTag.polyLineTag != null)
602         {
603             int intermediatePoints = linkTag.polyLineTag.vertices.length;
604             coordinates = new OTSPoint3D[intermediatePoints + 2];
605             coordinates[0] = linkTag.nodeStartTag.coordinate;
606             coordinates[intermediatePoints + 1] = linkTag.nodeEndTag.coordinate;
607             for (int p = 0; p < intermediatePoints; p++)
608             {
609                 coordinates[p + 1] = linkTag.polyLineTag.vertices[p];
610             }
611         }
612         return OTSLine3D.createAndCleanOTSLine3D(coordinates);
613 
614     }
615 
616     /**
617      * This method parses a length string that can have values such as: BEGIN, END, 10m, END-10m, 98%. Only use the method after
618      * the length of the cross section elements is known!
619      * @param posStr the position string to parse. Lengths are relative to the center line of the cross section element.
620      * @param cse the cross section element to retrieve the center line
621      * @return the corresponding position as a length on the center line
622      * @throws NetworkException when parsing fails
623      */
624     static Length parseBeginEndPosition(final String posStr, final CrossSectionElement cse) throws NetworkException
625     {
626         if (posStr.trim().equals("BEGIN"))
627         {
628             return new Length(0.0, LengthUnit.METER);
629         }
630 
631         double length = cse.getCenterLine().getLengthSI();
632 
633         if (posStr.trim().equals("END"))
634         {
635             return new Length(length, LengthUnit.METER);
636         }
637 
638         if (posStr.endsWith("%"))
639         {
640             String s = posStr.substring(0, posStr.length() - 1).trim();
641             try
642             {
643                 double fraction = Double.parseDouble(s) / 100.0;
644                 if (fraction < 0.0 || fraction > 1.0)
645                 {
646                     throw new NetworkException("parseBeginEndPosition: attribute POSITION with value " + posStr
647                             + " invalid for lane " + cse.toString() + ", should be a percentage between 0 and 100%");
648                 }
649                 return new Length(length * fraction, LengthUnit.METER);
650             }
651             catch (NumberFormatException nfe)
652             {
653                 throw new NetworkException("parseBeginEndPosition: attribute POSITION with value " + posStr
654                         + " invalid for lane " + cse.toString() + ", should be a percentage between 0 and 100%", nfe);
655             }
656         }
657 
658         if (posStr.trim().startsWith("END-"))
659         {
660             String s = posStr.substring(4).trim();
661             double offset = LengthUnits.parseLength(s).getSI();
662             if (offset > length)
663             {
664                 throw new NetworkException("parseBeginEndPosition - attribute POSITION with value " + posStr
665                         + " invalid for lane " + cse.toString() + ": provided negative offset greater than than link length");
666             }
667             return new Length(length - offset, LengthUnit.METER);
668         }
669 
670         Length offset = LengthUnits.parseLength(posStr);
671         if (offset.getSI() > length)
672         {
673             throw new NetworkException("parseBeginEndPosition - attribute POSITION with value " + posStr + " invalid for lane "
674                     + cse.toString() + ": provided offset greater than than link length");
675         }
676         return offset;
677     }
678 
679     /** {@inheritDoc} */
680     @Override
681     public String toString()
682     {
683         return "LinkTag [name=" + this.name + "]";
684     }
685 
686     /**
687      * @param parser VissimNetworkLaneParser; the VissimParser with info to create a network
688      */
689     public static void addSignalHeads(VissimNetworkLaneParser parser)
690     {
691         for (SignalHeadTag signalHeadTag : parser.getSignalHeadTags().values())
692         {
693             parser.getLinkTags().get(signalHeadTag.linkName).signalHeads.add(signalHeadTag);
694         }
695     }
696 
697     /**
698      * @param parser VissimNetworkLaneParser; the VissimParser with info to create a network
699      */
700     public static void addDetectors(VissimNetworkLaneParser parser)
701     {
702         for (SensorTag sensorTag : parser.getSensorTags().values())
703         {
704             parser.getLinkTags().get(sensorTag.linkName).sensors.add(sensorTag);
705         }
706     }
707 
708     /**
709      * @param parser VissimNetworkLaneParser; the VissimParser with info to create a network
710      * @throws OTSGeometryException:
711      * @throws NamingException:
712      * @throws NetworkException:
713      */
714     public static void shortenConnectors(VissimNetworkLaneParser parser)
715             throws OTSGeometryException, NetworkException, NamingException
716     {
717         for (LinkTag connectorTag : parser.getConnectorTags().values())
718         {
719             OTSLine3D designLineOTS = LinkTag.createLineString(connectorTag);
720             LineString designLine = designLineOTS.getLineString();
721             Double length = designLine.getLength();
722             Double decreaseLength = length / 3.0;
723             LineString shortenedDesignLine = SubstringLine.getSubstring(designLine, decreaseLength, length - decreaseLength);
724             createGeometryShortenedLink(connectorTag, shortenedDesignLine);
725             // make a new OTS node, because the coordinates have changed
726             connectorTag.nodeStartTag.node = NodeTag.makeOTSNode(connectorTag.nodeStartTag, parser);
727             connectorTag.nodeEndTag.node = NodeTag.makeOTSNode(connectorTag.nodeEndTag, parser);
728         }
729     }
730 
731 }