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