View Javadoc
1   package org.opentrafficsim.draw.graphs.road;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.Comparator;
6   import java.util.Iterator;
7   import java.util.LinkedHashMap;
8   import java.util.LinkedHashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  
13  import org.djunits.value.vdouble.scalar.Length;
14  import org.djunits.value.vdouble.scalar.Speed;
15  import org.djutils.exceptions.Throw;
16  import org.djutils.immutablecollections.ImmutableSet;
17  import org.opentrafficsim.core.gtu.GTUDirectionality;
18  import org.opentrafficsim.core.network.DirectedLinkPosition;
19  import org.opentrafficsim.core.network.Link;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.draw.graphs.GraphCrossSection;
22  import org.opentrafficsim.draw.graphs.GraphPath;
23  import org.opentrafficsim.draw.graphs.GraphPath.Section;
24  import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality;
25  import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
26  import org.opentrafficsim.road.network.lane.CrossSectionLink;
27  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
28  import org.opentrafficsim.road.network.lane.Lane;
29  import org.opentrafficsim.road.network.lane.LaneDirection;
30  import org.opentrafficsim.road.network.sampling.LaneData;
31  
32  /**
33   * Utilities to create {@code GraphPath}s and {@code GraphCrossSection}s for graphs, based on lanes.
34   * <p>
35   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
36   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
37   * <p>
38   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 19 okt. 2018 <br>
39   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
40   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
41   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
42   */
43  public final class GraphLaneUtil
44  {
45  
46      /**
47       * Constructor.
48       */
49      private GraphLaneUtil()
50      {
51          //
52      }
53  
54      /**
55       * Creates a path starting at the provided lane and moving downstream until a dead-end, split, or loop.
56       * @param name String; path name
57       * @param first LaneDirection; first lane
58       * @return GraphPath&lt;KpiLaneDirection&gt; path
59       * @throws NetworkException when the lane does not have any set speed limit
60       */
61      public static GraphPath<KpiLaneDirection> createPath(final String name, final LaneDirection first) throws NetworkException
62      {
63          Throw.whenNull(name, "Name may not be null.");
64          Throw.whenNull(first, "First may not be null.");
65          List<Section<KpiLaneDirection>> sections = new ArrayList<>();
66          Set<LaneDirection> set = new LinkedHashSet<>();
67          LaneDirection lane = first;
68          while (lane != null && !set.contains(lane))
69          {
70              KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane.getLane()),
71                      lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
72              List<KpiLaneDirection> list = new ArrayList<>();
73              list.add(kpiLaneDirection);
74              Speed speed = lane.getLane().getLowestSpeedLimit();
75              Length length = lane.getLength();
76              sections.add(new Section<KpiLaneDirection>()
77              {
78                  /** {@inheritDoc} */
79                  @Override
80                  public Iterator<KpiLaneDirection> iterator()
81                  {
82                      return list.iterator();
83                  }
84  
85                  /** {@inheritDoc} */
86                  @Override
87                  public Length getLength()
88                  {
89                      return length;
90                  }
91  
92                  /** {@inheritDoc} */
93                  @Override
94                  public Speed getSpeedLimit()
95                  {
96                      return speed;
97                  }
98  
99                  /** {@inheritDoc} */
100                 @Override
101                 public KpiLaneDirection getSource(final int series)
102                 {
103                     return kpiLaneDirection;
104                 }
105             });
106             set.add(lane);
107             Map<Lane, GTUDirectionality> map = lane.getLane().downstreamLanes(lane.getDirection(), null);
108             if (map.size() == 1)
109             {
110                 Map.Entry<Lane, GTUDirectionality> entry = map.entrySet().iterator().next();
111                 lane = new LaneDirection(entry.getKey(), entry.getValue());
112             }
113         }
114         return new GraphPath<>(name, sections);
115     }
116 
117     /**
118      * Creates a path starting at the provided lanes and moving downstream for as long as no lane finds a loop (on to any of the
119      * lanes) and there's a unique link all lanes have downstream. The length and speed limit are taken from the first lane.
120      * @param names List&lt;String&gt;; lane names
121      * @param first List&lt;LaneDirection&gt;; first lanes
122      * @return GraphPath&lt;KpiLaneDirection&gt; path
123      * @throws NetworkException when a lane does not have any set speed limit
124      */
125     public static GraphPath<KpiLaneDirection> createPath(final List<String> names, final List<LaneDirection> first)
126             throws NetworkException
127     {
128         Throw.whenNull(names, "Names may not be null.");
129         Throw.whenNull(first, "First may not be null.");
130         Throw.when(names.size() != first.size(), IllegalArgumentException.class, "Size of 'names' and 'first' must be equal.");
131         List<Section<KpiLaneDirection>> sections = new ArrayList<>();
132         Set<LaneDirection> set = new LinkedHashSet<>();
133         List<LaneDirection> lanes = first;
134         while (lanes != null && Collections.disjoint(set, lanes))
135         {
136             List<KpiLaneDirection> list = new ArrayList<>();
137             Speed speed = null;
138             for (LaneDirection lane : lanes)
139             {
140                 speed = speed == null ? lane.getLane().getLowestSpeedLimit()
141                         : Speed.min(speed, lane.getLane().getLowestSpeedLimit());
142                 KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane.getLane()),
143                         lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
144                 list.add(kpiLaneDirection);
145             }
146             Speed finalSpeed = speed;
147             Length length = lanes.get(0).getLane().getLength();
148             sections.add(new Section<KpiLaneDirection>()
149             {
150                 /** {@inheritDoc} */
151                 @Override
152                 public Iterator<KpiLaneDirection> iterator()
153                 {
154                     return list.iterator();
155                 }
156 
157                 /** {@inheritDoc} */
158                 @Override
159                 public Length getLength()
160                 {
161                     return length;
162                 }
163 
164                 /** {@inheritDoc} */
165                 @Override
166                 public Speed getSpeedLimit()
167                 {
168                     return finalSpeed;
169                 }
170 
171                 /** {@inheritDoc} */
172                 @Override
173                 public KpiLaneDirection getSource(final int series)
174                 {
175                     return list.get(series);
176                 }
177             });
178             set.addAll(lanes);
179             // per link and then per lane, find the downstream lane
180             Map<Link, List<LaneDirection>> linkMap = new LinkedHashMap<>();
181             Link link = lanes.get(0).getLane().getParentLink();
182             ImmutableSet<Link> links =
183                     (lanes.get(0).getDirection().isPlus() ? link.getEndNode() : link.getStartNode()).getLinks();
184             for (Link nextLink : links)
185             {
186                 if (!link.equals(nextLink))
187                 {
188                     List<LaneDirection> nextLanes = new ArrayList<>();
189                     for (LaneDirection laneDir : lanes)
190                     {
191                         Map<Lane, GTUDirectionality> map = laneDir.getLane().downstreamLanes(laneDir.getDirection(), null);
192                         int n = 0;
193                         for (Map.Entry<Lane, GTUDirectionality> entry : map.entrySet())
194                         {
195                             if (entry.getKey().getParentLink().equals(nextLink))
196                             {
197                                 n++;
198                                 nextLanes.add(new LaneDirection(entry.getKey(), entry.getValue()));
199                             }
200                         }
201                         if (n > 1)
202                         {
203                             // multiple downstream lanes of this lane go to this link, this is not allowed
204                             nextLanes.clear();
205                             break;
206                         }
207                         else if (n == 0)
208                         {
209                             nextLanes.addAll(null);
210                         }
211                     }
212                     if (nextLanes.size() == lanes.size())
213                     {
214                         linkMap.put(nextLink, nextLanes);
215                     }
216                 }
217             }
218             // in case there are multiple downstream links, remove all links for which some lanes had no downstream lane
219             if (linkMap.size() > 1)
220             {
221                 Iterator<List<LaneDirection>> it = linkMap.values().iterator();
222                 while (it.hasNext())
223                 {
224                     if (it.next().contains(null))
225                     {
226                         it.remove();
227                     }
228                 }
229             }
230             if (linkMap.size() == 1)
231             {
232                 lanes = linkMap.values().iterator().next();
233             }
234             else
235             {
236                 lanes = null;
237             }
238         }
239         return new GraphPath<>(names, sections);
240     }
241 
242     /**
243      * Creates a single-lane path.
244      * @param name String; name
245      * @param lane LaneDirection; lane
246      * @return GraphPath&lt;KpiLaneDirection&gt; path
247      * @throws NetworkException when a lane does not have any set speed limit
248      */
249     public static GraphPath<KpiLaneDirection> createSingleLanePath(final String name, final LaneDirection lane)
250             throws NetworkException
251     {
252         List<KpiLaneDirection> lanes = new ArrayList<>();
253         lanes.add(new KpiLaneDirection(new LaneData(lane.getLane()),
254                 lane.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
255         List<Section<KpiLaneDirection>> sections = new ArrayList<>();
256         Speed speed = lane.getLane().getLowestSpeedLimit();
257         sections.add(new Section<KpiLaneDirection>()
258         {
259 
260             /** {@inheritDoc} */
261             @Override
262             public Iterator<KpiLaneDirection> iterator()
263             {
264                 return lanes.iterator();
265             }
266 
267             /** {@inheritDoc} */
268             @Override
269             public Length getLength()
270             {
271                 return lane.getLength();
272             }
273 
274             /** {@inheritDoc} */
275             @Override
276             public Speed getSpeedLimit()
277             {
278                 return speed;
279             }
280 
281             /** {@inheritDoc} */
282             @Override
283             public KpiLaneDirection getSource(final int series)
284             {
285                 return lanes.get(0);
286             }
287 
288         });
289         return new GraphPath<>(name, sections);
290     }
291 
292     /**
293      * Creates a cross section at the provided lane and position.
294      * @param name String; name
295      * @param lanePosition DirectedLanePosition; lane position
296      * @return GraphCrossSection&lt;KpiLaneDirection&gt; cross section
297      * @throws NetworkException when the lane does not have any set speed limit
298      */
299     public static GraphCrossSection<KpiLaneDirection> createCrossSection(final String name,
300             final DirectedLanePosition lanePosition) throws NetworkException
301     {
302         Throw.whenNull(name, "Name may not be null.");
303         Throw.whenNull(lanePosition, "Lane position may not be null.");
304         List<KpiLaneDirection> list = new ArrayList<>();
305         List<String> names = new ArrayList<>();
306         List<Length> positions = new ArrayList<>();
307         names.add(name);
308         positions.add(lanePosition.getPosition());
309         list.add(new KpiLaneDirection(new LaneData(lanePosition.getLane()),
310                 lanePosition.getGtuDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
311         Speed speed = lanePosition.getLane().getLowestSpeedLimit();
312         return createCrossSection(names, list, positions, speed);
313     }
314 
315     /**
316      * Creates a cross section at the provided link and position.
317      * @param names List&lt;String&gt;; lane names
318      * @param linkPosition DirectedLinkPosition; link position
319      * @return GraphCrossSection&lt;KpiLaneDirection&gt; cross section
320      * @throws NetworkException when a lane does not have any set speed limit
321      */
322     public static GraphCrossSection<KpiLaneDirection> createCrossSection(final List<String> names,
323             final DirectedLinkPosition linkPosition) throws NetworkException
324     {
325         Throw.whenNull(names, "Names may not be null.");
326         Throw.whenNull(linkPosition, "Link position may not be null.");
327         Throw.when(!(linkPosition.getLink() instanceof CrossSectionLink), IllegalArgumentException.class,
328                 "The link is not a CrossEctionLink.");
329         List<Lane> lanes = ((CrossSectionLink) linkPosition.getLink()).getLanes();
330         Throw.when(names.size() != lanes.size(), IllegalArgumentException.class,
331                 "Size of 'names' not equal to the number of lanes.");
332         Collections.sort(lanes, new Comparator<Lane>()
333         {
334             /** {@ingeritDoc} */
335             @Override
336             public int compare(final Lane o1, final Lane o2)
337             {
338                 int comp = o1.getDesignLineOffsetAtBegin().compareTo(o2.getDesignLineOffsetAtEnd());
339                 return linkPosition.getDirection().isPlus() ? comp : -comp;
340             }
341 
342         });
343         List<KpiLaneDirection> list = new ArrayList<>();
344         List<Length> positions = new ArrayList<>();
345         Speed speed = null;
346         for (Lane lane : lanes)
347         {
348             speed = speed == null ? lane.getLowestSpeedLimit() : Speed.min(speed, lane.getLowestSpeedLimit());
349             list.add(new KpiLaneDirection(new LaneData(lane),
350                     linkPosition.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS));
351             positions.add(lane.getLength().multiplyBy(linkPosition.getFractionalLongitudinalPosition()));
352         }
353         return createCrossSection(names, list, positions, speed);
354     }
355 
356     /**
357      * Creates a cross section.
358      * @param names List&lt;String&gt;;; names
359      * @param lanes List&lt;KpiLaneDirection&gt;;; lanes
360      * @param positions List&lt;Length&gt;; positions
361      * @param speed Speed; speed
362      * @return GraphCrossSection&lt;KpiLaneDirection&gt;; cross section
363      */
364     public static GraphCrossSection<KpiLaneDirection> createCrossSection(final List<String> names,
365             final List<KpiLaneDirection> lanes, final List<Length> positions, final Speed speed)
366     {
367         Section<KpiLaneDirection> section = new Section<KpiLaneDirection>()
368         {
369             /** {@inheritDoc} */
370             @Override
371             public Iterator<KpiLaneDirection> iterator()
372             {
373                 return lanes.iterator();
374             }
375 
376             /** {@inheritDoc} */
377             @Override
378             public Length getLength()
379             {
380                 return lanes.get(0).getLaneData().getLength();
381             }
382 
383             /** {@inheritDoc} */
384             @Override
385             public Speed getSpeedLimit()
386             {
387                 return speed;
388             }
389 
390             /** {@inheritDoc} */
391             @Override
392             public KpiLaneDirection getSource(final int series)
393             {
394                 return lanes.get(series);
395             }
396         };
397         return new GraphCrossSection<>(names, section, positions);
398     }
399 
400 }