View Javadoc
1   package org.opentrafficsim.draw.graphs;
2   
3   import java.time.Period;
4   import java.util.ArrayList;
5   import java.util.EnumSet;
6   import java.util.Iterator;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  import java.util.SortedSet;
12  import java.util.TreeSet;
13  
14  import org.djunits.value.vdouble.scalar.Duration;
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.djutils.exceptions.Throw;
18  import org.jfree.chart.JFreeChart;
19  import org.jfree.chart.LegendItem;
20  import org.jfree.chart.LegendItemCollection;
21  import org.jfree.chart.axis.NumberAxis;
22  import org.jfree.chart.plot.XYPlot;
23  import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
24  import org.jfree.data.DomainOrder;
25  import org.jfree.data.xy.XYDataset;
26  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
27  import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
28  import org.opentrafficsim.kpi.sampling.Sampler;
29  import org.opentrafficsim.kpi.sampling.SamplingException;
30  import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
31  import org.opentrafficsim.kpi.sampling.Trajectory;
32  import org.opentrafficsim.kpi.sampling.Trajectory.SpaceTimeView;
33  import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
34  
35  /**
36   * Fundamental diagram from various sources.
37   * <p>
38   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
39   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
40   * <p>
41   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 okt. 2018 <br>
42   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
43   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
44   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
45   */
46  public class FundamentalDiagram extends AbstractBoundedPlot implements XYDataset
47  {
48  
49      /** Aggregation periods. */
50      public static final double[] DEFAULT_PERIODS = new double[] {5.0, 10.0, 30.0, 60.0, 120.0, 300.0, 900.0};
51  
52      /** Update frequencies (n * 1/period). */
53      public static final int[] DEFAULT_UPDATE_FREQUENCIES = new int[] {1, 2, 3, 5, 10};
54  
55      /** Source providing the data. */
56      private final FdSource source;
57  
58      /** Quantity on domain axis. */
59      private Quantity domainQuantity;
60  
61      /** Quantity on range axis. */
62      private Quantity rangeQuantity;
63  
64      /** The other, 3rd quantity. */
65      private Quantity otherQuantity;
66  
67      /** Labels of series. */
68      private final List<String> seriesLabels = new ArrayList<>();
69  
70      /** Updater for update times. */
71      private final GraphUpdater<Time> graphUpdater;
72  
73      /** Property for chart listener to provide time info for status label. */
74      private String timeInfo = "";
75  
76      /** Legend to change text color to indicate visibility. */
77      private LegendItemCollection legend;
78  
79      /** Whether each lane is visible or not. */
80      private final List<Boolean> laneVisible = new ArrayList<>();
81  
82      /**
83       * Constructor.
84       * @param caption String; caption
85       * @param domainQuantity Quantity; initial quantity on the domain axis
86       * @param rangeQuantity Quantity; initial quantity on the range axis
87       * @param simulator OTSSimulatorInterface; simulator
88       * @param source FdSource; source providing the data
89       */
90      public FundamentalDiagram(final String caption, final Quantity domainQuantity, final Quantity rangeQuantity,
91              final OTSSimulatorInterface simulator, final FdSource source)
92      {
93          super(simulator, caption, source.getUpdateInterval(), source.getDelay());
94          Throw.when(domainQuantity.equals(rangeQuantity), IllegalArgumentException.class,
95                  "Domain and range quantity should not be equal.");
96          this.setDomainQuantity(domainQuantity);
97          this.setRangeQuantity(rangeQuantity);
98          Set<Quantity> quantities = EnumSet.allOf(Quantity.class);
99          quantities.remove(domainQuantity);
100         quantities.remove(rangeQuantity);
101         this.setOtherQuantity(quantities.iterator().next());
102         this.source = source;
103         for (int series = 0; series < source.getNumberOfSeries(); series++)
104         {
105             this.seriesLabels.add(series, source.getName(series));
106             this.laneVisible.add(true);
107         }
108         setChart(createChart());
109         setLowerDomainBound(0.0);
110         setLowerRangeBound(0.0);
111 
112         // setup updater to do the actual work in another thread
113         this.graphUpdater = new GraphUpdater<>("Fundamental diagram worker", Thread.currentThread(), (t) ->
114         {
115             if (this.getSource() != null)
116             {
117                 this.getSource().increaseTime(t);
118                 notifyPlotChange();
119             }
120         });
121     }
122 
123     /**
124      * Constructor using a sampler as source.
125      * @param caption String; caption
126      * @param domainQuantity Quantity; initial quantity on the domain axis
127      * @param rangeQuantity Quantity; initial quantity on the range axis
128      * @param simulator OTSSimulatorInterface; simulator
129      * @param sampler Sampler&lt;?&gt;; sampler
130      * @param crossSection GraphCrossSection&lt;KpiLaneDirection&gt;; lanes
131      * @param aggregateLanes boolean; whether to aggregate the positions
132      * @param aggregationTime Duration; aggregation time (and update time)
133      * @param harmonic boolean; harmonic mean
134      */
135     @SuppressWarnings("parameternumber")
136     public FundamentalDiagram(final String caption, final Quantity domainQuantity, final Quantity rangeQuantity,
137             final OTSSimulatorInterface simulator, final Sampler<?> sampler,
138             final GraphCrossSection<KpiLaneDirection> crossSection, final boolean aggregateLanes,
139             final Duration aggregationTime, final boolean harmonic)
140     {
141         this(caption, domainQuantity, rangeQuantity, simulator,
142                 sourceFromSampler(sampler, crossSection, aggregateLanes, aggregationTime, harmonic));
143     }
144 
145     /**
146      * Constructor using a sampler as source.
147      * @param caption String; caption
148      * @param domainQuantity Quantity; initial quantity on the domain axis
149      * @param rangeQuantity Quantity; initial quantity on the range axis
150      * @param simulator OTSSimulatorInterface; simulator
151      * @param sampler Sampler&lt;?&gt;; sampler
152      * @param path GraphPath&lt;KpiLaneDirection&gt;; lanes
153      * @param aggregateLanes boolean; whether to aggregate the positions
154      * @param aggregationTime Duration; aggregation time (and update time)
155      */
156     @SuppressWarnings("parameternumber")
157     public FundamentalDiagram(final String caption, final Quantity domainQuantity, final Quantity rangeQuantity,
158             final OTSSimulatorInterface simulator, final Sampler<?> sampler, final GraphPath<KpiLaneDirection> path,
159             final boolean aggregateLanes, final Duration aggregationTime)
160     {
161         this(caption, domainQuantity, rangeQuantity, simulator,
162                 sourceFromSampler(sampler, path, aggregateLanes, aggregationTime));
163     }
164 
165     /**
166      * Create a chart.
167      * @return JFreeChart; chart
168      */
169     private JFreeChart createChart()
170     {
171         NumberAxis xAxis = new NumberAxis(this.getDomainQuantity().label());
172         NumberAxis yAxis = new NumberAxis(this.getRangeQuantity().label());
173         XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer()
174         {
175             /** */
176             private static final long serialVersionUID = 20181022L;
177 
178             /** {@inheritDoc} */
179             @SuppressWarnings("synthetic-access")
180             @Override
181             public boolean isSeriesVisible(final int series)
182             {
183                 return FundamentalDiagram.this.laneVisible.get(series);
184             }
185 
186         }; // XYDotRenderer doesn't support different markers
187         renderer.setDefaultLinesVisible(false);
188         XYPlot plot = new XYPlot(this, xAxis, yAxis, renderer);
189         boolean showLegend = true;
190         if (this.getSource().getNumberOfSeries() < 2)
191         {
192             plot.setFixedLegendItems(null);
193             showLegend = false;
194         }
195         else
196         {
197             this.legend = new LegendItemCollection();
198             for (int i = 0; i < this.getSource().getNumberOfSeries(); i++)
199             {
200                 LegendItem li = new LegendItem(this.getSource().getName(i));
201                 li.setSeriesKey(i); // lane series, not curve series
202                 li.setShape(renderer.lookupLegendShape(i));
203                 li.setFillPaint(renderer.lookupSeriesPaint(i));
204                 this.legend.add(li);
205             }
206             plot.setFixedLegendItems(this.legend);
207             showLegend = true;
208         }
209         return new JFreeChart(getCaption(), JFreeChart.DEFAULT_TITLE_FONT, plot, showLegend);
210     }
211 
212     /** {@inheritDoc} */
213     @Override
214     protected void increaseTime(final Time time)
215     {
216         if (this.graphUpdater != null && time.si >= this.getSource().getAggregationPeriod().si) // null during construction
217         {
218             this.graphUpdater.offer(time);
219         }
220     }
221 
222     /** {@inheritDoc} */
223     @Override
224     public int getSeriesCount()
225     {
226         if (this.getSource() == null)
227         {
228             return 0;
229         }
230         return this.getSource().getNumberOfSeries();
231     }
232 
233     /** {@inheritDoc} */
234     @Override
235     public Comparable<String> getSeriesKey(final int series)
236     {
237         return this.seriesLabels.get(series);
238     }
239 
240     /** {@inheritDoc} */
241     @SuppressWarnings("rawtypes")
242     @Override
243     public int indexOf(final Comparable seriesKey)
244     {
245         int index = this.seriesLabels.indexOf(seriesKey);
246         return index < 0 ? 0 : index;
247     }
248 
249     /** {@inheritDoc} */
250     @Override
251     public DomainOrder getDomainOrder()
252     {
253         return DomainOrder.NONE;
254     }
255 
256     /** {@inheritDoc} */
257     @Override
258     public int getItemCount(final int series)
259     {
260         return this.getSource().getItemCount(series);
261     }
262 
263     /** {@inheritDoc} */
264     @Override
265     public Number getX(final int series, final int item)
266     {
267         return getXValue(series, item);
268     }
269 
270     /** {@inheritDoc} */
271     @Override
272     public double getXValue(final int series, final int item)
273     {
274         return this.getDomainQuantity().getValue(this.getSource(), series, item);
275     }
276 
277     /** {@inheritDoc} */
278     @Override
279     public Number getY(final int series, final int item)
280     {
281         return getYValue(series, item);
282     }
283 
284     /** {@inheritDoc} */
285     @Override
286     public double getYValue(final int series, final int item)
287     {
288         return this.getRangeQuantity().getValue(this.getSource(), series, item);
289     }
290 
291     /** {@inheritDoc} */
292     @Override
293     public GraphType getGraphType()
294     {
295         return GraphType.FUNDAMENTAL_DIAGRAM;
296     }
297 
298     /** {@inheritDoc} */
299     @Override
300     public String getStatusLabel(final double domainValue, final double rangeValue)
301     {
302         return this.getDomainQuantity().format(domainValue) + ", " + this.getRangeQuantity().format(rangeValue) + ", "
303                 + this.getOtherQuantity().format(this.getDomainQuantity().computeOther(this.getRangeQuantity(), domainValue, rangeValue))
304                 + this.getTimeInfo();
305     }
306 
307     /**
308      * Quantity enum defining density, flow and speed.
309      * <p>
310      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
311      * <br>
312      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
313      * <p>
314      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 okt. 2018 <br>
315      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
316      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
317      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
318      */
319     public enum Quantity
320     {
321         /** Density. */
322         DENSITY
323         {
324             /** {@inheritDoc} */
325             @Override
326             public String label()
327             {
328                 return "Density [veh/km] \u2192";
329             }
330 
331             /** {@inheritDoc} */
332             @Override
333             public String format(final double value)
334             {
335                 return String.format("%.0f veh/km", value);
336             }
337 
338             /** {@inheritDoc} */
339             @Override
340             public double getValue(final FdSource src, final int series, final int item)
341             {
342                 return 1000 * src.getDensity(series, item);
343             }
344 
345             /** {@inheritDoc} */
346             @Override
347             public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
348             {
349                 // .......................... speed = flow / density .. flow = density * speed
350                 return pairing.equals(FLOW) ? pairedValue / thisValue : thisValue * pairedValue;
351             }
352         },
353 
354         /** Flow. */
355         FLOW
356         {
357             /** {@inheritDoc} */
358             @Override
359             public String label()
360             {
361                 return "Flow [veh/h] \u2192";
362             }
363 
364             /** {@inheritDoc} */
365             @Override
366             public String format(final double value)
367             {
368                 return String.format("%.0f veh/h", value);
369             }
370 
371             /** {@inheritDoc} */
372             @Override
373             public double getValue(final FdSource src, final int series, final int item)
374             {
375                 return 3600 * src.getFlow(series, item);
376             }
377 
378             /** {@inheritDoc} */
379             @Override
380             public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
381             {
382                 // speed = flow * density ... density = flow / speed
383                 return thisValue / pairedValue;
384             }
385         },
386 
387         /** Speed. */
388         SPEED
389         {
390             /** {@inheritDoc} */
391             @Override
392             public String label()
393             {
394                 return "Speed [km/h] \u2192";
395             }
396 
397             /** {@inheritDoc} */
398             @Override
399             public String format(final double value)
400             {
401                 return String.format("%.1f km/h", value);
402             }
403 
404             /** {@inheritDoc} */
405             @Override
406             public double getValue(final FdSource src, final int series, final int item)
407             {
408                 return 3.6 * src.getSpeed(series, item);
409             }
410 
411             /** {@inheritDoc} */
412             @Override
413             public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
414             {
415                 // ............................. flow = speed * density .. density = flow / speed
416                 return pairing.equals(DENSITY) ? thisValue * pairedValue : pairedValue / thisValue;
417             }
418         };
419 
420         /**
421          * Returns an axis label of the quantity.
422          * @return String; axis label of the quantity
423          */
424         public abstract String label();
425 
426         /**
427          * Formats a value for status display.
428          * @param value double; value
429          * @return String; formatted string including quantity
430          */
431         public abstract String format(double value);
432 
433         /**
434          * Get scaled value in presentation unit.
435          * @param src FdSource; the data source
436          * @param series int; series number
437          * @param item int; item number in series
438          * @return double; scaled value in presentation unit
439          */
440         public abstract double getValue(FdSource src, int series, int item);
441 
442         /**
443          * Compute the value of the 3rd quantity.
444          * @param pairing Quantity; quantity on other axis
445          * @param thisValue double; value of this quantity
446          * @param pairedValue double; value of the paired quantity on the other axis
447          * @return double; value of the 3rd quantity
448          */
449         public abstract double computeOther(Quantity pairing, double thisValue, double pairedValue);
450 
451     }
452 
453     /**
454      * Data source for a fundamental diagram.
455      * <p>
456      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
457      * <br>
458      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
459      * <p>
460      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 okt. 2018 <br>
461      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
462      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
463      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
464      */
465     public interface FdSource
466     {
467         /**
468          * Returns the possible intervals.
469          * @return double[]; possible intervals
470          */
471         default double[] getPossibleAggregationPeriods()
472         {
473             return DEFAULT_PERIODS;
474         }
475 
476         /**
477          * Returns the possible frequencies, as a factor on 1 / 'aggregation interval'.
478          * @return int[]; possible frequencies
479          */
480         default int[] getPossibleUpdateFrequencies()
481         {
482             return DEFAULT_UPDATE_FREQUENCIES;
483         }
484 
485         /**
486          * The update interval.
487          * @return Duration; update interval
488          */
489         Duration getUpdateInterval();
490 
491         /**
492          * Changes the update interval.
493          * @param interval Duration; update interval
494          * @param time Time; time until which data has to be recalculated
495          * @param fd FundamentalDiagram; the fundamental diagram to notify when data is ready
496          */
497         void setUpdateInterval(Duration interval, Time time, FundamentalDiagram fd);
498 
499         /**
500          * The aggregation period.
501          * @return Duration; aggregation period
502          */
503         Duration getAggregationPeriod();
504 
505         /**
506          * Changes the aggregation period.
507          * @param period Duration; aggregation period
508          */
509         void setAggregationPeriod(Duration period);
510 
511         /**
512          * Return the delay for graph updates so future influencing events have occurred, e.d. GTU move's.
513          * @return Duration; graph delay
514          */
515         Duration getDelay();
516 
517         /**
518          * Increase the time span.
519          * @param time Time; time to increase to
520          */
521         void increaseTime(Time time);
522 
523         /**
524          * Returns the number of series (i.e. lanes or 1 for aggregated).
525          * @return int; number of series
526          */
527         int getNumberOfSeries();
528 
529         /**
530          * Returns a name of the series.
531          * @param series int; series number
532          * @return String; name of the series
533          */
534         String getName(int series);
535 
536         /**
537          * Returns the number of items in the series.
538          * @param series int; series number
539          * @return int; number of items in the series
540          */
541         int getItemCount(int series);
542 
543         /**
544          * Return the SI flow value of item in series.
545          * @param series int; series number
546          * @param item int; item number in the series
547          * @return double; SI flow value of item in series
548          */
549         double getFlow(int series, int item);
550 
551         /**
552          * Return the SI density value of item in series.
553          * @param series int; series number
554          * @param item int; item number in the series
555          * @return double; SI density value of item in series
556          */
557         double getDensity(int series, int item);
558 
559         /**
560          * Return the SI speed value of item in series.
561          * @param series int; series number
562          * @param item int; item number in the series
563          * @return double; SI speed value of item in series
564          */
565         double getSpeed(int series, int item);
566     }
567 
568     /**
569      * Creates a {@code Source} from a sampler and positions.
570      * @param sampler Sampler&lt;?&gt;; sampler
571      * @param crossSection GraphCrossSection&lt;KpiLaneDirection&gt;; cross section
572      * @param aggregateLanes boolean; whether to aggregate the positions
573      * @param aggregationTime Duration; aggregation time (and update time)
574      * @param harmonic boolean; harmonic mean
575      * @return Source; source for a fundamental diagram from a sampler and positions
576      */
577     @SuppressWarnings("methodlength")
578     public static FdSource sourceFromSampler(final Sampler<?> sampler, final GraphCrossSection<KpiLaneDirection> crossSection,
579             final boolean aggregateLanes, final Duration aggregationTime, final boolean harmonic)
580     {
581         return new CrossSectionSamplerFdSource<>(sampler, crossSection, aggregateLanes, aggregationTime, harmonic);
582     }
583 
584     /**
585      * Creates a {@code Source} from a sampler and positions.
586      * @param sampler Sampler&lt;?&gt;; sampler
587      * @param path GraphPath&lt;KpiLaneDirection&gt;; cross section
588      * @param aggregateLanes boolean; whether to aggregate the positions
589      * @param aggregationTime Duration; aggregation time (and update time)
590      * @return Source; source for a fundamental diagram from a sampler and positions
591      */
592     public static FdSource sourceFromSampler(final Sampler<?> sampler, final GraphPath<KpiLaneDirection> path,
593             final boolean aggregateLanes, final Duration aggregationTime)
594     {
595         return new PathSamplerFdSource<>(sampler, path, aggregateLanes, aggregationTime);
596     }
597 
598     /**
599      * Fundamental diagram source based on a cross section.
600      * <p>
601      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
602      * <br>
603      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
604      * <p>
605      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 23 okt. 2018 <br>
606      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
607      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
608      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
609      * @param <S> underlying source type
610      */
611     private static class CrossSectionSamplerFdSource<S extends GraphCrossSection<? extends KpiLaneDirection>>
612             extends AbstractSpaceSamplerFdSource<S>
613     {
614         /** Harmonic mean. */
615         private final boolean harmonic;
616 
617         /**
618          * Constructor.
619          * @param sampler Sampler&lt;?&gt;; sampler
620          * @param crossSection S; cross section
621          * @param aggregateLanes boolean; whether to aggregate the lanes
622          * @param aggregationPeriod Duration; initial aggregation {@link Period}
623          * @param harmonic boolean; harmonic mean
624          */
625         CrossSectionSamplerFdSource(final Sampler<?> sampler, final S crossSection, final boolean aggregateLanes,
626                 final Duration aggregationPeriod, final boolean harmonic)
627         {
628             super(sampler, crossSection, aggregateLanes, aggregationPeriod);
629             this.harmonic = harmonic;
630         }
631 
632         /** {@inheritDoc} */
633         @Override
634         protected void getMeasurements(final Trajectory<?> trajectory, final Time startTime, final Time endTime,
635                 final Length length, final int series, final double[] measurements)
636         {
637             Length x = getSpace().position(series);
638             if (GraphUtil.considerTrajectory(trajectory, x, x))
639             {
640                 // detailed check
641                 Time t = trajectory.getTimeAtPosition(x);
642                 if (t.si >= startTime.si && t.si < endTime.si)
643                 {
644                     measurements[0] = 1; // first = count
645                     measurements[1] = // second = sum of (inverted) speeds
646                             this.harmonic ? 1.0 / trajectory.getSpeedAtPosition(x).si : trajectory.getSpeedAtPosition(x).si;
647                 }
648             }
649         }
650 
651         /** {@inheritDoc} */
652         @Override
653         protected double getVehicleCount(final double first, final double second)
654         {
655             return first; // is divided by aggregation period by caller
656         }
657 
658         /** {@inheritDoc} */
659         @Override
660         protected double getSpeed(final double first, final double second)
661         {
662             return this.harmonic ? first / second : second / first;
663         }
664 
665         /** {@inheritDoc} */
666         @Override
667         public String toString()
668         {
669             return "CrossSectionSamplerFdSource [harmonic=" + this.harmonic + "]";
670         }
671 
672     }
673 
674     /**
675      * Fundamental diagram source based on a path. Density, speed and flow over the entire path are calculated per lane.
676      * <p>
677      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
678      * <br>
679      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
680      * <p>
681      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 23 okt. 2018 <br>
682      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
683      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
684      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
685      * @param <S> underlying source type
686      */
687     private static class PathSamplerFdSource<S extends GraphPath<? extends KpiLaneDirection>>
688             extends AbstractSpaceSamplerFdSource<S>
689     {
690         /**
691          * Constructor.
692          * @param sampler Sampler&lt;?&gt;; sampler
693          * @param path S; path
694          * @param aggregateLanes boolean; whether to aggregate the lanes
695          * @param aggregationPeriod Duration; initial aggregation period
696          */
697         PathSamplerFdSource(final Sampler<?> sampler, final S path, final boolean aggregateLanes,
698                 final Duration aggregationPeriod)
699         {
700             super(sampler, path, aggregateLanes, aggregationPeriod);
701         }
702 
703         /** {@inheritDoc} */
704         @Override
705         protected void getMeasurements(final Trajectory<?> trajectory, final Time startTime, final Time endTime,
706                 final Length length, final int sereies, final double[] measurements)
707         {
708             SpaceTimeView stv = trajectory.getSpaceTimeView(Length.ZERO, length, startTime, endTime);
709             measurements[0] = stv.getDistance().si; // first = total traveled distance
710             measurements[1] = stv.getTime().si; // second = total traveled time
711         }
712 
713         /** {@inheritDoc} */
714         @Override
715         protected double getVehicleCount(final double first, final double second)
716         {
717             return first / getSpace().getTotalLength().si; // is divided by aggregation period by caller
718         }
719 
720         /** {@inheritDoc} */
721         @Override
722         protected double getSpeed(final double first, final double second)
723         {
724             return first / second;
725         }
726 
727         /** {@inheritDoc} */
728         @Override
729         public String toString()
730         {
731             return "PathSamplerFdSource []";
732         }
733 
734     }
735 
736     /**
737      * Abstract class that deals with updating and recalculating the fundamental diagram.
738      * <p>
739      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
740      * <br>
741      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
742      * <p>
743      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 23 okt. 2018 <br>
744      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
745      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
746      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
747      * @param <S> underlying source type
748      */
749     private abstract static class AbstractSpaceSamplerFdSource<S extends AbstractGraphSpace<? extends KpiLaneDirection>>
750             implements FdSource
751     {
752         /** Period number of last calculated period. */
753         private int periodNumber = -1;
754 
755         /** Update interval. */
756         private Duration updateInterval;
757 
758         /** Aggregation period. */
759         private Duration aggregationPeriod;
760 
761         /** Number of series. */
762         private final int nSeries;
763 
764         /** First data. */
765         private double[][] firstMeasurement;
766 
767         /** Second data. */
768         private double[][] secondMeasurement;
769 
770         /** Whether the plot is in a process such that the data is invalid for the current draw of the plot. */
771         private boolean invalid = false;
772 
773         /** The sampler. */
774         private final Sampler<?> sampler;
775 
776         /** Space. */
777         private final S space;
778 
779         /** Whether to aggregate the lanes. */
780         private final boolean aggregateLanes;
781 
782         /** For each series (lane), the highest trajectory number (n) below which all trajectories were also handled (0:n). */
783         private Map<KpiLaneDirection, Integer> lastConsecutivelyAssignedTrajectories = new LinkedHashMap<>();
784 
785         /** For each series (lane), a list of handled trajectories above n, excluding n+1. */
786         private Map<KpiLaneDirection, SortedSet<Integer>> assignedTrajectories = new LinkedHashMap<>();
787 
788         /**
789          * Constructor.
790          * @param sampler Sampler&lt;?&gt;; sampler
791          * @param space S; space
792          * @param aggregateLanes boolean; whether to aggregate the lanes
793          * @param aggregationPeriod Duration; initial aggregation period
794          */
795         AbstractSpaceSamplerFdSource(final Sampler<?> sampler, final S space, final boolean aggregateLanes,
796                 final Duration aggregationPeriod)
797         {
798             this.sampler = sampler;
799             this.space = space;
800             this.aggregateLanes = aggregateLanes;
801             this.nSeries = aggregateLanes ? 1 : space.getNumberOfSeries();
802             // create and register kpi lane directions
803             for (KpiLaneDirection laneDirection : space)
804             {
805                 sampler.registerSpaceTimeRegion(new SpaceTimeRegion(laneDirection, Length.ZERO,
806                         laneDirection.getLaneData().getLength(), Time.ZERO, Time.instantiateSI(Double.MAX_VALUE)));
807 
808                 // info per kpi lane direction
809                 this.lastConsecutivelyAssignedTrajectories.put(laneDirection, -1);
810                 this.assignedTrajectories.put(laneDirection, new TreeSet<>());
811             }
812 
813             this.updateInterval = aggregationPeriod;
814             this.aggregationPeriod = aggregationPeriod;
815             this.firstMeasurement = new double[this.nSeries][10];
816             this.secondMeasurement = new double[this.nSeries][10];
817         }
818 
819         /**
820          * Returns the space.
821          * @return S; space
822          */
823         protected S getSpace()
824         {
825             return this.space;
826         }
827 
828         /** {@inheritDoc} */
829         @Override
830         public Duration getUpdateInterval()
831         {
832             return this.updateInterval;
833         }
834 
835         /** {@inheritDoc} */
836         @Override
837         public void setUpdateInterval(final Duration interval, final Time time, final FundamentalDiagram fd)
838         {
839             if (this.updateInterval != interval)
840             {
841                 this.updateInterval = interval;
842                 recalculate(time, fd);
843             }
844         }
845 
846         /** {@inheritDoc} */
847         @Override
848         public Duration getAggregationPeriod()
849         {
850             return this.aggregationPeriod;
851         }
852 
853         /** {@inheritDoc} */
854         @Override
855         public void setAggregationPeriod(final Duration period)
856         {
857             if (this.aggregationPeriod != period)
858             {
859                 this.aggregationPeriod = period;
860             }
861         }
862 
863         /**
864          * Recalculates the data after the aggregation or update time was changed.
865          * @param time Time; time up to which recalculation is required
866          * @param fd FundamentalDiagram; fundamental diagram to notify
867          */
868         private void recalculate(final Time time, final FundamentalDiagram fd)
869         {
870             new Thread(new Runnable()
871             {
872                 @Override
873                 @SuppressWarnings("synthetic-access")
874                 public void run()
875                 {
876                     synchronized (AbstractSpaceSamplerFdSource.this)
877                     {
878                         // an active plot draw will now request data on invalid items
879                         AbstractSpaceSamplerFdSource.this.invalid = true;
880                         AbstractSpaceSamplerFdSource.this.periodNumber = -1;
881                         AbstractSpaceSamplerFdSource.this.updateInterval = getUpdateInterval();
882                         AbstractSpaceSamplerFdSource.this.firstMeasurement =
883                                 new double[AbstractSpaceSamplerFdSource.this.nSeries][10];
884                         AbstractSpaceSamplerFdSource.this.secondMeasurement =
885                                 new double[AbstractSpaceSamplerFdSource.this.nSeries][10];
886                         AbstractSpaceSamplerFdSource.this.lastConsecutivelyAssignedTrajectories.clear();
887                         AbstractSpaceSamplerFdSource.this.assignedTrajectories.clear();
888                         for (KpiLaneDirection lane : AbstractSpaceSamplerFdSource.this.space)
889                         {
890                             AbstractSpaceSamplerFdSource.this.lastConsecutivelyAssignedTrajectories.put(lane, -1);
891                             AbstractSpaceSamplerFdSource.this.assignedTrajectories.put(lane, new TreeSet<>());
892                         }
893                         while ((AbstractSpaceSamplerFdSource.this.periodNumber + 1) * getUpdateInterval().si
894                                 + AbstractSpaceSamplerFdSource.this.aggregationPeriod.si <= time.si)
895                         {
896                             increaseTime(
897                                     Time.instantiateSI((AbstractSpaceSamplerFdSource.this.periodNumber + 1) * getUpdateInterval().si
898                                             + AbstractSpaceSamplerFdSource.this.aggregationPeriod.si));
899                             fd.notifyPlotChange();
900                         }
901                         AbstractSpaceSamplerFdSource.this.invalid = false;
902                     }
903                 }
904             }, "Fundamental diagram recalculation").start();
905         }
906 
907         /** {@inheritDoc} */
908         @Override
909         public Duration getDelay()
910         {
911             return Duration.instantiateSI(1.0);
912         }
913 
914         /** {@inheritDoc} */
915         @Override
916         public synchronized void increaseTime(final Time time)
917         {
918             if (time.si < this.aggregationPeriod.si)
919             {
920                 // skip periods that fall below 0.0 time
921                 return;
922             }
923 
924             // ensure capacity
925             int nextPeriod = this.periodNumber + 1;
926             if (nextPeriod >= this.firstMeasurement[0].length - 1)
927             {
928                 for (int i = 0; i < this.nSeries; i++)
929                 {
930                     this.firstMeasurement[i] = GraphUtil.ensureCapacity(this.firstMeasurement[i], nextPeriod + 1);
931                     this.secondMeasurement[i] = GraphUtil.ensureCapacity(this.secondMeasurement[i], nextPeriod + 1);
932                 }
933             }
934 
935             // loop positions and trajectories
936             Time startTime = time.minus(this.aggregationPeriod);
937             double first = 0;
938             double second = 0.0;
939             for (int series = 0; series < this.space.getNumberOfSeries(); series++)
940             {
941                 Iterator<? extends KpiLaneDirection> it = this.space.iterator(series);
942                 while (it.hasNext())
943                 {
944                     KpiLaneDirection lane = it.next();
945                     TrajectoryGroup<?> trajectoryGroup = this.sampler.getTrajectoryGroup(lane);
946                     int last = this.lastConsecutivelyAssignedTrajectories.get(lane);
947                     SortedSet<Integer> assigned = this.assignedTrajectories.get(lane);
948                     if (!this.aggregateLanes)
949                     {
950                         first = 0.0;
951                         second = 0.0;
952                     }
953 
954                     // Length x = this.crossSection.position(series);
955                     int i = 0;
956                     for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
957                     {
958                         // we can skip all assigned trajectories, which are all up to and including 'last' and all in 'assigned'
959                         try
960                         {
961                             if (i > last && !assigned.contains(i))
962                             {
963                                 // quickly filter
964                                 if (GraphUtil.considerTrajectory(trajectory, startTime, time))
965                                 {
966                                     double[] measurements = new double[2];
967                                     getMeasurements(trajectory, startTime, time, lane.getLaneData().getLength(), series,
968                                             measurements);
969                                     first += measurements[0];
970                                     second += measurements[1];
971                                 }
972                                 if (trajectory.getT(trajectory.size() - 1) < startTime.si - getDelay().si)
973                                 {
974                                     assigned.add(i);
975                                 }
976                             }
977                             i++;
978                         }
979                         catch (SamplingException exception)
980                         {
981                             throw new RuntimeException("Unexpected exception while counting trajectories.", exception);
982                         }
983                     }
984                     if (!this.aggregateLanes)
985                     {
986                         this.firstMeasurement[series][nextPeriod] = first;
987                         this.secondMeasurement[series][nextPeriod] = second;
988                     }
989 
990                     // consolidate list of assigned trajectories in 'all up to n' and 'these specific ones beyond n'
991                     if (!assigned.isEmpty())
992                     {
993                         int possibleNextLastAssigned = assigned.first();
994                         while (possibleNextLastAssigned == last + 1) // consecutive or very first
995                         {
996                             last = possibleNextLastAssigned;
997                             assigned.remove(possibleNextLastAssigned);
998                             possibleNextLastAssigned = assigned.isEmpty() ? -1 : assigned.first();
999                         }
1000                         this.lastConsecutivelyAssignedTrajectories.put(lane, last);
1001                     }
1002                 }
1003             }
1004             if (this.aggregateLanes)
1005             {
1006                 // whatever we measured, it was summed and can be normalized per line like this
1007                 this.firstMeasurement[0][nextPeriod] = first / this.space.getNumberOfSeries();
1008                 this.secondMeasurement[0][nextPeriod] = second / this.space.getNumberOfSeries();
1009             }
1010             this.periodNumber = nextPeriod;
1011         }
1012 
1013         /** {@inheritDoc} */
1014         @Override
1015         public int getNumberOfSeries()
1016         {
1017             // if there is an active plot draw as the data is being recalculated, data on invalid items is requested
1018             // a call to getSeriesCount() indicates a new draw, and during a recalculation the data is limited but valid
1019             this.invalid = false;
1020             return this.nSeries;
1021         }
1022 
1023         /** {@inheritDoc} */
1024         @Override
1025         public String getName(final int series)
1026         {
1027             if (this.aggregateLanes)
1028             {
1029                 return "Aggregate";
1030             }
1031             return this.space.getName(series);
1032         }
1033 
1034         /** {@inheritDoc} */
1035         @Override
1036         public int getItemCount(final int series)
1037         {
1038             return this.periodNumber + 1;
1039         }
1040 
1041         /** {@inheritDoc} */
1042         @Override
1043         public final double getFlow(final int series, final int item)
1044         {
1045             if (this.invalid)
1046             {
1047                 return Double.NaN;
1048             }
1049             return getVehicleCount(this.firstMeasurement[series][item], this.secondMeasurement[series][item])
1050                     / this.aggregationPeriod.si;
1051         }
1052 
1053         /** {@inheritDoc} */
1054         @Override
1055         public final double getDensity(final int series, final int item)
1056         {
1057             return getFlow(series, item) / getSpeed(series, item);
1058         }
1059 
1060         /** {@inheritDoc} */
1061         @Override
1062         public final double getSpeed(final int series, final int item)
1063         {
1064             if (this.invalid)
1065             {
1066                 return Double.NaN;
1067             }
1068             return getSpeed(this.firstMeasurement[series][item], this.secondMeasurement[series][item]);
1069         }
1070 
1071         /**
1072          * Returns the first and the second measurement of a trajectory. For a cross-section this is 1 and the vehicle speed if
1073          * the trajectory crosses the location, and for a path it is the traveled distance and the traveled time. If the
1074          * trajectory didn't cross the cross section or space-time range, both should be 0.
1075          * @param trajectory Trajectory&lt;?&gt;; trajectory
1076          * @param startTime Time; start time of aggregation period
1077          * @param endTime Time; end time of aggregation period
1078          * @param length Length; length of the section (to cut off possible lane overshoot of trajectories)
1079          * @param series int; series number in the section
1080          * @param measurements double[]; array with length 2 to place the first and second measurement in
1081          */
1082         protected abstract void getMeasurements(Trajectory<?> trajectory, Time startTime, Time endTime, Length length,
1083                 int series, double[] measurements);
1084 
1085         /**
1086          * Returns the vehicle count of two related measurement values. For a cross section: vehicle count & sum of speeds (or
1087          * sum of inverted speeds for the harmonic mean). For a path: total traveled distance & total traveled time.
1088          * <p>
1089          * The value will be divided by the aggregation time to calculate flow. Hence, for a cross section the first measurement
1090          * should be returned, while for a path the first measurement divided by the section length should be returned. That
1091          * will end up to equate to {@code q = sum(x)/XT}.
1092          * @param first double; first measurement value
1093          * @param second double; second measurement value
1094          * @return double; flow
1095          */
1096         protected abstract double getVehicleCount(double first, double second);
1097 
1098         /**
1099          * Returns the speed of two related measurement values. For a cross section: vehicle count & sum of speeds (or sum of
1100          * inverted speeds for the harmonic mean). For a path: total traveled distance & total traveled time.
1101          * @param first double; first measurement value
1102          * @param second double; second measurement value
1103          * @return double; speed
1104          */
1105         protected abstract double getSpeed(double first, double second);
1106 
1107     }
1108 
1109     /** {@inheritDoc} */
1110     @Override
1111     public String toString()
1112     {
1113         return "FundamentalDiagram [source=" + this.getSource() + ", domainQuantity=" + this.getDomainQuantity() + ", rangeQuantity="
1114                 + this.getRangeQuantity() + ", otherQuantity=" + this.getOtherQuantity() + ", seriesLabels=" + this.seriesLabels
1115                 + ", graphUpdater=" + this.graphUpdater + ", timeInfo=" + this.getTimeInfo() + ", legend=" + this.legend
1116                 + ", laneVisible=" + this.laneVisible + "]";
1117     }
1118 
1119     /**
1120      * Get the data source.
1121      * @return FdSource; the data source
1122      */
1123     public FdSource getSource()
1124     {
1125         return source;
1126     }
1127 
1128     /**
1129      * Retrievee the legend of this FundamentalDiagram.
1130      * @return LegendItemCollection; the legend
1131      */
1132     public LegendItemCollection getLegend()
1133     {
1134         return legend;
1135     }
1136 
1137     /**
1138      * Return the list of lane visibility flags.
1139      * @return List&lt;Boolean&gt;; the list of lane visibility flags
1140      */
1141     public List<Boolean> getLaneVisible()
1142     {
1143         return laneVisible;
1144     }
1145 
1146     /**
1147      * Return the domain quantity.
1148      * @return Quantity; the domain quantity
1149      */
1150     public Quantity getDomainQuantity()
1151     {
1152         return domainQuantity;
1153     }
1154 
1155     /**
1156      * Set the domain quantity.
1157      * @param domainQuantity Quantity; the new domain quantity
1158      */
1159     public void setDomainQuantity(final Quantity domainQuantity)
1160     {
1161         this.domainQuantity = domainQuantity;
1162     }
1163 
1164     /**
1165      * Get the other (non domain; vertical axis) quantity.
1166      * @return Quantity; the quantity for the vertical axis
1167      */
1168     public Quantity getOtherQuantity()
1169     {
1170         return otherQuantity;
1171     }
1172 
1173     /**
1174      * Set the other (non domain; vertical axis) quantity.
1175      * @param otherQuantity Quantity; the quantity for the vertical axis
1176      */
1177     public void setOtherQuantity(final Quantity otherQuantity)
1178     {
1179         this.otherQuantity = otherQuantity;
1180     }
1181 
1182     /**
1183      * Get the range quantity.
1184      * @return Quantity; the range quantity
1185      */
1186     public Quantity getRangeQuantity()
1187     {
1188         return rangeQuantity;
1189     }
1190 
1191     /**
1192      * Set the range quantity.
1193      * @param rangeQuantity Quantity; the new range quantity
1194      */
1195     public void setRangeQuantity(final Quantity rangeQuantity)
1196     {
1197         this.rangeQuantity = rangeQuantity;
1198     }
1199 
1200     /**
1201      * Retrieve the time info.
1202      * @return String; the time info
1203      */
1204     public String getTimeInfo()
1205     {
1206         return timeInfo;
1207     }
1208 
1209     /**
1210      * Set the time info.
1211      * @param timeInfo String; the new time info
1212      */
1213     public void setTimeInfo(final String timeInfo)
1214     {
1215         this.timeInfo = timeInfo;
1216     }
1217 
1218 }