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<?>; sampler
130 * @param crossSection GraphCrossSection<KpiLaneDirection>; 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<?>; sampler
152 * @param path GraphPath<KpiLaneDirection>; 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<?>; sampler
571 * @param crossSection GraphCrossSection<KpiLaneDirection>; 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<?>; sampler
587 * @param path GraphPath<KpiLaneDirection>; 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<?>; 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<?>; 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<?>; 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<?>; 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<Boolean>; 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 }