View Javadoc
1   package org.opentrafficsim.draw.graphs;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.List;
6   import java.util.NoSuchElementException;
7   
8   import org.djunits.value.vdouble.scalar.Duration;
9   import org.djunits.value.vdouble.scalar.Length;
10  import org.djunits.value.vdouble.scalar.Speed;
11  import org.djutils.exceptions.Throw;
12  import org.djutils.immutablecollections.Immutable;
13  import org.djutils.immutablecollections.ImmutableArrayList;
14  import org.djutils.immutablecollections.ImmutableList;
15  import org.djutils.math.means.ArithmeticMean;
16  import org.opentrafficsim.kpi.interfaces.LaneData;
17  import org.opentrafficsim.kpi.sampling.Sampler;
18  import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
19  
20  /**
21   * A {@code GraphPath} defines the spatial dimension of graphs. It has a number of sections, each of which may have one or more
22   * source objects depending on the number of series. For example, a 3-lane road may result in a few sections each having 3
23   * series. Graphs can aggregate the series, or show multiple series.
24   * <p>
25   * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
26   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
27   * </p>
28   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
29   * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
30   * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
31   * @param <S> underlying type of path sections
32   */
33  public class GraphPath<S> extends AbstractGraphSpace<S>
34  {
35  
36      /** Sections. */
37      private final List<Section<S>> sections;
38  
39      /** Start distance per section. */
40      private final List<Length> startDistances = new ArrayList<>();
41  
42      /** Total path length. */
43      private final Length totalLength;
44  
45      /** Mean speed limit over the entire path. */
46      private final Speed speedLimit;
47  
48      /** Whether the path is circular. */
49      private boolean circular = false;
50  
51      /**
52       * Constructor for a one-series path.
53       * @param name name
54       * @param sections sections
55       */
56      public GraphPath(final String name, final List<Section<S>> sections)
57      {
58          this(new ArrayList<String>()
59          {
60              /** */
61              private static final long serialVersionUID = 20181020L;
62              {
63                  add(name);
64              }
65          }, sections);
66      }
67  
68      /**
69       * Constructor.
70       * @param seriesNames names of series
71       * @param sections sections
72       */
73      public GraphPath(final List<String> seriesNames, final List<Section<S>> sections)
74      {
75          super(seriesNames);
76          this.sections = sections;
77          Length cumulativeLength = Length.ZERO;
78          for (Section<S> section : sections)
79          {
80              this.startDistances.add(cumulativeLength);
81              cumulativeLength = cumulativeLength.plus(section.length());
82          }
83          this.totalLength = cumulativeLength;
84          ArithmeticMean<Double, Double> mean = new ArithmeticMean<>();
85          for (Section<S> section : sections)
86          {
87              mean.add(section.speedLimit().si, section.length().si);
88          }
89          this.speedLimit = Speed.ofSI(mean.getMean());
90      }
91  
92      /**
93       * Sets whether this path is circular.
94       * @param circular whether this path is circular
95       * @return this graph path for method chaining
96       */
97      @SuppressWarnings("hiddenfield")
98      public GraphPath<S> setCircular(final boolean circular)
99      {
100         this.circular = circular;
101         return this;
102     }
103 
104     /**
105      * Returns whether this path is circular.
106      * @return whether this path is circular
107      */
108     public boolean isCircular()
109     {
110         return this.circular;
111     }
112 
113     /**
114      * Returns the start distance of the section.
115      * @param section Section&lt;?&gt; section
116      * @return start distance of the section
117      */
118     public Length getStartDistance(final Section<?> section)
119     {
120         int index = this.sections.indexOf(section);
121         Throw.when(index == -1, IllegalArgumentException.class, "Section is not part of the path.");
122         return this.startDistances.get(index);
123     }
124 
125     /**
126      * Returns the total path length.
127      * @return total path length
128      */
129     public Length getTotalLength()
130     {
131         return this.totalLength;
132     }
133 
134     /**
135      * Returns the mean speed over the entire section.
136      * @return mean speed over the entire section
137      */
138     public Speed getSpeedLimit()
139     {
140         return this.speedLimit;
141     }
142 
143     /**
144      * Returns a section.
145      * @param index index of section
146      * @return section
147      */
148     public Section<S> get(final int index)
149     {
150         return this.sections.get(index);
151     }
152 
153     @Override
154     public Iterator<S> iterator()
155     {
156         return new Iterator<S>()
157         {
158 
159             /** Section iterator. */
160             private Iterator<Section<S>> sectionIterator = getSections().iterator();
161 
162             /** Source object iterator per section. */
163             private Iterator<S> sourceIterator = this.sectionIterator.hasNext() ? this.sectionIterator.next().iterator() : null;
164 
165             @Override
166             public boolean hasNext()
167             {
168                 if (this.sourceIterator != null && this.sourceIterator.hasNext())
169                 {
170                     return true;
171                 }
172                 while (this.sectionIterator.hasNext())
173                 {
174                     Iterator<S> it = this.sectionIterator.next().iterator();
175                     if (it.hasNext())
176                     {
177                         this.sourceIterator = it;
178                         return true;
179                     }
180                 }
181                 this.sourceIterator = null;
182                 return false;
183             }
184 
185             @Override
186             public S next()
187             {
188                 Throw.when(!hasNext(), NoSuchElementException.class, "No more element left.");
189                 return this.sourceIterator.next();
190             }
191         };
192     }
193 
194     @Override
195     public Iterator<S> iterator(final int series)
196     {
197         List<S> list = new ArrayList<>();
198         for (Section<S> section : this.sections)
199         {
200             list.add(section.getSource(series));
201         }
202         return new ImmutableArrayList<>(list, Immutable.WRAP).iterator();
203     }
204 
205     /**
206      * Returns an immutable list of the sections.
207      * @return sections
208      */
209     public ImmutableList<Section<S>> getSections()
210     {
211         return new ImmutableArrayList<>(this.sections, Immutable.WRAP);
212     }
213 
214     @Override
215     public String toString()
216     {
217         return "GraphPath [sections=" + this.sections + ", startDistances=" + this.startDistances + ", totalLength="
218                 + this.totalLength + ", speedLimit=" + this.speedLimit + "]";
219     }
220 
221     /**
222      * Start recording along path.
223      * @param sampler sampler
224      * @param path path
225      * @param <L> lane data type
226      */
227     public static <L extends LaneData<L>> void initRecording(final Sampler<?, L> sampler, final GraphPath<L> path)
228     {
229         for (Section<L> section : path.getSections())
230         {
231             for (L lane : section)
232             {
233                 sampler.registerSpaceTimeRegion(new SpaceTimeRegion<>(lane, Length.ZERO, lane.getLength(), Duration.ZERO,
234                         Duration.ofSI(Double.MAX_VALUE)));
235             }
236         }
237     }
238 
239     /**
240      * Class for sections.
241      * <p>
242      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
243      * <br>
244      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
245      * </p>
246      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
247      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
248      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
249      * @param <L> underlying type
250      * @param length length of the section
251      * @param speedLimit speed limit on the section
252      * @param sections list of underlying objects
253      */
254     public record Section<L>(Length length, Speed speedLimit, List<L> sections) implements Iterable<L>
255     {
256         /**
257          * Returns the source object.
258          * @param series number
259          * @return underlying object of the series
260          */
261         public L getSource(final int series)
262         {
263             return this.sections.get(series);
264         }
265 
266         @Override
267         public Iterator<L> iterator()
268         {
269             return this.sections.iterator();
270         }
271     }
272 
273 }