View Javadoc
1   package org.opentrafficsim.core.network.factory;
2   
3   import java.awt.geom.Point2D;
4   import java.util.ArrayList;
5   import java.util.Arrays;
6   import java.util.HashMap;
7   import java.util.List;
8   import java.util.Map;
9   
10  import javax.vecmath.Point2d;
11  import javax.vecmath.Point3d;
12  
13  import org.opentrafficsim.core.network.Network;
14  import org.opentrafficsim.core.network.NetworkException;
15  import org.opentrafficsim.core.network.Node;
16  import org.opentrafficsim.core.network.geotools.NodeGeotools;
17  import org.opentrafficsim.core.network.lane.CrossSectionElement;
18  import org.opentrafficsim.core.network.point2d.NodePoint2D;
19  import org.opentrafficsim.core.unit.LengthUnit;
20  import org.opentrafficsim.core.unit.SpeedUnit;
21  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
22  
23  import com.vividsolutions.jts.geom.Coordinate;
24  
25  /**
26   * Parse a String with a simple representation of a lane-based network. An example of such a network is:
27   * 
28   * <pre>
29   * NODE = {NAME=N1, COORDINATE=(0,0)} # the first node
30   * NODE = {NAME=N2} # the second node
31   * LINK = {NAME="A4_12", FROM=N1, TO=N2, E="S1|V2:V1|D|A1:A2|S2", T=S, L=50m, s=80km/h, w=4m, w(S1)=1m, w(S2=1m)} # a lane
32   * NODE = {NAME="N3 b2"}
33   * LINK = {NAME="A4_13", FROM=N2, TO="N3 b2", E="S1|V2:V1|D|A1:A2|S2", T=C, R=100m, A=+90, s=80km/h, w=4m, 
34   *         w(S1)=1m, w(S2)=1m} # another lane
35   * NODE = {NAME="N4"}
36   * LINK = {NAME="A4_14", FROM="N3 b2", TO=N4, E="S1|V2:V1|D|A1:A2&lt;A3|S2", T=S, L=50m, s=80km/h, s(A3)=60km/h, w=4m,
37   *         w(S1)=1m, w(S2)=1m}
38   * NODE = {NAME="N5"}
39   * NODE = {NAME="ENTRY5"}
40   * LINK = {NAME="A4_15", FROM="N4", TO=N5, E="S1|V2:V1|D|A1:A2|S2", T=S, L=100m, s=80km/h, w=4m,
41   *         w(S1)=1m, w(S2)=1m}
42   * LINK = {NAME="LE1", FROM="ENTRY5", TO=N4(A3), E="|A|", T=C, R=50m, a=-45, s=60km/h, w=4m}
43   * NODE = {NAME="ENTRY6"}
44   * LINK = {NAME="LE2", FROM="ENTRY6", TO=ENTRY5, E="|A|", T=C, R=50m, a=-45, s=60km/h, w=4m}
45   * </pre>
46   * <p>
47   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
48   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
49   * <p>
50   * @version Feb 6, 2015 <br>
51   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
52   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
53   */
54  public class NetworkLaneParser
55  {
56      /** the ID class of the Network. */
57      private final Class<?> networkIdClass;
58  
59      /** the class of the Node. */
60      private final Class<?> nodeClass;
61  
62      /** the ID class of the Node. */
63      private final Class<?> nodeIdClass;
64  
65      /** the Point class of the Node. */
66      private final Class<?> nodePointClass;
67  
68      /** the ID class of the Link. */
69      private final Class<?> linkIdClass;
70  
71      /** the tokens. */
72      private static final List<String> TOKENS = new ArrayList<>();
73  
74      /** the lane tokens. */
75      private static final List<String> LINK_TOKENS = new ArrayList<>();
76  
77      /** the node tokens. */
78      private static final List<String> NODE_TOKENS = new ArrayList<>();
79  
80      /** the speed units. */
81      private static final Map<String, SpeedUnit> SPEED_UNITS = new HashMap<>();
82  
83      /** the length units. */
84      private static final Map<String, LengthUnit> LENGTH_UNITS = new HashMap<>();
85  
86      /** the nodes for further reference. */
87      @SuppressWarnings("rawtypes")
88      private Map<String, Node> nodes = new HashMap<>();
89  
90      static
91      {
92          TOKENS.addAll(Arrays.asList(new String[] {"NODE", "N", "LANE", "L", "GTU", "G"}));
93          NODE_TOKENS.addAll(Arrays.asList(new String[] {"COORDINATE", "C", "NAME", "N"}));
94          LINK_TOKENS.addAll(Arrays.asList(new String[] {"NAME", "N", "ELEMENTS", "E", "TYPE", "T", "LENGTH", "L", "SPEED",
95              "S", "RADIUS", "R", "ANGLE", "A", "FROMNODE", "FROM", "F", "TONODE", "TO", "WIDTH", "W"}));
96  
97          SPEED_UNITS.put("km/h", SpeedUnit.KM_PER_HOUR);
98          SPEED_UNITS.put("mi/h", SpeedUnit.MILE_PER_HOUR);
99          SPEED_UNITS.put("m/s", SpeedUnit.METER_PER_SECOND);
100 
101         LENGTH_UNITS.put("mm", LengthUnit.MILLIMETER);
102         LENGTH_UNITS.put("cm", LengthUnit.CENTIMETER);
103         LENGTH_UNITS.put("dm", LengthUnit.DECIMETER);
104         LENGTH_UNITS.put("dam", LengthUnit.DEKAMETER);
105         LENGTH_UNITS.put("hm", LengthUnit.HECTOMETER);
106         LENGTH_UNITS.put("m", LengthUnit.METER);
107         LENGTH_UNITS.put("km", LengthUnit.KILOMETER);
108         LENGTH_UNITS.put("mi", LengthUnit.MILE);
109         LENGTH_UNITS.put("y", LengthUnit.YARD);
110         LENGTH_UNITS.put("ft", LengthUnit.FOOT);
111     }
112 
113     /**
114      * @param networkIdClass the ID class of the Network.
115      * @param nodeClass the class of the Node.
116      * @param nodeIdClass the ID class of the Node.
117      * @param nodePointClass the Point class of the Node.
118      * @param linkIdClass the ID class of the Link.
119      */
120     public NetworkLaneParser(final Class<?> networkIdClass, final Class<?> nodeClass, final Class<?> nodeIdClass,
121         final Class<?> nodePointClass, final Class<?> linkIdClass)
122     {
123         this.networkIdClass = networkIdClass;
124         this.nodeClass = nodeClass;
125         this.nodeIdClass = nodeIdClass;
126         this.nodePointClass = nodePointClass;
127         this.linkIdClass = linkIdClass;
128     }
129 
130     /**
131      * @param original the network in the agreed grammar.
132      * @return the network with Nodes, Links, and Lanes.
133      * @throws NetworkException in case of parsing problems.
134      */
135     @SuppressWarnings({"rawtypes", "unchecked"})
136     public final Network<?, ?> build(final String original) throws NetworkException
137     {
138         // clear storage.
139         this.nodes.clear();
140 
141         // take out the comments and the special characters we are not interested in
142         boolean keep = true;
143         boolean inString = false;
144         StringBuilder ns = new StringBuilder();
145         for (int i = 0; i < original.length(); i++)
146         {
147             char c = original.charAt(i);
148             if (c == '"')
149             {
150                 inString = !inString;
151             }
152             if (c == '#')
153             {
154                 keep = false;
155             }
156             else if (c == '\r' || c == '\n')
157             {
158                 keep = true;
159             }
160             if (keep)
161             {
162                 if (!inString && c != ',' && c != ';' & c != '\t' && c != '\r' && c != '\n')
163                 {
164                     ns.append(c);
165                 }
166                 else
167                 {
168                     ns.append(' ');
169                 }
170             }
171         }
172 
173         String networkId = "";
174 
175         while (ns.length() > 0)
176         {
177             String token = eatToken(ns, TOKENS);
178             String args = eatArgs(ns, token);
179 
180             switch (token)
181             {
182                 case "NODE": // new node
183                 case "N":
184                     parseNode(args);
185                     break;
186 
187                 case "LINK": // new link
188                 case "L":
189                     parseLink(args);
190                     break;
191 
192                 default:
193                     throw new NetworkException("Parsing network. NODE: unknown token: " + token);
194             }
195         }
196 
197         Network network = new Network(makeId(this.networkIdClass, networkId));
198         return network;
199     }
200 
201     /**
202      * @param nodeArgs the lane arguments to parse.
203      * @return the constructed node.
204      * @throws NetworkException in case of parsing problems.
205      */
206     @SuppressWarnings("rawtypes")
207     private Node parseNode(final String nodeArgs) throws NetworkException
208     {
209         StringBuilder ns = new StringBuilder(nodeArgs);
210         String nodeName = null;
211         Point3d coordinate = null;
212 
213         while (ns.length() > 0)
214         {
215             String token = eatToken(ns, NODE_TOKENS);
216             switch (token)
217             {
218                 case "NAME": // lane type
219                 case "N":
220                     nodeName = eatValue(ns, token);
221                     nodeName.replace('"', ' ');
222                     break;
223 
224                 case "COORDINATE": // new node
225                 case "C":
226                     double x = eatDoubleValue(ns, token);
227                     double y = eatDoubleValue(ns, token);
228                     coordinate = new Point3d(x, y, 0);
229                     break;
230 
231                 default:
232                     throw new NetworkException("Parsing network. NODE: unknown token: " + token);
233             }
234         }
235         if (coordinate == null)
236         {
237             throw new NetworkException("Parsing network. NODE " + nodeName + ", no known coordinate");
238         }
239         if (nodeName == null)
240         {
241             throw new NetworkException("Parsing network. NODE has no name");
242         }
243         Node node = makeNode(this.nodeClass, makeId(this.nodeIdClass, nodeName), makePoint(this.nodePointClass, coordinate));
244         this.nodes.put(node.getId().toString(), node);
245         return node;
246     }
247 
248     /**
249      * @param laneArgs the lane arguments to parse.
250      * @throws NetworkException in case of parsing problems.
251      */
252     @SuppressWarnings("rawtypes")
253     private void parseLink(final String laneArgs) throws NetworkException
254     {
255         StringBuilder ls = new StringBuilder(laneArgs);
256 
257         String linkName = null;
258         Node fromNode = null;
259         Node toNode = null;
260         String type = null;
261         DoubleScalar.Rel<LengthUnit> length = null;
262         DoubleScalar.Abs<SpeedUnit> speed = null;
263         DoubleScalar.Rel<LengthUnit> radius = null;
264         double angle = Double.NaN;
265         Map<String, CrossSectionElement> lanes = null;
266 
267         while (ls.length() > 0)
268         {
269             String token = eatToken(ls, LINK_TOKENS);
270             switch (token)
271             {
272                 case "TYPE": // lane type
273                 case "T":
274                     type = eatValue(ls, token);
275                     break;
276 
277                 case "NAME": // new node
278                 case "N":
279                     linkName = eatValue(ls, token);
280                     break;
281 
282                 case "FROMNODE": // from node
283                 case "FROM":
284                 case "F":
285                     String fromNodeName = eatValue(ls, token);
286                     fromNodeName.replace('"', ' ');
287                     fromNode = this.nodes.get(fromNodeName);
288                     break;
289 
290                 case "TONODE": // to node
291                 case "TO":
292                     String toNodeName = eatValue(ls, token);
293                     toNodeName.replace('"', ' ');
294                     toNode = this.nodes.get(toNodeName);
295                     break;
296 
297                 case "ELEMENTS": // lanes
298                 case "E":
299                     lanes = parseLanes(ls);
300                     break;
301 
302                 case "LENGTH":
303                 case "L":
304                     length = eatLengthRel(ls, token);
305                     break;
306 
307                 case "SPEED":
308                 case "S":
309                     speed = eatSpeedAbs(ls, token);
310                     // TODO speed element of a specific lane
311                     break;
312 
313                 case "RADIUS":
314                 case "R":
315                     radius = eatLengthRel(ls, token);
316                     break;
317 
318                 case "ANGLE":
319                 case "A":
320                     angle = eatDoubleValue(ls, token);
321                     break;
322 
323                 case "WIDTH":
324                 case "W":
325                     // TODO width element
326                     // TODO width element of a specific lane
327                     break;
328 
329                 default:
330                     throw new NetworkException("Parsing network. LINK: unknown token: " + token);
331             }
332         }
333 
334         if (linkName == null)
335         {
336             throw new NetworkException("Parsing network. LINK has no name");
337         }
338         if (fromNode == null || toNode == null || type == null || lanes == null)
339         {
340             throw new NetworkException("Parsing network. LINK " + linkName + " has missing elements");
341         }
342         if (type.equals("S") && length == null)
343         {
344             throw new NetworkException("Parsing network. LINK " + linkName + " (S) has missing length");
345         }
346         if (type.equals("C") && (radius == null || angle == Double.NaN))
347         {
348             throw new NetworkException("Parsing network. LINK " + linkName + " (C) has missing radius or angle");
349         }
350 
351         // TODO create lanes and link
352     }
353 
354     /**
355      * Generate an ID of the right type.
356      * @param clazz the class to instantiate.
357      * @param ids the id as a String.
358      * @return the object as an instance of the right class.
359      * @throws NetworkException when id cannot be instantiated
360      */
361     private Object makeId(final Class<?> clazz, final String ids) throws NetworkException
362     {
363         Object id = null;
364         try
365         {
366             if (String.class.isAssignableFrom(clazz))
367             {
368                 id = new String(ids);
369             }
370             else if (int.class.isAssignableFrom(clazz))
371             {
372                 id = Integer.valueOf(ids);
373             }
374             else if (long.class.isAssignableFrom(clazz))
375             {
376                 id = Long.valueOf(ids);
377             }
378             else
379             {
380                 throw new NetworkException("Parsing network. ID class " + clazz.getName() + ": cannot instantiate.");
381             }
382         }
383         catch (NumberFormatException nfe)
384         {
385             throw new NetworkException("Parsing network. ID class " + clazz.getName() + ": cannot instantiate number: "
386                 + ids, nfe);
387         }
388         return id;
389     }
390 
391     /**
392      * Generate an ID of the right type.
393      * @param clazz the class to instantiate.
394      * @param p the point as a String.
395      * @return the object as an instance of the right class.
396      * @throws NetworkException when point cannot be instantiated
397      */
398     private Object makePoint(final Class<?> clazz, final Point3d p) throws NetworkException
399     {
400         Object point = null;
401         if (Point3d.class.isAssignableFrom(clazz))
402         {
403             point = p;
404         }
405         else if (Point2D.class.isAssignableFrom(clazz))
406         {
407             point = new Point2D.Double(p.x, p.y);
408         }
409         else if (Point2d.class.isAssignableFrom(clazz))
410         {
411             point = new Point2d(new double[] {p.x, p.y});
412         }
413         else if (Coordinate.class.isAssignableFrom(clazz))
414         {
415             point = new Coordinate(p.x, p.y, p.z);
416         }
417         else
418         {
419             throw new NetworkException("Parsing network. Point class " + clazz.getName() + ": cannot instantiate.");
420         }
421         return point;
422     }
423 
424     /**
425      * @param clazz the node class
426      * @param id the id as an object
427      * @param point the point as an object
428      * @return a constructed node
429      * @throws NetworkException when point cannot be instantiated
430      */
431     @SuppressWarnings({"unchecked", "rawtypes"})
432     private Node makeNode(final Class<?> clazz, final Object id, final Object point) throws NetworkException
433     {
434         if (NodeGeotools.class.isAssignableFrom(clazz))
435         {
436             if (point instanceof Coordinate)
437             {
438                 return new NodeGeotools(id, (Coordinate) point);
439             }
440             throw new NetworkException("Parsing network. Node class " + clazz.getName()
441                 + ": cannot instantiate. Wrong Coordinate type: " + point.getClass() + ", coordinate: " + point);
442         }
443         else if (NodePoint2D.class.isAssignableFrom(clazz))
444         {
445             if (point instanceof Point2D)
446             {
447                 return new NodePoint2D(id, (Point2D) point);
448             }
449             throw new NetworkException("Parsing network. Node class " + clazz.getName()
450                 + ": cannot instantiate. Wrong Point2D type: " + point.getClass() + ", coordinate: " + point);
451         }
452         else
453         {
454             throw new NetworkException("Parsing network. Node class " + clazz.getName() + ": cannot instantiate.");
455         }
456     }
457 
458     /**
459      * @param ns the string to parse
460      * @param tokens the tokens to scan for
461      * @return the token.
462      * @throws NetworkException when token not correct
463      */
464     @SuppressWarnings("checkstyle:finalparameters")
465     private String eatToken(StringBuilder ns, final List<String> tokens) throws NetworkException
466     {
467         ns.replace(0, ns.length() - 1, ns.toString().trim());
468         int eq = ns.indexOf("=");
469         String token = ns.substring(0, eq - 1).trim();
470         ns.delete(0, eq + 1);
471         if (!tokens.contains(token))
472         {
473             throw new NetworkException("Parsing network. Got token:" + token + ", expected one of:" + tokens);
474         }
475         return token;
476     }
477 
478     /**
479      * @param args the string to parse
480      * @param token the token for debugging purposes.
481      * @return the next value.
482      * @throws NetworkException when parsing fails
483      */
484     @SuppressWarnings("checkstyle:finalparameters")
485     private String eatValue(StringBuilder args, final String token) throws NetworkException
486     {
487         args.replace(0, args.length() - 1, args.toString().trim());
488         if (args.length() == 0)
489         {
490             throw new NetworkException("Parsing network. Expected value for token: " + token + ", but none found");
491         }
492         int space = args.indexOf(" ");
493         String value = args.substring(0, space - 1).trim();
494         args.delete(0, space + 1);
495         return value;
496     }
497 
498     /**
499      * @param args the string to parse
500      * @param token the token for debugging purposes.
501      * @return the next value.
502      * @throws NetworkException when parsing fails
503      */
504     @SuppressWarnings("checkstyle:finalparameters")
505     private double eatDoubleValue(StringBuilder args, final String token) throws NetworkException
506     {
507         String s = eatValue(args, token);
508         try
509         {
510             double value = Double.parseDouble(s);
511             return value;
512         }
513         catch (NumberFormatException nfe)
514         {
515             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate number: " + s, nfe);
516         }
517     }
518 
519     /**
520      * @param s the string to parse
521      * @param token the token for debugging purposes.
522      * @return the unit.
523      * @throws NetworkException when parsing fails
524      */
525     @SuppressWarnings("checkstyle:finalparameters")
526     private SpeedUnit eatSpeedUnit(StringBuilder s, final String token) throws NetworkException
527     {
528         SpeedUnit u = null;
529         for (String us : SPEED_UNITS.keySet())
530         {
531             if (s.toString().contains(us))
532             {
533                 u = SPEED_UNITS.get(us);
534                 s.delete(s.indexOf(us), s.indexOf(us) + us.length() - 1);
535             }
536         }
537         if (u == null)
538         {
539             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate speed unit in: " + s);
540         }
541         return u;
542     }
543 
544     /**
545      * @param s the string to parse
546      * @param token the token for debugging purposes.
547      * @return the next value.
548      * @throws NetworkException when parsing fails
549      */
550     @SuppressWarnings("checkstyle:finalparameters")
551     private DoubleScalar.Abs<SpeedUnit> eatSpeedAbs(StringBuilder s, final String token) throws NetworkException
552     {
553         SpeedUnit u = eatSpeedUnit(s, token);
554         try
555         {
556             double value = Double.parseDouble(s.toString());
557             return new DoubleScalar.Abs<SpeedUnit>(value, u);
558         }
559         catch (NumberFormatException nfe)
560         {
561             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate scalar: " + s, nfe);
562         }
563     }
564 
565     /**
566      * @param s the string to parse
567      * @param token the token for debugging purposes.
568      * @return the next value.
569      * @throws NetworkException when parsing fails
570      */
571     @SuppressWarnings("checkstyle:finalparameters")
572     private DoubleScalar.Rel<SpeedUnit> eatSpeedRel(StringBuilder s, final String token) throws NetworkException
573     {
574         SpeedUnit u = eatSpeedUnit(s, token);
575         try
576         {
577             double value = Double.parseDouble(s.toString());
578             return new DoubleScalar.Rel<SpeedUnit>(value, u);
579         }
580         catch (NumberFormatException nfe)
581         {
582             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate scalar: " + s, nfe);
583         }
584     }
585 
586     /**
587      * @param s the string to parse
588      * @param token the token for debugging purposes.
589      * @return the unit.
590      * @throws NetworkException when parsing fails
591      */
592     @SuppressWarnings("checkstyle:finalparameters")
593     private LengthUnit eatLengthUnit(StringBuilder s, final String token) throws NetworkException
594     {
595         LengthUnit u = null;
596         for (String us : LENGTH_UNITS.keySet())
597         {
598             if (s.toString().contains(us))
599             {
600                 u = LENGTH_UNITS.get(us);
601                 s.delete(s.indexOf(us), s.indexOf(us) + us.length() - 1);
602             }
603         }
604         if (u == null)
605         {
606             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate length unit in: " + s);
607         }
608         return u;
609     }
610 
611     /**
612      * @param s the string to parse
613      * @param token the token for debugging purposes.
614      * @return the next value.
615      * @throws NetworkException when parsing fails
616      */
617     @SuppressWarnings("checkstyle:finalparameters")
618     private DoubleScalar.Abs<LengthUnit> eatLengthAbs(StringBuilder s, final String token) throws NetworkException
619     {
620         LengthUnit u = eatLengthUnit(s, token);
621         try
622         {
623             double value = Double.parseDouble(s.toString());
624             return new DoubleScalar.Abs<LengthUnit>(value, u);
625         }
626         catch (NumberFormatException nfe)
627         {
628             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate scalar: " + s, nfe);
629         }
630     }
631 
632     /**
633      * @param s the string to parse
634      * @param token the token for debugging purposes.
635      * @return the next value.
636      * @throws NetworkException when parsing fails
637      */
638     @SuppressWarnings("checkstyle:finalparameters")
639     private DoubleScalar.Rel<LengthUnit> eatLengthRel(StringBuilder s, final String token) throws NetworkException
640     {
641         LengthUnit u = eatLengthUnit(s, token);
642         try
643         {
644             double value = Double.parseDouble(s.toString());
645             return new DoubleScalar.Rel<LengthUnit>(value, u);
646         }
647         catch (NumberFormatException nfe)
648         {
649             throw new NetworkException("Parsing network. Token " + token + ": cannot instantiate scalar: " + s, nfe);
650         }
651     }
652 
653     /**
654      * @param ns the string to parse
655      * @param token the token for debugging purposes.
656      * @return the arguments after a token.
657      * @throws NetworkException when brackets not correct
658      */
659     @SuppressWarnings("checkstyle:finalparameters")
660     private String eatArgs(StringBuilder ns, final String token) throws NetworkException
661     {
662         ns.replace(0, ns.length() - 1, ns.toString().trim());
663         char bs;
664         char be;
665         if (ns.charAt(0) == '(')
666         {
667             bs = '(';
668             be = ')';
669         }
670         else if (ns.charAt(0) == '{')
671         {
672             bs = '{';
673             be = '}';
674         }
675         else if (ns.charAt(0) == '[')
676         {
677             bs = '[';
678             be = ']';
679         }
680         else
681         {
682             throw new NetworkException("Parsing network. After token:" + token + ", expected (, { or [ but got :"
683                 + ns.charAt(0));
684         }
685         int index = 0;
686         int nrBracket = 0;
687         for (int i = 0; i < ns.length() && index == 0; i++)
688         {
689             if (ns.charAt(i) == bs)
690             {
691                 nrBracket++;
692             }
693             else if (ns.charAt(i) == be)
694             {
695                 nrBracket--;
696             }
697             if (nrBracket == 0)
698             {
699                 index = i;
700             }
701         }
702 
703         String args = ns.substring(0, index - 1).trim();
704         ns.replace(0, ns.length() - 1, ns.toString().trim());
705         return args;
706     }
707 
708     /**
709      * @param elements the lane element string to parse, e.g "|V1:V2|D|A2:A1|"
710      * @return map of cross section elements
711      */
712     private Map<String, CrossSectionElement> parseLanes(final StringBuilder elements)
713     {
714         // TODO parse lane elements in the link
715         return null;
716     }
717 
718     /**
719      * Test.
720      * @param args none.
721      * @throws NetworkException
722      */
723     public static void main(final String[] args) throws NetworkException
724     {
725         String s = "NODE = {NAME=N1, COORDINATE=(0,0)} # the first node\n";
726         s += "NODE = {NAME=N2} # the second node\n";
727         s += "LINK = {NAME=\"A4_12\", FROM=N1, TO=N2, E=\"S1|V2:V1|D|A1:A2|S2\", T=S, L=50m, s=80km/h, w=4m, \n";
728         s += "        w(S1)=1m, w(S2=1m)} # a lane\n";
729         s += "NODE = {NAME=\"N3 b2\"}\n";
730         s += "LINK = {NAME=\"A4_13\", FROM=N2, TO=\"N3 b2\", E=\"S1|V2:V1|D|A1:A2|S2\", T=C, R=100m, A=+90, \n";
731         s += "        s=80km/h, w=4m, w(S1)=1m, w(S2)=1m} # another lane\n";
732         s += "NODE = {NAME=\"N4\"}\n";
733         s += "LINK = {NAME=\"A4_14\", FROM=\"N3 b2\", TO=N4, E=\"S1|V2:V1|D|A1:A2<A3|S2\", T=S, L=50m, s=80km/h, \n";
734         s += "        s(A3)=60km/h, w=4m, w(S1)=1m, w(S2)=1m}\n";
735         s += "NODE = {NAME=\"N5\"}\n";
736         s += "NODE = {NAME=\"ENTRY5\"}\n";
737         s += "LINK = {NAME=\"A4_15\", FROM=N4, TO=N5, E=\"S1|V2:V1|D|A1:A2|S2\", T=S, L=100m, s=80km/h, w=4m,\n";
738         s += "        w(S1)=1m, w(S2)=1m}\n";
739         s += "LINK = {NAME=\"LE1\", FROM=\"ENTRY5\", TO=N4(A3), E=\"|A|\", T=C, R=50m, a=-45, s=60km/h, w=4m}\n";
740         s += "NODE = {NAME=\"ENTRY6\"}\n";
741         s += "LINK = {NAME=\"LE2\", FROM=\"ENTRY6\", TO=ENTRY5, E=\"|A|\", T=C, R=50m, a=-45, s=60km/h, w=4m}\n";
742         NetworkLaneParser nlp =
743             new NetworkLaneParser(String.class, NodeGeotools.class, String.class, Coordinate.class, String.class);
744         Network n = nlp.build(s);
745     }
746 }