View Javadoc
1   package org.opentrafficsim.animation;
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.opentrafficsim.core.network.Link;
17  import org.opentrafficsim.core.network.LinkPosition;
18  import org.opentrafficsim.core.network.NetworkException;
19  import org.opentrafficsim.draw.graphs.GraphCrossSection;
20  import org.opentrafficsim.draw.graphs.GraphPath;
21  import org.opentrafficsim.draw.graphs.GraphPath.Section;
22  import org.opentrafficsim.road.network.lane.CrossSectionLink;
23  import org.opentrafficsim.road.network.lane.Lane;
24  import org.opentrafficsim.road.network.lane.LanePosition;
25  import org.opentrafficsim.road.network.lane.Shoulder;
26  import org.opentrafficsim.road.network.sampling.LaneDataRoad;
27  
28  /**
29   * Utilities to create {@code GraphPath}s and {@code GraphCrossSection}s for graphs, based on lanes.
30   * <p>
31   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
32   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
33   * </p>
34   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
36   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
37   */
38  public final class GraphLaneUtil
39  {
40  
41      /**
42       * Constructor.
43       */
44      private GraphLaneUtil()
45      {
46          //
47      }
48  
49      /**
50       * Creates a path starting at the provided lane and moving downstream until a dead-end, split, or loop.
51       * @param name path name
52       * @param first first lane
53       * @return path
54       * @throws NetworkException when the lane does not have any set speed limit
55       */
56      public static GraphPath<LaneDataRoad> createPath(final String name, final Lane first) throws NetworkException
57      {
58          return createPath(name, first, null);
59      }
60  
61      /**
62       * Creates a path starting at the provided lane and moving downstream until a dead-end, split, or loop.
63       * @param name path name
64       * @param first first lane
65       * @param last last lane
66       * @return path
67       * @throws NetworkException when the lane does not have any set speed limit
68       */
69      public static GraphPath<LaneDataRoad> createPath(final String name, final Lane first, final Lane last)
70              throws NetworkException
71      {
72          Throw.whenNull(name, "Name may not be null.");
73          Throw.whenNull(first, "First may not be null.");
74          List<Section<LaneDataRoad>> sections = new ArrayList<>();
75          Set<Lane> set = new LinkedHashSet<>();
76          Lane lane = first;
77          do
78          {
79              LaneDataRoad laneData = new LaneDataRoad(lane);
80              List<LaneDataRoad> list = new ArrayList<>();
81              list.add(laneData);
82              Speed speed = lane.getLowestSpeedLimit();
83              Length length = lane.getLength();
84              sections.add(new Section<>(length, speed, list));
85              set.add(lane);
86              Set<Lane> nextLaneSet = lane.nextLanes(null);
87              if (nextLaneSet.size() == 1 && !(nextLaneSet.iterator().next() instanceof Shoulder))
88              {
89                  lane = nextLaneSet.iterator().next();
90              }
91          }
92          while (lane != null && !set.contains(lane) && !lane.equals(last));
93          return new GraphPath<>(name, sections);
94      }
95  
96      /**
97       * 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
98       * lanes) and there's a unique link all lanes have downstream. The length and speed limit are taken from the first lane.
99       * @param names lane names
100      * @param first first lanes
101      * @return path
102      * @throws NetworkException when a lane does not have any set speed limit
103      */
104     public static GraphPath<LaneDataRoad> createPath(final List<String> names, final List<Lane> first) throws NetworkException
105     {
106         return createPath(names, first, Collections.emptyList());
107     }
108 
109     /**
110      * 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
111      * lanes) and there's a unique link all lanes have downstream. The length is taken from the first lane. The speed is the
112      * minimum of all lanes in each section.
113      * @param names lane names
114      * @param first first lanes
115      * @param last last lanes
116      * @return path
117      * @throws NetworkException when a lane does not have any set speed limit
118      */
119     public static GraphPath<LaneDataRoad> createPath(final List<String> names, final List<Lane> first, final List<Lane> last)
120             throws NetworkException
121     {
122         Throw.whenNull(names, "Names may not be null.");
123         Throw.whenNull(first, "First may not be null.");
124         Throw.when(names.size() != first.size(), IllegalArgumentException.class, "Size of 'names' and 'first' must be equal.");
125         List<Section<LaneDataRoad>> sections = new ArrayList<>();
126         Set<Lane> seenLanes = new LinkedHashSet<>();
127         List<Lane> currentLanes = first;
128         while (currentLanes != null && Collections.disjoint(seenLanes, currentLanes) && Collections.disjoint(seenLanes, last))
129         {
130             // create next section
131             List<LaneDataRoad> sectionLanes = new ArrayList<>();
132             Speed sectionSpeed = null;
133             for (Lane lane : currentLanes)
134             {
135                 if (lane == null)
136                 {
137                     sectionLanes.add(null);
138                 }
139                 else
140                 {
141                     sectionSpeed = sectionSpeed == null ? lane.getLowestSpeedLimit()
142                             : Speed.min(sectionSpeed, lane.getLowestSpeedLimit());
143                     sectionLanes.add(new LaneDataRoad(lane));
144                     seenLanes.add(lane);
145                 }
146             }
147             Lane firstCurrentLane = currentLanes.stream().filter((l) -> l != null).findFirst().get();
148             Length sectionLength = firstCurrentLane.getLength();
149             sections.add(new Section<>(sectionLength, sectionSpeed, sectionLanes));
150             
151             // per link and then per lane, find the downstream lane
152             Map<Link, List<Lane>> nextLinks = new LinkedHashMap<>();
153             Link link = firstCurrentLane.getLink();
154             for (Link nextLink : link.getEndNode().getLinks())
155             {
156                 if (!link.equals(nextLink)) // only other links
157                 {
158                     List<Lane> nextLanes = new ArrayList<>();
159                     for (Lane currentLane : currentLanes)
160                     {
161                         Set<Lane> nextLanesOfLane = currentLane.nextLanes(null);
162                         int n = 0;
163                         for (Lane nextLane : nextLanesOfLane)
164                         {
165                             if (nextLane.getLink().equals(nextLink))
166                             {
167                                 n++;
168                                 nextLanes.add(nextLane);
169                             }
170                         }
171                         if (n > 1)
172                         {
173                             // multiple downstream lanes of this lane go to this link, this is not allowed
174                             nextLanes.clear();
175                             break;
176                         }
177                         else if (n == 0)
178                         {
179                             nextLanes.add(null);
180                         }
181                     }
182                     if (nextLanes.size() == currentLanes.size())
183                     {
184                         nextLinks.put(nextLink, nextLanes);
185                     }
186                 }
187             }
188             // in case there are multiple downstream links, remove all links for which some lanes had no downstream lane
189             if (nextLinks.size() > 1)
190             {
191                 Iterator<List<Lane>> it = nextLinks.values().iterator();
192                 while (it.hasNext())
193                 {
194                     if (it.next().contains(null))
195                     {
196                         it.remove();
197                     }
198                 }
199             }
200             if (nextLinks.size() == 1)
201             {
202                 currentLanes = nextLinks.values().iterator().next();
203             }
204             else
205             {
206                 currentLanes = null;
207             }
208         }
209         return new GraphPath<>(names, sections);
210     }
211 
212     /**
213      * Creates a single-lane path.
214      * @param name name
215      * @param lane lane
216      * @return path
217      * @throws NetworkException when a lane does not have any set speed limit
218      */
219     public static GraphPath<LaneDataRoad> createSingleLanePath(final String name, final Lane lane) throws NetworkException
220     {
221         List<LaneDataRoad> lanes = new ArrayList<>();
222         lanes.add(new LaneDataRoad(lane));
223         List<Section<LaneDataRoad>> sections = new ArrayList<>();
224         Speed speed = lane.getLowestSpeedLimit();
225         sections.add(new Section<>(lane.getLength(), speed, lanes));
226         return new GraphPath<>(name, sections);
227     }
228 
229     /**
230      * Creates a cross section at the provided lane and position.
231      * @param name name
232      * @param lanePosition lane position
233      * @return cross section
234      * @throws NetworkException when the lane does not have any set speed limit
235      */
236     public static GraphCrossSection<LaneDataRoad> createCrossSection(final String name, final LanePosition lanePosition)
237             throws NetworkException
238     {
239         Throw.whenNull(name, "Name may not be null.");
240         Throw.whenNull(lanePosition, "Lane position may not be null.");
241         List<LaneDataRoad> list = new ArrayList<>();
242         List<String> names = new ArrayList<>();
243         List<Length> positions = new ArrayList<>();
244         names.add(name);
245         positions.add(lanePosition.position());
246         list.add(new LaneDataRoad(lanePosition.lane()));
247         Speed speed = lanePosition.lane().getLowestSpeedLimit();
248         return createCrossSection(names, list, positions, speed);
249     }
250 
251     /**
252      * Creates a cross section at the provided link and position.
253      * @param names lane names
254      * @param linkPosition link position
255      * @return cross section
256      * @throws NetworkException when a lane does not have any set speed limit
257      */
258     public static GraphCrossSection<LaneDataRoad> createCrossSection(final List<String> names, final LinkPosition linkPosition)
259             throws NetworkException
260     {
261         Throw.whenNull(names, "Names may not be null.");
262         Throw.whenNull(linkPosition, "Link position may not be null.");
263         Throw.when(!(linkPosition.link() instanceof CrossSectionLink), IllegalArgumentException.class,
264                 "The link is not a CrossEctionLink.");
265         List<Lane> lanes = ((CrossSectionLink) linkPosition.link()).getLanes();
266         Throw.when(names.size() != lanes.size(), IllegalArgumentException.class,
267                 "Size of 'names' not equal to the number of lanes.");
268         Collections.sort(lanes, new Comparator<Lane>()
269         {
270             /** {@ingeritDoc} */
271             @Override
272             public int compare(final Lane o1, final Lane o2)
273             {
274                 return o1.getOffsetAtBegin().compareTo(o2.getOffsetAtEnd());
275             }
276 
277         });
278         List<LaneDataRoad> list = new ArrayList<>();
279         List<Length> positions = new ArrayList<>();
280         Speed speed = null;
281         for (Lane lane : lanes)
282         {
283             speed = speed == null ? lane.getLowestSpeedLimit() : Speed.min(speed, lane.getLowestSpeedLimit());
284             list.add(new LaneDataRoad(lane));
285             positions.add(lane.getLength().times(linkPosition.fractionalLongitudinalPosition()));
286         }
287         return createCrossSection(names, list, positions, speed);
288     }
289 
290     /**
291      * Creates a cross section.
292      * @param names names
293      * @param lanes lanes
294      * @param positions positions
295      * @param speed speed
296      * @return cross section
297      */
298     public static GraphCrossSection<LaneDataRoad> createCrossSection(final List<String> names, final List<LaneDataRoad> lanes,
299             final List<Length> positions, final Speed speed)
300     {
301         Section<LaneDataRoad> section = new Section<>(lanes.get(0).getLength(), speed, lanes);
302         return new GraphCrossSection<>(names, section, positions);
303     }
304 
305 }