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