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