1 package org.opentrafficsim.animation.graphs;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5 import java.util.LinkedHashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Map.Entry;
9 import java.util.SortedSet;
10 import java.util.TreeSet;
11
12 import org.djunits.value.vdouble.scalar.Duration;
13 import org.djunits.value.vdouble.scalar.Length;
14 import org.djutils.event.EventType;
15 import org.djutils.exceptions.Throw;
16 import org.djutils.metadata.MetaData;
17 import org.djutils.metadata.ObjectDescriptor;
18 import org.opentrafficsim.animation.graphs.FundamentalDiagram.FdPaintState;
19 import org.opentrafficsim.animation.graphs.FundamentalDiagram.FdSeries;
20 import org.opentrafficsim.kpi.interfaces.LaneData;
21 import org.opentrafficsim.kpi.sampling.Sampler;
22 import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
23 import org.opentrafficsim.kpi.sampling.Trajectory;
24 import org.opentrafficsim.kpi.sampling.Trajectory.SpaceTimeView;
25 import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
26
27
28
29
30
31
32
33
34
35
36
37 public abstract class FdDataSource extends PlotDelegate<FdPaintState, FundamentalDiagram>
38 {
39
40
41 public static final EventType UPDATES_PER_PERIOD = new EventType("UPDATES_PER_PERIOD", new MetaData("Updates per period",
42 "Updates per period", new ObjectDescriptor("Updates per period", "Updates per period", Integer.class)));
43
44
45 public static final EventType AGGREGATION_PERIOD = new EventType("AGGREGATION_PERIOD", new MetaData("Aggregation period",
46 "Aggregation period", new ObjectDescriptor("Aggregation period", "Aggregation period", Duration.class)));
47
48
49 static final PlotSetting<Duration> AGGREGATION_PERIODS =
50 PlotSetting.of(new double[] {5.0, 10.0, 30.0, 60.0, 120.0, 300.0, 900.0}, Duration::ofSI, 3);
51
52
53 static final PlotSetting<Integer> UPDATES = PlotSetting.of(List.of(1, 2, 3, 5, 10), 0);
54
55
56 private int updatesPerPeriod = 1;
57
58
59 private Duration aggregationPeriod;
60
61
62 private final PlotSetting<Duration> aggregationPeriodSetting;
63
64
65 private final PlotSetting<Integer> updatesPerPeriodSetting;
66
67
68
69
70
71
72
73 public FdDataSource(final Duration aggregationInterval, final Duration delay, final PlotScheduler plotScheduler)
74 {
75 this(aggregationInterval, delay, plotScheduler, AGGREGATION_PERIODS, UPDATES);
76 }
77
78
79
80
81
82
83
84
85
86 public FdDataSource(final Duration aggregationInterval, final Duration delay, final PlotScheduler plotScheduler,
87 final PlotSetting<Duration> aggregationPeriodSetting, final PlotSetting<Integer> updatesPerPeriodSetting)
88 {
89 super(aggregationInterval, delay, plotScheduler);
90 this.aggregationPeriod = aggregationInterval;
91 this.aggregationPeriodSetting = aggregationPeriodSetting;
92 this.updatesPerPeriodSetting = updatesPerPeriodSetting;
93 }
94
95
96
97
98
99 public PlotSetting<Duration> getAggregationPeriodSetting()
100 {
101 return this.aggregationPeriodSetting;
102 }
103
104
105
106
107
108 public PlotSetting<Integer> getUpdatesPerPeriodSetting()
109 {
110 return this.updatesPerPeriodSetting;
111 }
112
113
114
115
116
117 public int getUpdatesPerPeriod()
118 {
119 return this.updatesPerPeriod;
120 }
121
122
123
124
125
126 public void setUpdatesPerPeriod(final int n)
127 {
128 synchronized (this)
129 {
130 this.updatesPerPeriod = n;
131 Duration update = this.aggregationPeriod.divide(this.updatesPerPeriod);
132 getPlots().forEach((p) -> p.offerUpdateInterval(update));
133 invalidateTimeSpan();
134 }
135 fireEvent(UPDATES_PER_PERIOD, new Object[] {n});
136 }
137
138
139
140
141
142 public Duration getAggregationPeriod()
143 {
144 return this.aggregationPeriod;
145 }
146
147
148
149
150
151 public void setAggregationPeriod(final Duration aggregationPeriod)
152 {
153 synchronized (this)
154 {
155 this.aggregationPeriod = aggregationPeriod;
156 Duration update = this.aggregationPeriod.divide(this.updatesPerPeriod);
157 getPlots().forEach((p) -> p.offerUpdateInterval(update));
158 invalidateTimeSpan();
159 }
160 fireEvent(AGGREGATION_PERIOD, new Object[] {aggregationPeriod});
161 }
162
163
164
165
166
167 public Duration getUpdateInterval()
168 {
169 return getAggregationPeriod().divide(getUpdatesPerPeriod());
170 }
171
172 @Override
173 public void calculatePaintStateUnsafe(final Duration time)
174 {
175 FdPaintState paintState = getPaintState(time);
176 for (FundamentalDiagram plot : getPlots())
177 {
178 plot.offerPaintState(paintState);
179 }
180 }
181
182
183
184
185
186
187 abstract FdPaintState getPaintState(Duration time);
188
189
190
191
192
193 abstract int getNumberOfSeries();
194
195
196
197
198
199
200 abstract String getName(int series);
201
202
203
204
205
206 abstract boolean isAggregate();
207
208
209
210
211
212 public abstract void setAggregateName(String aggregateName);
213
214
215
216
217
218
219
220
221
222
223
224
225 public static <L extends LaneData<L>> FdDataSource sourceFromSampler(final Sampler<?, L> sampler,
226 final PlotScheduler plotScheduler, final GraphCrossSection<L> crossSection, final boolean aggregateLanes,
227 final Duration aggregationTime, final boolean harmonic)
228 {
229 return new CrossSectionFdDataSource<>(sampler, plotScheduler, crossSection, aggregateLanes, aggregationTime, harmonic);
230 }
231
232
233
234
235
236
237
238
239
240
241
242 public static <L extends LaneData<L>> FdDataSource sourceFromSampler(final Sampler<?, L> sampler,
243 final PlotScheduler plotScheduler, final GraphPath<L> path, final boolean aggregateLanes,
244 final Duration aggregationTime)
245 {
246 return new PathFdDataSource<>(sampler, plotScheduler, path, aggregateLanes, aggregationTime);
247 }
248
249
250
251
252
253
254 public static FdDataSource combinedSource(final Map<String, FdDataSource> sources)
255 {
256 return new MultiFdSource(sources);
257 }
258
259
260
261
262
263
264 private static final class CrossSectionFdDataSource<L extends LaneData<L>, S extends GraphCrossSection<L>>
265 extends AbstractFdDataSource<L, S>
266 {
267
268
269 private static final Length EPS = Length.ofSI(1e-9);
270
271
272 private final boolean harmonic;
273
274
275
276
277
278
279
280
281
282
283 private CrossSectionFdDataSource(final Sampler<?, L> sampler, final PlotScheduler plotScheduler, final S crossSection,
284 final boolean aggregateLanes, final Duration aggregationPeriod, final boolean harmonic)
285 {
286 super(sampler, plotScheduler, crossSection, aggregateLanes, aggregationPeriod);
287 this.harmonic = harmonic;
288 }
289
290 @Override
291 protected void getMeasurements(final Trajectory<?> trajectory, final Duration startTime, final Duration endTime,
292 final Length length, final int series, final double[] measurements)
293 {
294 Length x = getSpace().position(series);
295 if (GraphUtil.considerTrajectory(trajectory, x.minus(EPS), x.plus(EPS)))
296 {
297
298 if (trajectory.getSpeedAtPosition(x).si != 0.0)
299 {
300 Duration t = trajectory.getTimeAtPosition(x);
301 if (t.si >= startTime.si && t.si < endTime.si)
302 {
303 measurements[0] = 1;
304 measurements[1] =
305 this.harmonic ? 1.0 / trajectory.getSpeedAtPosition(x).si : trajectory.getSpeedAtPosition(x).si;
306 }
307 }
308 }
309 }
310
311 @Override
312 protected double getVehicleCount(final double first, final double second)
313 {
314 return first;
315 }
316
317 @Override
318 protected double getSpeed(final double first, final double second)
319 {
320 return this.harmonic ? first / second : second / first;
321 }
322
323 @Override
324 public String toString()
325 {
326 return "CrossSectionFdDataSource [harmonic=" + this.harmonic + "]";
327 }
328
329 }
330
331
332
333
334
335
336 private static final class PathFdDataSource<L extends LaneData<L>, S extends GraphPath<L>>
337 extends AbstractFdDataSource<L, S>
338 {
339
340
341
342
343
344
345
346
347
348 private PathFdDataSource(final Sampler<?, L> sampler, final PlotScheduler plotScheduler, final S path,
349 final boolean aggregateLanes, final Duration aggregationPeriod)
350 {
351 super(sampler, plotScheduler, path, aggregateLanes, aggregationPeriod);
352 }
353
354 @Override
355 protected void getMeasurements(final Trajectory<?> trajectory, final Duration startTime, final Duration endTime,
356 final Length length, final int series, final double[] measurements)
357 {
358 SpaceTimeView stv = trajectory.getSpaceTimeView(Length.ZERO, length, startTime, endTime);
359 measurements[0] = stv.distance().si;
360 measurements[1] = stv.time().si;
361 }
362
363 @Override
364 protected double getVehicleCount(final double first, final double second)
365 {
366 return first / getSpace().getTotalLength().si;
367 }
368
369 @Override
370 protected double getSpeed(final double first, final double second)
371 {
372 return first / second;
373 }
374
375 @Override
376 public String toString()
377 {
378 return "PathFdDataSource []";
379 }
380
381 }
382
383
384
385
386
387
388 private abstract static class AbstractFdDataSource<L extends LaneData<L>, S extends AbstractGraphSpace<L>>
389 extends FdDataSource
390 {
391
392
393 private int periodNumber = -1;
394
395
396 private Duration lastUpdateTime;
397
398
399 private final int nSeries;
400
401
402 private double[][] firstMeasurement;
403
404
405 private double[][] secondMeasurement;
406
407
408 private final Sampler<?, L> sampler;
409
410
411 private final S space;
412
413
414 private final boolean aggregateLanes;
415
416
417 private String aggregateName = "Aggregate";
418
419
420 private Map<L, Integer> lastConsecutivelyAssignedTrajectories = new LinkedHashMap<>();
421
422
423 private Map<L, SortedSet<Integer>> assignedTrajectories = new LinkedHashMap<>();
424
425
426
427
428
429
430
431
432
433 private AbstractFdDataSource(final Sampler<?, L> sampler, final PlotScheduler plotScheduler, final S space,
434 final boolean aggregateLanes, final Duration aggregationPeriod)
435 {
436 super(aggregationPeriod, Duration.ONE, plotScheduler);
437 this.sampler = sampler;
438 this.space = space;
439 this.aggregateLanes = aggregateLanes;
440 this.nSeries = aggregateLanes ? 1 : space.getNumberOfSeries();
441
442 for (L laneDirection : space)
443 {
444 sampler.registerSpaceTimeRegion(new SpaceTimeRegion<>(laneDirection, Length.ZERO, laneDirection.getLength(),
445 sampler.now(), Duration.ofSI(Double.MAX_VALUE)));
446
447
448 this.lastConsecutivelyAssignedTrajectories.put(laneDirection, -1);
449 this.assignedTrajectories.put(laneDirection, new TreeSet<>());
450 }
451 this.firstMeasurement = new double[this.nSeries][10];
452 this.secondMeasurement = new double[this.nSeries][10];
453 }
454
455
456
457
458
459 protected S getSpace()
460 {
461 return this.space;
462 }
463
464 @Override
465 public Duration getDelay()
466 {
467 return Duration.ONE;
468 }
469
470 @Override
471 public FdPaintState getPaintState(final Duration time)
472 {
473 boolean redo;
474 Duration aggregationPeriod;
475 synchronized (AbstractFdDataSource.this)
476 {
477 redo = getAndResetInvalidTimeSpan();
478 aggregationPeriod = getAggregationPeriod();
479 }
480
481 if (redo)
482 {
483 this.periodNumber = -1;
484 this.firstMeasurement = new double[AbstractFdDataSource.this.nSeries][10];
485 this.secondMeasurement = new double[AbstractFdDataSource.this.nSeries][10];
486 this.lastConsecutivelyAssignedTrajectories.clear();
487 this.assignedTrajectories.clear();
488 for (L lane : AbstractFdDataSource.this.space)
489 {
490 AbstractFdDataSource.this.lastConsecutivelyAssignedTrajectories.put(lane, -1);
491 AbstractFdDataSource.this.assignedTrajectories.put(lane, new TreeSet<>());
492 }
493 this.lastUpdateTime = null;
494 }
495
496 while ((AbstractFdDataSource.this.periodNumber + 2) * aggregationPeriod.si <= time.si)
497 {
498 increaseTime(Duration.ofSI((this.periodNumber + 2) * aggregationPeriod.si), aggregationPeriod);
499 }
500
501 return buildPaintState(aggregationPeriod);
502 }
503
504
505
506
507
508
509 private void increaseTime(final Duration time, final Duration aggregationPeriod)
510 {
511 if (time.si < getAggregationPeriod().si)
512 {
513
514 return;
515 }
516 this.lastUpdateTime = time;
517
518
519 int nextPeriod = this.periodNumber + 1;
520 if (nextPeriod >= this.firstMeasurement[0].length - 1)
521 {
522 for (int i = 0; i < this.nSeries; i++)
523 {
524 this.firstMeasurement[i] = GraphUtil.ensureCapacity(this.firstMeasurement[i], nextPeriod + 1);
525 this.secondMeasurement[i] = GraphUtil.ensureCapacity(this.secondMeasurement[i], nextPeriod + 1);
526 }
527 }
528
529
530 Duration startTime = time.minus(aggregationPeriod);
531 double first = 0;
532 double second = 0.0;
533 for (int series = 0; series < this.space.getNumberOfSeries(); series++)
534 {
535 Iterator<L> it = this.space.iterator(series);
536 while (it.hasNext())
537 {
538 L lane = it.next();
539 if (!this.sampler.getSamplerData().contains(lane))
540 {
541
542 continue;
543 }
544 TrajectoryGroup<?> trajectoryGroup = this.sampler.getSamplerData().getTrajectoryGroup(lane).get();
545 int last = this.lastConsecutivelyAssignedTrajectories.get(lane);
546 SortedSet<Integer> assigned = this.assignedTrajectories.get(lane);
547 if (!this.aggregateLanes)
548 {
549 first = 0.0;
550 second = 0.0;
551 }
552
553 int i = 0;
554 for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
555 {
556
557 if (i > last && !assigned.contains(i))
558 {
559
560 if (GraphUtil.considerTrajectory(trajectory, startTime, time))
561 {
562 double[] measurements = new double[2];
563 getMeasurements(trajectory, startTime, time, lane.getLength(), series, measurements);
564 first += measurements[0];
565 second += measurements[1];
566 }
567 if (trajectory.getT(trajectory.size() - 1) < startTime.si - getDelay().si)
568 {
569 assigned.add(i);
570 }
571 }
572 i++;
573 }
574 if (!this.aggregateLanes)
575 {
576 this.firstMeasurement[series][nextPeriod] = first;
577 this.secondMeasurement[series][nextPeriod] = second;
578 }
579
580
581 if (!assigned.isEmpty())
582 {
583 int possibleNextLastAssigned = assigned.first();
584 while (possibleNextLastAssigned == last + 1)
585 {
586 last = possibleNextLastAssigned;
587 assigned.remove(possibleNextLastAssigned);
588 possibleNextLastAssigned = assigned.isEmpty() ? -1 : assigned.first();
589 }
590 this.lastConsecutivelyAssignedTrajectories.put(lane, last);
591 }
592 }
593 }
594 if (this.aggregateLanes)
595 {
596
597 this.firstMeasurement[0][nextPeriod] = first / this.space.getNumberOfSeries();
598 this.secondMeasurement[0][nextPeriod] = second / this.space.getNumberOfSeries();
599 }
600 this.periodNumber = nextPeriod;
601 }
602
603
604
605
606
607
608 private FdPaintState buildPaintState(final Duration aggregationPeriod)
609 {
610 FdSeries[] series = new FdSeries[getNumberOfSeries()];
611 int n = this.periodNumber + 1;
612 for (int i = 0; i < getNumberOfSeries(); i++)
613 {
614 float[] q = new float[n];
615 float[] v = new float[n];
616 float[] k = new float[n];
617 for (int j = 0; j < n; j++)
618 {
619 q[j] = (float) (3600 * getItemFlow(i, j, aggregationPeriod));
620 v[j] = (float) (3.6 * getItemSpeed(i, j));
621 k[j] = q[j] / v[j];
622 }
623 FdSeries serie = new FdSeries(q, v, k);
624 series[i] = serie;
625 }
626 return new FdPaintState(series, this.lastUpdateTime);
627 }
628
629 @Override
630 public int getNumberOfSeries()
631 {
632 return this.nSeries;
633 }
634
635 @Override
636 public void setAggregateName(final String aggregateName)
637 {
638 this.aggregateName = aggregateName;
639 }
640
641 @Override
642 public String getName(final int series)
643 {
644 if (this.aggregateLanes)
645 {
646 return this.aggregateName;
647 }
648 return this.space.getName(series);
649 }
650
651 @Override
652 public final boolean isAggregate()
653 {
654 return this.aggregateLanes;
655 }
656
657
658
659
660
661
662
663
664 private double getItemFlow(final int series, final int item, final Duration aggregationPeriod)
665 {
666 return getVehicleCount(this.firstMeasurement[series][item], this.secondMeasurement[series][item])
667 / aggregationPeriod.si;
668 }
669
670
671
672
673
674
675
676 private double getItemSpeed(final int series, final int item)
677 {
678 return getSpeed(this.firstMeasurement[series][item], this.secondMeasurement[series][item]);
679 }
680
681
682
683
684
685
686
687
688
689
690
691
692 protected abstract void getMeasurements(Trajectory<?> trajectory, Duration startTime, Duration endTime, Length length,
693 int series, double[] measurements);
694
695
696
697
698
699
700
701
702
703
704
705
706 protected abstract double getVehicleCount(double first, double second);
707
708
709
710
711
712
713
714
715 protected abstract double getSpeed(double first, double second);
716
717 }
718
719
720
721
722 private static final class MultiFdSource extends FdDataSource
723 {
724
725
726 private FdDataSource[] sources;
727
728
729 private String[] sourceNames;
730
731
732
733
734
735 private MultiFdSource(final Map<String, FdDataSource> sources)
736 {
737 super(sources.values().iterator().next().getAggregationPeriod(), Duration.ONE,
738 sources.values().iterator().next().getPlotScheduler());
739 Throw.when(sources == null || sources.size() == 0, IllegalArgumentException.class,
740 "At least 1 source is required.");
741 this.sources = new FdDataSource[sources.size()];
742 this.sourceNames = new String[sources.size()];
743 int index = 0;
744 for (Entry<String, FdDataSource> entry : sources.entrySet())
745 {
746 this.sources[index] = entry.getValue();
747 this.sourceNames[index] = entry.getKey();
748 index++;
749 }
750 }
751
752
753
754
755
756
757 private int[] getSourceAndSeries(final int series)
758 {
759 int source = 0;
760 int sourceSeries = series;
761 while (sourceSeries >= this.sources[source].getNumberOfSeries())
762 {
763 sourceSeries -= this.sources[source].getNumberOfSeries();
764 source++;
765 }
766 return new int[] {source, sourceSeries};
767 }
768
769 @Override
770 public void setAggregationPeriod(final Duration period)
771 {
772 for (FdDataSource source : this.sources)
773 {
774 source.setAggregationPeriod(period);
775 }
776 }
777
778 @Override
779 public void setUpdatesPerPeriod(final int n)
780 {
781 for (FdDataSource source : this.sources)
782 {
783 source.setUpdatesPerPeriod(n);
784 }
785 }
786
787 @Override
788 public Duration getDelay()
789 {
790 return this.sources[0].getDelay();
791 }
792
793 @Override
794 public int getNumberOfSeries()
795 {
796 int numberOfSeries = 0;
797 for (FdDataSource source : this.sources)
798 {
799 numberOfSeries += source.getNumberOfSeries();
800 }
801 return numberOfSeries;
802 }
803
804 @Override
805 public String getName(final int series)
806 {
807 int[] ss = getSourceAndSeries(series);
808 return this.sourceNames[ss[0]]
809 + (this.sources[ss[0]].isAggregate() ? "" : ": " + this.sources[ss[0]].getName(ss[1]));
810 }
811
812 @Override
813 public FdPaintState getPaintState(final Duration time)
814 {
815 List<FdPaintState> paintStates = new ArrayList<>();
816 int n = 0;
817 for (FdDataSource source : this.sources)
818 {
819 FdPaintState state = source.getPaintState(time);
820 paintStates.add(state);
821 n += state.getSeriesCount();
822 }
823 FdSeries[] series = new FdSeries[n];
824 int i = 0;
825 for (FdPaintState state : paintStates)
826 {
827 System.arraycopy(state.fdSeries(), 0, series, i, state.getSeriesCount());
828 i += state.getSeriesCount();
829 }
830 return new FdPaintState(series, time);
831 }
832
833 @Override
834 public boolean isAggregate()
835 {
836 return false;
837 }
838
839 @Override
840 public void setAggregateName(final String aggregateName)
841 {
842
843 }
844
845 }
846
847 }