View Javadoc
1   package org.opentrafficsim.road.network.factory.osm.output;
2   
3   import static org.opentrafficsim.core.gtu.GTUType.BIKE;
4   import static org.opentrafficsim.core.gtu.GTUType.BOAT;
5   import static org.opentrafficsim.core.gtu.GTUType.PEDESTRIAN;
6   import static org.opentrafficsim.road.gtu.lane.RoadGTUTypes.CAR;
7   
8   import java.awt.Color;
9   import java.io.Serializable;
10  import java.rmi.RemoteException;
11  import java.util.ArrayList;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Locale;
15  import java.util.Map;
16  import java.util.Objects;
17  import java.util.SortedMap;
18  import java.util.TreeMap;
19  
20  import javax.naming.NamingException;
21  
22  import org.djunits.unit.LengthUnit;
23  import org.djunits.unit.SpeedUnit;
24  import org.djunits.value.vdouble.scalar.Length;
25  import org.djunits.value.vdouble.scalar.Speed;
26  import org.opengis.referencing.FactoryException;
27  import org.opengis.referencing.operation.TransformException;
28  import org.opentrafficsim.core.dsol.OTSAnimatorInterface;
29  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
30  import org.opentrafficsim.core.geometry.OTSGeometryException;
31  import org.opentrafficsim.core.geometry.OTSLine3D;
32  import org.opentrafficsim.core.geometry.OTSPoint3D;
33  import org.opentrafficsim.core.gtu.GTUType;
34  import org.opentrafficsim.core.network.LinkType;
35  import org.opentrafficsim.core.network.LongitudinalDirectionality;
36  import org.opentrafficsim.core.network.Network;
37  import org.opentrafficsim.core.network.NetworkException;
38  import org.opentrafficsim.core.network.OTSNode;
39  import org.opentrafficsim.road.network.animation.LaneAnimation;
40  import org.opentrafficsim.road.network.factory.osm.OSMLink;
41  import org.opentrafficsim.road.network.factory.osm.OSMNetwork;
42  import org.opentrafficsim.road.network.factory.osm.OSMNode;
43  import org.opentrafficsim.road.network.factory.osm.OSMTag;
44  import org.opentrafficsim.road.network.factory.osm.events.ProgressEvent;
45  import org.opentrafficsim.road.network.factory.osm.events.ProgressListener;
46  import org.opentrafficsim.road.network.factory.osm.events.WarningEvent;
47  import org.opentrafficsim.road.network.factory.osm.events.WarningListener;
48  import org.opentrafficsim.road.network.lane.CrossSectionLink;
49  import org.opentrafficsim.road.network.lane.Lane;
50  import org.opentrafficsim.road.network.lane.LaneType;
51  import org.opentrafficsim.road.network.lane.changing.LaneKeepingPolicy;
52  import org.opentrafficsim.road.network.lane.changing.OvertakingConditions;
53  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
54  
55  import com.vividsolutions.jts.geom.Coordinate;
56  
57  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
58  
59  /**
60   * <p>
61   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
62   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
63   * <p>
64   * $LastChangedDate: 2015-09-16 19:20:07 +0200 (Wed, 16 Sep 2015) $, @version $Revision: 1405 $, by $Author: averbraeck $,
65   * initial version 30.12.2014 <br>
66   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
67   * @author <a>Moritz Bergmann</a>
68   */
69  public final class Convert
70  {
71  
72      /**
73       * Construct a converter.
74       */
75      @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
76      public Convert()
77      {
78          baseX = Double.NaN;
79      }
80  
81      /** Meridian of least distortion. */
82      private static double baseX = Double.NaN;
83  
84      /**
85       * @param c Coordinate in WGS84
86       * @return Coordinate in Geocentric Cartesian system
87       * @throws FactoryException on ???
88       * @throws TransformException on problems with the coordinate transformation
89       */
90      public static Coordinate transform(final Coordinate c) throws FactoryException, TransformException
91      {
92          // final CoordinateReferenceSystem wgs84 = DefaultGeographicCRS.WGS84;
93          // final CoordinateReferenceSystem cartesianCRS = DefaultGeocentricCRS.CARTESIAN;
94          // final MathTransform mathTransform;
95          // mathTransform = CRS.findMathTransform(wgs84, cartesianCRS, false);
96          // double[] srcPt = {c.x, c.y};
97          // double[] dstPt = new double[mathTransform.getTargetDimensions()];
98  
99          // mathTransform.transform(srcPt, 0, dstPt, 0, 1);
100         // System.out.println(String.format(Locale.US, "%fkm, %fkm, %fkm", dstPt[0] / 1000, dstPt[1] / 1000, dstPt[2] /
101         // 1000));
102         // return new Coordinate(dstPt[1], -dstPt[0]);
103 
104         // Simple-minded DIY solution
105         double radius = 6371000; // Assume Earth is a perfect sphere
106         if (Double.isNaN(baseX))
107         {
108             baseX = c.x; // Use first coordinate as the reference
109         }
110         double x = radius * Math.toRadians(c.x - baseX) * Math.cos(Math.toRadians(c.y));
111         double y = radius * Math.toRadians(c.y);
112         // System.out.println(String.format(Locale.US, "%fkm, %fkm, %fkm", x / 1000, y / 1000, radius / 1000));
113         return new Coordinate(x, y);
114     }
115 
116     /**
117      * This method converts an OSM link to an OTS link.
118      * @param network the network
119      * @param link OSM Link to be converted
120      * @param simulator OTSDEVSSimulatorInterface; the simulator that will animate the generates lanes (if it happens to be an
121      *            instance of OTSAnimatorInterface)
122      * @return OTS Link
123      * @throws OTSGeometryException on failure
124      * @throws NetworkException if link already exists in the network, if name of the link is not unique, or if the start node
125      *             or the end node of the link are not registered in the network.
126      */
127     public CrossSectionLink convertLink(final Network network, final OSMLink link, final OTSDEVSSimulatorInterface simulator)
128             throws OTSGeometryException, NetworkException
129     {
130         if (null == link.getStart().getOtsNode())
131         {
132             link.getStart().setOtsNode(convertNode(network, link.getStart()));
133         }
134         if (null == link.getEnd().getOtsNode())
135         {
136             link.getEnd().setOtsNode(convertNode(network, link.getEnd()));
137         }
138         CrossSectionLink result;
139         Coordinate[] coordinates;
140         List<OSMNode> nodes = link.getSplineList();
141         int coordinateCount = 2 + nodes.size();
142         coordinates = new Coordinate[coordinateCount];
143         OTSNode start = link.getStart().getOtsNode();
144         coordinates[0] = new Coordinate(start.getPoint().x, start.getPoint().y, 0);
145         for (int i = 0; i < nodes.size(); i++)
146         {
147             coordinates[i + 1] = new Coordinate(nodes.get(i).getLongitude(), nodes.get(i).getLatitude());
148         }
149         OTSNode end = link.getEnd().getOtsNode();
150         coordinates[coordinates.length - 1] = new Coordinate(end.getPoint().x, end.getPoint().y, 0);
151         OTSLine3D designLine = new OTSLine3D(coordinates);
152         // XXX How to figure out whether to keep left, right or keep lane?
153         // XXX How to figure out if this is a lane in one or two directions? For now, two is assumed...
154         result = new CrossSectionLink(network, link.getId(), start, end, LinkType.ALL, designLine, simulator,
155                 LongitudinalDirectionality.DIR_BOTH, LaneKeepingPolicy.KEEP_RIGHT);
156         return result;
157     }
158 
159     /**
160      * This method converts an OSM node to an OTS node.
161      * @param network the network
162      * @param node OSM Node to be converted
163      * @return OTS Node
164      * @throws NetworkException if node already exists in the network, or if name of the node is not unique
165      */
166     public OTSNode convertNode(final Network network, final OSMNode node) throws NetworkException
167     {
168         OSMTag tag = node.getTag("ele");
169         if (null != tag)
170         {
171             try
172             {
173                 String ele = tag.getValue();
174                 Double elevation = 0d;
175                 if (ele.matches("[0-9]+(km)|m"))
176                 {
177                     String[] ele2 = ele.split("(km)|m");
178                     ele = ele2[0];
179                     if (ele2[1].equals("km"))
180                     {
181                         elevation = Double.parseDouble(ele) * 1000;
182                     }
183                     else if (ele2[1].equals("m"))
184                     {
185                         elevation = Double.parseDouble(ele);
186                     }
187                     else
188                     {
189                         throw new NumberFormatException("Cannot parse elevation value\"" + ele + "\"");
190                     }
191                 }
192                 else if (ele.matches("[0-9]+"))
193                 {
194                     elevation = Double.parseDouble(ele);
195                 }
196                 Coordinate coordWGS84 = new Coordinate(node.getLongitude(), node.getLatitude(), elevation);
197                 try
198                 {
199                     return new OTSNode(network, Objects.toString(node.getId()), new OTSPoint3D(transform(coordWGS84)));
200                 }
201                 catch (FactoryException | TransformException exception)
202                 {
203                     exception.printStackTrace();
204                 }
205             }
206             catch (NumberFormatException exception)
207             {
208                 exception.printStackTrace();
209             }
210         }
211         // No elevation specified, or we could not parse it; assume elevation is 0
212         Coordinate coordWGS84 = new Coordinate(node.getLongitude(), node.getLatitude(), 0d);
213         try
214         {
215             return new OTSNode(network, Objects.toString(node.getId()), new OTSPoint3D(Convert.transform(coordWGS84)));
216         }
217         catch (FactoryException | TransformException exception)
218         {
219             exception.printStackTrace();
220             // FIXME: how does the caller deal with a null result? (Answer: not!)
221             return null;
222         }
223     }
224 
225     /**
226      * Determine the positions of the various lanes on an OSMLink.
227      * @param osmLink - The OSM Link on which the conversion is based.
228      * @param warningListener WarningListener; the warning listener that receives warning events
229      * @return Map&lt;Double, LaneAttributes&gt;; the lane structure
230      * @throws NetworkException on failure
231      */
232     private static Map<Double, LaneAttributes> makeStructure(final OSMLink osmLink, final WarningListener warningListener)
233             throws NetworkException
234     {
235         SortedMap<Integer, LaneAttributes> structure = new TreeMap<Integer, LaneAttributes>();
236         int forwards = osmLink.getForwardLanes();
237         int backwards = osmLink.getLanes() - osmLink.getForwardLanes();
238         LaneType laneType;
239         LaneAttributes laneAttributes;
240         for (OSMTag tag : osmLink.getTags())
241         {
242             if (tag.getKey().equals("waterway"))
243             {
244                 switch (tag.getValue())
245                 {
246                     case "river":
247                         laneType = makeLaneType(BOAT);
248                         break;
249                     case "canal":
250                         laneType = makeLaneType(BOAT);
251                         break;
252                     default:
253                         laneType = makeLaneType(GTUType.NONE);
254                         break;
255                 }
256                 laneAttributes = new LaneAttributes(laneType, Color.CYAN, LongitudinalDirectionality.DIR_BOTH);
257                 structure.put(0, laneAttributes);
258             }
259         }
260         for (OSMTag tag : osmLink.getTags())
261         {
262             if (tag.getKey().equals("highway") && (tag.getValue().equals("primary") || tag.getValue().equals("secondary")
263                     || tag.getValue().equals("tertiary") || tag.getValue().equals("residential")
264                     || tag.getValue().equals("trunk") || tag.getValue().equals("motorway") || tag.getValue().equals("service")
265                     || tag.getValue().equals("unclassified") || tag.getValue().equals("motorway_link")
266                     || tag.getValue().equals("primary_link") || tag.getValue().equals("secondary_link")
267                     || tag.getValue().equals("tertiary_link") || tag.getValue().equals("trunk_link")
268                     || tag.getValue().equals("road") || tag.getValue().equals("track")
269                     || tag.getValue().equals("living_street")))
270             {
271                 laneType = makeLaneType(CAR);
272                 if (osmLink.getLanes() == 1 && !osmLink.isOneway())
273                 {
274                     laneAttributes = new LaneAttributes(laneType, Color.LIGHT_GRAY, LongitudinalDirectionality.DIR_BOTH);
275                     structure.put(0, laneAttributes);
276                 }
277                 else
278                 {
279                     for (int i = 0 - backwards; i < forwards; i++)
280                     {
281                         if (i < 0)
282                         {
283                             laneAttributes =
284                                     new LaneAttributes(laneType, Color.LIGHT_GRAY, LongitudinalDirectionality.DIR_MINUS);
285                             structure.put(i, laneAttributes);
286                         }
287                         if (i >= 0)
288                         {
289                             laneAttributes =
290                                     new LaneAttributes(laneType, Color.LIGHT_GRAY, LongitudinalDirectionality.DIR_PLUS);
291                             structure.put(i, laneAttributes);
292                         }
293                     }
294                 }
295             }
296             else if (tag.getKey().equals("highway") && (tag.getValue().equals("path") || tag.getValue().equals("steps")))
297             {
298                 List<GTUType> types = new ArrayList<GTUType>();
299                 for (OSMTag t2 : osmLink.getTags())
300                 {
301                     if (t2.getKey().equals("bicycle"))
302                     {
303                         types.add(BIKE);
304                     }
305                     /*
306                      * if (t2.getKey().equals("foot")) {
307                      * types.add(org.opentrafficsim.importexport.osm.PredefinedGTUTypes.pedestrian); }
308                      */
309                 }
310                 laneType = makeLaneType(types);
311                 types.add(PEDESTRIAN);
312                 if (!types.isEmpty())
313                 {
314                     if (osmLink.getLanes() == 1 && !osmLink.isOneway())
315                     {
316                         laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_BOTH);
317                         structure.put(0, laneAttributes);
318                     }
319                     else
320                     {
321                         for (int i = 0 - backwards; i < forwards; i++)
322                         {
323                             if (i < 0)
324                             {
325                                 laneAttributes =
326                                         new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_MINUS);
327                                 structure.put(i, laneAttributes);
328                             }
329                             if (i >= 0)
330                             {
331                                 laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_PLUS);
332                                 structure.put(i, laneAttributes);
333                             }
334                         }
335                     }
336                 }
337                 types.clear();
338             }
339         }
340         for (OSMTag tag : osmLink.getTags())
341         {
342             if (tag.getKey().equals("cycleway"))
343             {
344                 laneType = makeLaneType(BIKE);
345                 switch (tag.getValue())
346                 {
347                     case "lane": // cycleway:lane is directly adjacent to the highway.
348                         forwards++;
349                         backwards++;
350                         laneAttributes = new LaneAttributes(laneType, Color.ORANGE, LongitudinalDirectionality.DIR_MINUS);
351                         structure.put(0 - backwards, laneAttributes);
352                         laneAttributes = new LaneAttributes(laneType, Color.ORANGE, LongitudinalDirectionality.DIR_PLUS);
353                         structure.put(forwards - 1, laneAttributes);
354                         break;
355                     case "track": // cycleway:track is separated by a gap from the highway.
356                         forwards++;
357                         backwards++;
358                         laneAttributes = new LaneAttributes(laneType, Color.ORANGE, LongitudinalDirectionality.DIR_MINUS);
359                         structure.put(0 - backwards, laneAttributes);
360                         laneAttributes = new LaneAttributes(laneType, Color.ORANGE, LongitudinalDirectionality.DIR_PLUS);
361                         structure.put(forwards - 1, laneAttributes);
362                         break;
363                     case "shared_lane": // cycleway:shared_lane is embedded into the highway.
364                         List<GTUType> types = new ArrayList<GTUType>();
365                         types.add(BIKE);
366                         types.add(CAR);
367                         laneType = makeLaneType(types);
368                         laneAttributes = new LaneAttributes(laneType, Color.ORANGE, LongitudinalDirectionality.DIR_MINUS);
369                         structure.put(0 - backwards, laneAttributes);
370                         laneAttributes = new LaneAttributes(laneType, Color.ORANGE, LongitudinalDirectionality.DIR_PLUS);
371                         structure.put(forwards - 1, laneAttributes);
372                         break;
373                     default:
374                         break;
375                 }
376             }
377         }
378         for (OSMTag tag : osmLink.getTags())
379         {
380             if (tag.getKey().equals("sidewalk"))
381             {
382                 laneType = makeLaneType(PEDESTRIAN);
383                 switch (tag.getValue())
384                 {
385                     case "both":
386                         forwards++;
387                         backwards++;
388                         laneAttributes = new LaneAttributes(laneType, Color.YELLOW, LongitudinalDirectionality.DIR_MINUS);
389                         structure.put(0 - backwards, laneAttributes);
390                         laneAttributes = new LaneAttributes(laneType, Color.YELLOW, LongitudinalDirectionality.DIR_PLUS);
391                         structure.put(forwards - 1, laneAttributes);
392                         break;
393                     case "left":
394                         backwards++;
395                         laneAttributes = new LaneAttributes(laneType, Color.YELLOW, LongitudinalDirectionality.DIR_BOTH);
396                         structure.put(0 - backwards, laneAttributes);
397                         break;
398                     case "right":
399                         forwards++;
400                         laneAttributes = new LaneAttributes(laneType, Color.YELLOW, LongitudinalDirectionality.DIR_BOTH);
401                         structure.put(forwards - 1, laneAttributes);
402                         break;
403                     default:
404                         break;
405                 }
406             }
407         }
408         for (OSMTag tag : osmLink.getTags())
409         {
410             if (tag.getKey().equals("highway") && (tag.getValue().equals("cycleway") || tag.getValue().equals("footway")
411                     || tag.getValue().equals("pedestrian") || tag.getValue().equals("steps")))
412             {
413                 if (tag.getValue().equals("footway") || tag.getValue().equals("pedestrian") || tag.getValue().equals("steps"))
414                 {
415                     laneType = makeLaneType(PEDESTRIAN);
416                     if (osmLink.getLanes() == 1 && !osmLink.isOneway())
417                     {
418                         laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_BOTH);
419                         structure.put(0, laneAttributes);
420                     }
421                     else
422                     {
423                         for (int i = 0 - backwards; i < forwards; i++)
424                         {
425                             if (i < 0)
426                             {
427                                 laneAttributes =
428                                         new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_MINUS);
429                                 structure.put(i, laneAttributes);
430                             }
431                             if (i >= 0)
432                             {
433                                 laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_PLUS);
434                                 structure.put(i, laneAttributes);
435                             }
436                         }
437                     }
438                 }
439                 if (tag.getValue().equals("cycleway"))
440                 {
441                     laneType = makeLaneType(BIKE);
442                     if (osmLink.getLanes() == 1 && !osmLink.isOneway())
443                     {
444                         laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_BOTH);
445                         structure.put(0, laneAttributes);
446                     }
447                     for (int i = 0 - backwards; i < forwards; i++)
448                     {
449                         if (i < 0)
450                         {
451                             laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_MINUS);
452                             structure.put(i, laneAttributes);
453                         }
454                         if (i >= 0)
455                         {
456                             laneAttributes = new LaneAttributes(laneType, Color.GREEN, LongitudinalDirectionality.DIR_PLUS);
457                             structure.put(i, laneAttributes);
458                         }
459                     }
460                 }
461             }
462         }
463         return calculateOffsets(structure, osmLink, forwards, backwards, warningListener);
464     }
465 
466     /**
467      * Calculates the actual offsets of the individual lanes.
468      * @param structure - Sorted Map of Lane Positions and Attributes
469      * @param osmLink - The osmLink on which the conversion is based.
470      * @param forwards - Number of forwards oriented lanes.
471      * @param backwards - Number of backwards oriented lanes.
472      * @param warningListener WarningListener; the warning listener that receives warning events
473      * @return Map containing the lane structure with offsets.
474      * @throws NetworkException on failure
475      */
476     private static Map<Double, LaneAttributes> calculateOffsets(final SortedMap<Integer, LaneAttributes> structure,
477             final OSMLink osmLink, final Integer forwards, final Integer backwards, final WarningListener warningListener)
478             throws NetworkException
479     {
480         HashMap<Double, LaneAttributes> structurewithOffset = new HashMap<Double, LaneAttributes>();
481         LaneAttributes laneAttributes;
482         double currentOffset = 0.0D;
483         if (structure.isEmpty())
484         {
485             warningListener.warning(new WarningEvent(osmLink, "Empty Structure at Link " + osmLink.getId()));
486         }
487         if (structure.lastKey() >= 0)
488         {
489             for (int i = 0; i < forwards; i++)
490             {
491                 laneAttributes = structure.get(i);
492                 if (null == laneAttributes)
493                 {
494                     break;
495                 }
496                 double useWidth = laneWidth(laneAttributes, osmLink, warningListener);
497                 laneAttributes.setWidth(useWidth);
498                 structurewithOffset.put(currentOffset, laneAttributes);
499                 currentOffset += useWidth;
500             }
501         }
502         if (structure.firstKey() < 0)
503         {
504             currentOffset = 0.0d;
505             for (int i = -1; i >= (0 - backwards); i--)
506             {
507                 laneAttributes = structure.get(i);
508                 if (null == laneAttributes)
509                 {
510                     break;
511                 }
512                 LaneAttributes previousLaneAttributes = null;
513                 for (int k = i + 1; k <= 0; k++)
514                 {
515                     previousLaneAttributes = structure.get(k);
516                     if (null != previousLaneAttributes)
517                     {
518                         break;
519                     }
520                 }
521                 if (null == previousLaneAttributes)
522                 {
523                     throw new NetworkException("reverse lane without main lane?");
524                 }
525                 double useWidth = laneWidth(laneAttributes, osmLink, warningListener);
526                 laneAttributes.setWidth(useWidth);
527                 currentOffset -= previousLaneAttributes.getWidth().getSI();
528                 structurewithOffset.put(currentOffset, laneAttributes);
529             }
530         }
531         return structurewithOffset;
532     }
533 
534     /**
535      * Figure out a reasonable width for a lane.
536      * @param laneAttributes LaneAttributes; the attributes of the lane
537      * @param link OSMLink; the link that owns the lane
538      * @param warningListener WarningListener; the warning listener that receives warning events
539      * @return double; the width (in meters) of the lane
540      */
541     static double laneWidth(final LaneAttributes laneAttributes, final OSMLink link, final WarningListener warningListener)
542     {
543         Double defaultLaneWidth = 3.05d; // TODO This is the German standard car lane width
544         boolean widthOverride = false;
545         for (OSMTag tag : link.getTags())
546         {
547             if (tag.getKey().equals("width"))
548             {
549                 String w = tag.getValue().replace(",", ".");
550                 w = w.replace(" ", "");
551                 w = w.replace("m", "");
552                 w = w.replace("Meter", "");
553                 try
554                 {
555                     defaultLaneWidth = Double.parseDouble(w) / link.getLanes();
556                 }
557                 catch (NumberFormatException nfe)
558                 {
559                     System.err.println("Bad lanewidth: \"" + tag.getValue() + "\"");
560                 }
561                 widthOverride = true;
562             }
563         }
564         LaneType laneType = laneAttributes.getLaneType();
565         if (laneType.isCompatible(CAR))
566         {
567             return defaultLaneWidth;
568         }
569         else if (laneType.isCompatible(BIKE))
570         {
571             return 0.8d; // TODO German default bikepath width
572         }
573         else if (laneType.isCompatible(PEDESTRIAN))
574         {
575             return 0.95d; // TODO German default footpath width
576         }
577         else if (laneType.isCompatible(BOAT))
578         {
579             for (OSMTag tag : link.getTags())
580             {
581                 if (tag.getKey().equals("waterway"))
582                 {
583                     switch (tag.getValue())
584                     {
585                         case "riverbank":
586                             return 1d;
587                         default:
588                             return defaultLaneWidth;
589                     }
590                 }
591                 else
592                 {
593                     return 5d;
594                 }
595             }
596         }
597         if (!widthOverride)
598         {
599             warningListener.warning(new WarningEvent(link, "No width given; using default laneWidth for Link " + link.getId()));
600         }
601         return defaultLaneWidth;
602     }
603 
604     /**
605      * This method creates lanes out of an OSM link LaneTypes are not yet extensive and can be further increased through Tags
606      * provided by OSM. The standard lane width of 3.05 is an estimation based on the European width limitation for vehicles
607      * (2.55m) + 25cm each side.
608      * @param network the network
609      * @param osmlink Link OSMLink; the OSM link to make lanes for
610      * @param simulator OTSDEVSSimulatorInterface; the simulator that will animate the generates lanes (if it happens to be an
611      *            instance of OTSAnimatorInterface)
612      * @param warningListener WarningListener; the warning listener that will receive warning events
613      * @return List&lt;Lane&gt;
614      * @throws NetworkException on network inconsistency
615      * @throws NamingException on naming problems (in the animator)
616      * @throws OTSGeometryException when lane contour or center line cannot be instantiated
617      */
618     public List<Lane> makeLanes(final Network network, final OSMLink osmlink, final OTSDEVSSimulatorInterface simulator,
619             final WarningListener warningListener) throws NetworkException, NamingException, OTSGeometryException
620     {
621         CrossSectionLink otslink = convertLink(network, osmlink, simulator);
622         List<Lane> lanes = new ArrayList<Lane>();
623         Map<Double, LaneAttributes> structure = makeStructure(osmlink, warningListener);
624 
625         int laneNum = 0;
626         for (Double offset : structure.keySet())
627         {
628             laneNum++;
629             LaneAttributes laneAttributes = structure.get(offset);
630             if (laneAttributes == null)
631             {
632                 break;
633             }
634             Color color = Color.LIGHT_GRAY;
635             LaneType laneType = laneAttributes.getLaneType();
636             Length latPos = new Length(offset, LengthUnit.METER);
637             Map<GTUType, LongitudinalDirectionality> directionality = new HashMap<>();
638             directionality.put(GTUType.ALL, laneAttributes.getDirectionality());
639             Map<GTUType, Speed> speedLimit = new HashMap<>();
640             speedLimit.put(GTUType.ALL, new Speed(100, SpeedUnit.KM_PER_HOUR));
641             Lane newLane = null;
642             // FIXME the following code assumes right-hand-side driving.
643             if (osmlink.hasTag("hasPreceding") && offset >= 0 || osmlink.hasTag("hasFollowing") && offset < 0)
644             {
645                 color = Color.RED;
646                 // FIXME overtaking conditions per country and/or type of road?
647                 newLane = new Lane(otslink, "lane." + laneNum, latPos, latPos, laneAttributes.getWidth(),
648                         laneAttributes.getWidth(), laneType, directionality, speedLimit,
649                         new OvertakingConditions.LeftAndRight());
650                 new SinkSensor(newLane, new Length(0.25, LengthUnit.METER), simulator);
651             }
652             else if (osmlink.hasTag("hasPreceding") && offset < 0 || osmlink.hasTag("hasFollowing") && offset >= 0)
653             {
654                 color = Color.BLUE;
655                 // FIXME overtaking conditions per country and/or type of road?
656                 newLane = new Lane(otslink, "lane." + laneNum, latPos, latPos, laneAttributes.getWidth(),
657                         laneAttributes.getWidth(), laneType, directionality, speedLimit,
658                         new OvertakingConditions.LeftAndRight());
659             }
660             else
661             {
662                 color = laneAttributes.getColor();
663                 // FIXME overtaking conditions per country and/or type of road?
664                 newLane = new Lane(otslink, "lane." + laneNum, latPos, latPos, laneAttributes.getWidth(),
665                         laneAttributes.getWidth(), laneType, directionality, speedLimit,
666                         new OvertakingConditions.LeftAndRight());
667             }
668             if (simulator instanceof OTSAnimatorInterface)
669             {
670                 try
671                 {
672                     new LaneAnimation(newLane, simulator, color, false);
673                 }
674                 catch (RemoteException exception)
675                 {
676                     exception.printStackTrace();
677                 }
678             }
679             lanes.add(newLane);
680         }
681         return lanes;
682     }
683 
684     /**
685      * This method creates a LaneType which supports all GTUTypes that have been specified in the GTUType List "GTUs".
686      * @param gtuTypes List&lt;GTUType&lt;String&gt;&gt;; list of GTUTypes
687      * @return LaneType permeable for all of the specific GTUTypes
688      */
689     public static LaneType makeLaneType(final List<GTUType> gtuTypes)
690     {
691         StringBuilder name = new StringBuilder();
692         for (GTUType gtu : gtuTypes)
693         {
694             if (name.length() > 0)
695             {
696                 name.append("|");
697             }
698             name.append(gtu.getId());
699         }
700         LaneType result = new LaneType(name.toString(), gtuTypes);
701         return result;
702     }
703 
704     /**
705      * This method creates a LaneType which supports the specified GTUType.
706      * @param gtuType GTUType; the type of GTU that can travel on the new LaneType
707      * @return LaneType
708      */
709     public static LaneType makeLaneType(final GTUType gtuType)
710     {
711         List<GTUType> gtuTypes = new ArrayList<GTUType>(1);
712         gtuTypes.add(gtuType);
713         return makeLaneType(gtuTypes);
714         // String name = gtuType.getId();
715         // LaneType result = new LaneType(name);
716         // result.addPermeability(gtuType);
717         // return result;
718     }
719 
720     /**
721      * Identify Links that are sources or sinks.
722      * @param nodes List of Nodes
723      * @param links List of Links
724      * @return List of Links which are candidates for becoming sinks/sources.
725      */
726     private static ArrayList<OSMLink> findBoundaryLinks(final List<OSMNode> nodes, final List<OSMLink> links)
727     {
728         // TODO: test performance (memory- and time-wise) when the counters are replaced by ArrayList<OSMLink> which
729         // would obviate the need to do full searches over all Links to find OSMNodes that are source or sink.
730         // Reset the counters (should not be necessary unless this method is called more than once)
731         for (OSMNode node : nodes)
732         {
733             node.linksOriginating = 0;
734             node.linksTerminating = 0;
735         }
736         for (OSMLink link : links)
737         {
738             link.getStart().linksOriginating++;
739             link.getEnd().linksTerminating++;
740         }
741         ArrayList<OSMNode> foundEndNodes = new ArrayList<OSMNode>();
742         for (OSMNode node : nodes)
743         {
744             if (0 == node.linksOriginating && node.linksTerminating > 0
745                     || 0 == node.linksTerminating && node.linksOriginating > 0)
746             {
747                 foundEndNodes.add(node);
748             }
749         }
750         ArrayList<OSMLink> result = new ArrayList<OSMLink>();
751         for (OSMLink link : links)
752         {
753             if (foundEndNodes.contains(link.getStart()) || foundEndNodes.contains(link.getEnd()))
754             {
755                 result.add(link);
756             }
757         }
758         return result;
759     }
760 
761     /**
762      * @param net The OSM network which is to be searched for Sinks and Sources.
763      * @param progressListener ProgressListener; the progress listener that will receive progress events
764      * @return Network with all possible sinks and sources tagged.
765      */
766     public static OSMNetwork findSinksandSources(final OSMNetwork net, final ProgressListener progressListener)
767     {
768         progressListener.progress(new ProgressEvent(net, "Counting number of links at each node"));
769         List<OSMNode> nodes = new ArrayList<OSMNode>();
770         nodes.addAll(net.getNodes().values());
771         ArrayList<OSMLink> foundEndpoints = findBoundaryLinks(nodes, net.getLinks());
772         progressListener.progress(new ProgressEvent(net, "Adding tags to non-sinks and non-sources"));
773         int progress = 0;
774         final int progressReportStep = 5000;
775         // As tags are immutable we make ONE for following and ONE for preceding
776         final OSMTag hasFollowing = new OSMTag("hasFollowing", "");
777         final OSMTag hasPreceding = new OSMTag("hasPreceding", "");
778         for (OSMLink l : net.getLinks())
779         {
780             if (foundEndpoints.contains(l))
781             {
782                 if (net.hasFollowingLink(l))
783                 {
784                     l.addTag(hasFollowing);
785                 }
786                 else if (net.hasPrecedingLink(l))
787                 {
788                     l.addTag(hasPreceding);
789                 }
790             }
791             if (0 == ++progress % progressReportStep)
792             {
793                 progressListener.progress(new ProgressEvent(net, String.format(Locale.US, "%d of %d links processed (%.1f%%)",
794                         progress, net.getLinks().size(), 100.0 * progress / net.getLinks().size())));
795             }
796         }
797         progressListener.progress(new ProgressEvent(net, "Found " + foundEndpoints.size() + " Sinks and Sources."));
798         return net;
799     }
800 
801     /** {@inheritDoc} */
802     @Override
803     public final String toString()
804     {
805         return "Convert []";
806     }
807 }
808 
809 /**
810  * <p>
811  * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
812  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
813  * <p>
814  * $LastChangedDate: 2015-09-16 19:20:07 +0200 (Wed, 16 Sep 2015) $, @version $Revision: 1405 $, by $Author: averbraeck $,
815  * initial version Mar 3, 2015 <br>
816  * @author <a>Moritz Bergmann</a>
817  */
818 class LaneAttributes implements Serializable
819 {
820     /** */
821     private static final long serialVersionUID = 20150303L;
822 
823     /** Type of the lane (immutable). */
824     private final LaneType laneType;
825 
826     /** Drawing color of the lane (immutable). */
827     private final Color color;
828 
829     /** LongitudinalDirectionality of the lane (immutable). */
830     private final LongitudinalDirectionality directionality;
831 
832     /** Width of the lane. */
833     private Length width;
834 
835     /**
836      * @param lt - LaneType
837      * @param c - Color
838      * @param d - LongitudinalDIrectionality
839      */
840     public LaneAttributes(final LaneType lt, final Color c, final LongitudinalDirectionality d)
841     {
842         if (lt == null)
843         {
844             this.laneType = Convert.makeLaneType(GTUType.NONE);
845         }
846         else
847         {
848             this.laneType = lt;
849         }
850         this.color = c;
851         this.directionality = d;
852     }
853 
854     /**
855      * @param laneType - LaneType
856      * @param color - Color
857      * @param directionality - LongitudinalDIrectionality
858      * @param width - width
859      */
860     public LaneAttributes(final LaneType laneType, final Color color, final LongitudinalDirectionality directionality,
861             final Double width)
862     {
863         if (laneType == null)
864         {
865             this.laneType = Convert.makeLaneType(GTUType.NONE);
866         }
867         else
868         {
869             this.laneType = laneType;
870         }
871         this.color = color;
872         this.directionality = directionality;
873         this.setWidth(width);
874     }
875 
876     /**
877      * @return LaneType
878      */
879     public LaneType getLaneType()
880     {
881         return this.laneType;
882     }
883 
884     /**
885      * @return Color
886      */
887     public Color getColor()
888     {
889         return this.color;
890     }
891 
892     /**
893      * @return LongitudinalDirectionality
894      */
895     public LongitudinalDirectionality getDirectionality()
896     {
897         return this.directionality;
898     }
899 
900     /**
901      * @return width.
902      */
903     public Length getWidth()
904     {
905         return this.width;
906     }
907 
908     /**
909      * @param width set width.
910      */
911     public void setWidth(final Double width)
912     {
913         Length w = new Length(width, LengthUnit.METER);
914         this.width = w;
915     }
916 
917     /** {@inheritDoc} */
918     public String toString()
919     {
920         return "Lane Attributes: " + this.laneType + "; " + this.color + "; " + this.directionality + "; " + this.width;
921     }
922 
923 }