1 package org.opentrafficsim.draw.graphs;
2
3 import java.awt.Color;
4 import java.time.Period;
5 import java.util.ArrayList;
6 import java.util.EnumSet;
7 import java.util.Iterator;
8 import java.util.LinkedHashMap;
9 import java.util.LinkedHashSet;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Map.Entry;
13 import java.util.Set;
14 import java.util.SortedSet;
15 import java.util.TreeSet;
16
17 import org.djunits.value.vdouble.scalar.Duration;
18 import org.djunits.value.vdouble.scalar.Length;
19 import org.djunits.value.vdouble.scalar.Time;
20 import org.djutils.exceptions.Throw;
21 import org.djutils.immutablecollections.ImmutableLinkedHashSet;
22 import org.djutils.immutablecollections.ImmutableSet;
23 import org.jfree.chart.JFreeChart;
24 import org.jfree.chart.LegendItem;
25 import org.jfree.chart.LegendItemCollection;
26 import org.jfree.chart.axis.NumberAxis;
27 import org.jfree.chart.plot.XYPlot;
28 import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
29 import org.jfree.data.DomainOrder;
30 import org.jfree.data.xy.XYDataset;
31 import org.opentrafficsim.kpi.interfaces.LaneData;
32 import org.opentrafficsim.kpi.sampling.Sampler;
33 import org.opentrafficsim.kpi.sampling.SamplingException;
34 import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
35 import org.opentrafficsim.kpi.sampling.Trajectory;
36 import org.opentrafficsim.kpi.sampling.Trajectory.SpaceTimeView;
37 import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
38
39
40
41
42
43
44
45
46
47
48
49 public class FundamentalDiagram extends AbstractBoundedPlot implements XYDataset
50 {
51
52
53 public static final double[] DEFAULT_PERIODS = new double[] {5.0, 10.0, 30.0, 60.0, 120.0, 300.0, 900.0};
54
55
56 public static final int[] DEFAULT_UPDATE_FREQUENCIES = new int[] {1, 2, 3, 5, 10};
57
58
59 private final FdSource source;
60
61
62 private final FdLine fdLine;
63
64
65 private Quantity domainQuantity;
66
67
68 private Quantity rangeQuantity;
69
70
71 private Quantity otherQuantity;
72
73
74 private final List<String> seriesLabels = new ArrayList<>();
75
76
77 private final GraphUpdater<Time> graphUpdater;
78
79
80 private String timeInfo = "";
81
82
83 private LegendItemCollection legend;
84
85
86 private final List<Boolean> laneVisible = new ArrayList<>();
87
88
89
90
91
92
93
94
95
96
97 public FundamentalDiagram(final String caption, final Quantity domainQuantity, final Quantity rangeQuantity,
98 final PlotScheduler scheduler, final FdSource source, final FdLine fdLine)
99 {
100 super(scheduler, caption, source.getUpdateInterval(), source.getDelay());
101 Throw.when(domainQuantity.equals(rangeQuantity), IllegalArgumentException.class,
102 "Domain and range quantity should not be equal.");
103 this.fdLine = fdLine;
104 this.setDomainQuantity(domainQuantity);
105 this.setRangeQuantity(rangeQuantity);
106 Set<Quantity> quantities = EnumSet.allOf(Quantity.class);
107 quantities.remove(domainQuantity);
108 quantities.remove(rangeQuantity);
109 this.setOtherQuantity(quantities.iterator().next());
110 this.source = source;
111 int d = 0;
112 if (fdLine != null)
113 {
114 d = 1;
115 this.seriesLabels.add(fdLine.getName());
116 this.laneVisible.add(true);
117 }
118 for (int series = 0; series < source.getNumberOfSeries(); series++)
119 {
120 this.seriesLabels.add(series + d, source.getName(series));
121 this.laneVisible.add(true);
122 }
123 setChart(createChart());
124 setLowerDomainBound(0.0);
125 setLowerRangeBound(0.0);
126
127
128 this.graphUpdater = new GraphUpdater<>("Fundamental diagram worker", Thread.currentThread(), (t) ->
129 {
130 if (this.getSource() != null)
131 {
132 this.getSource().increaseTime(t);
133 notifyPlotChange();
134 }
135 });
136
137
138 source.addFundamentalDiagram(this);
139 }
140
141
142
143
144
145 private JFreeChart createChart()
146 {
147 NumberAxis xAxis = new NumberAxis(this.getDomainQuantity().label());
148 NumberAxis yAxis = new NumberAxis(this.getRangeQuantity().label());
149 XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer()
150 {
151
152 private static final long serialVersionUID = 20181022L;
153
154
155 @SuppressWarnings("synthetic-access")
156 @Override
157 public boolean isSeriesVisible(final int series)
158 {
159 return FundamentalDiagram.this.laneVisible.get(series);
160 }
161 };
162 renderer.setDefaultLinesVisible(false);
163 if (hasLineFD())
164 {
165 int series = this.getSource().getNumberOfSeries();
166 renderer.setSeriesLinesVisible(series, true);
167 renderer.setSeriesPaint(series, Color.BLACK);
168 renderer.setSeriesShapesVisible(series, false);
169 }
170 XYPlot plot = new XYPlot(this, xAxis, yAxis, renderer);
171 boolean showLegend = true;
172 if (!hasLineFD() && this.getSource().getNumberOfSeries() < 2)
173 {
174 plot.setFixedLegendItems(null);
175 showLegend = false;
176 }
177 else
178 {
179 this.legend = new LegendItemCollection();
180 for (int i = 0; i < this.getSource().getNumberOfSeries(); i++)
181 {
182 LegendItem li = new LegendItem(this.getSource().getName(i));
183 li.setSeriesKey(i);
184 li.setShape(renderer.lookupLegendShape(i));
185 li.setFillPaint(renderer.lookupSeriesPaint(i));
186 this.legend.add(li);
187 }
188 if (hasLineFD())
189 {
190 LegendItem li = new LegendItem(this.fdLine.getName());
191 li.setSeriesKey(-1);
192 this.legend.add(li);
193 }
194 plot.setFixedLegendItems(this.legend);
195 showLegend = true;
196 }
197 return new JFreeChart(getCaption(), JFreeChart.DEFAULT_TITLE_FONT, plot, showLegend);
198 }
199
200
201 @Override
202 protected void increaseTime(final Time time)
203 {
204 if (this.graphUpdater != null && time.si >= this.getSource().getAggregationPeriod().si)
205 {
206 this.graphUpdater.offer(time);
207 }
208 }
209
210
211 @Override
212 public int getSeriesCount()
213 {
214 if (this.getSource() == null)
215 {
216 return 0;
217 }
218 return this.getSource().getNumberOfSeries() + (hasLineFD() ? 1 : 0);
219 }
220
221
222 @Override
223 public Comparable<String> getSeriesKey(final int series)
224 {
225 return this.seriesLabels.get(series);
226 }
227
228
229 @SuppressWarnings("rawtypes")
230 @Override
231 public int indexOf(final Comparable seriesKey)
232 {
233 int index = this.seriesLabels.indexOf(seriesKey);
234 return index < 0 ? 0 : index;
235 }
236
237
238 @Override
239 public DomainOrder getDomainOrder()
240 {
241 return DomainOrder.NONE;
242 }
243
244
245 @Override
246 public int getItemCount(final int series)
247 {
248 if (hasLineFD() && series == getSeriesCount() - 1)
249 {
250 return this.fdLine.getValues(this.domainQuantity).length;
251 }
252 return this.getSource().getItemCount(series);
253 }
254
255
256 @Override
257 public Number getX(final int series, final int item)
258 {
259 return getXValue(series, item);
260 }
261
262
263 @Override
264 public double getXValue(final int series, final int item)
265 {
266 if (hasLineFD() && series == getSeriesCount() - 1)
267 {
268 return this.fdLine.getValues(this.domainQuantity)[item];
269 }
270 return this.getDomainQuantity().getValue(this.getSource(), series, item);
271 }
272
273
274 @Override
275 public Number getY(final int series, final int item)
276 {
277 return getYValue(series, item);
278 }
279
280
281 @Override
282 public double getYValue(final int series, final int item)
283 {
284 if (hasLineFD() && series == getSeriesCount() - 1)
285 {
286 return this.fdLine.getValues(this.rangeQuantity)[item];
287 }
288 return this.getRangeQuantity().getValue(this.getSource(), series, item);
289 }
290
291
292 @Override
293 public GraphType getGraphType()
294 {
295 return GraphType.FUNDAMENTAL_DIAGRAM;
296 }
297
298
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()
304 .format(this.getDomainQuantity().computeOther(this.getRangeQuantity(), domainValue, rangeValue))
305 + this.getTimeInfo();
306 }
307
308
309
310
311
312
313
314
315
316
317
318
319 public enum Quantity
320 {
321
322 DENSITY
323 {
324
325 @Override
326 public String label()
327 {
328 return "Density [veh/km] \u2192";
329 }
330
331
332 @Override
333 public String format(final double value)
334 {
335 return String.format("%.0f veh/km", value);
336 }
337
338
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
346 @Override
347 public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
348 {
349
350 return pairing.equals(FLOW) ? pairedValue / thisValue : thisValue * pairedValue;
351 }
352 },
353
354
355 FLOW
356 {
357
358 @Override
359 public String label()
360 {
361 return "Flow [veh/h] \u2192";
362 }
363
364
365 @Override
366 public String format(final double value)
367 {
368 return String.format("%.0f veh/h", value);
369 }
370
371
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
379 @Override
380 public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
381 {
382
383 return thisValue / pairedValue;
384 }
385 },
386
387
388 SPEED
389 {
390
391 @Override
392 public String label()
393 {
394 return "Speed [km/h] \u2192";
395 }
396
397
398 @Override
399 public String format(final double value)
400 {
401 return String.format("%.1f km/h", value);
402 }
403
404
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
412 @Override
413 public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
414 {
415
416 return pairing.equals(DENSITY) ? thisValue * pairedValue : pairedValue / thisValue;
417 }
418 };
419
420
421
422
423
424 public abstract String label();
425
426
427
428
429
430
431 public abstract String format(double value);
432
433
434
435
436
437
438
439
440 public abstract double getValue(FdSource src, int series, int item);
441
442
443
444
445
446
447
448
449 public abstract double computeOther(Quantity pairing, double thisValue, double pairedValue);
450
451 }
452
453
454
455
456
457
458
459
460
461
462
463
464 public interface FdSource
465 {
466
467
468
469
470 default double[] getPossibleAggregationPeriods()
471 {
472 return DEFAULT_PERIODS;
473 }
474
475
476
477
478
479 default int[] getPossibleUpdateFrequencies()
480 {
481 return DEFAULT_UPDATE_FREQUENCIES;
482 }
483
484
485
486
487
488 void addFundamentalDiagram(FundamentalDiagram fundamentalDiagram);
489
490
491
492
493 void clearFundamentalDiagrams();
494
495
496
497
498
499 ImmutableSet<FundamentalDiagram> getDiagrams();
500
501
502
503
504
505 Duration getUpdateInterval();
506
507
508
509
510
511
512 void setUpdateInterval(Duration interval, Time time);
513
514
515
516
517
518 Duration getAggregationPeriod();
519
520
521
522
523
524 void setAggregationPeriod(Duration period);
525
526
527
528
529
530 void recalculate(Time time);
531
532
533
534
535
536 Duration getDelay();
537
538
539
540
541
542 void increaseTime(Time time);
543
544
545
546
547
548 int getNumberOfSeries();
549
550
551
552
553
554
555 String getName(int series);
556
557
558
559
560
561
562 int getItemCount(int series);
563
564
565
566
567
568
569
570 double getFlow(int series, int item);
571
572
573
574
575
576
577
578 double getDensity(int series, int item);
579
580
581
582
583
584
585
586 double getSpeed(int series, int item);
587
588
589
590
591
592 boolean isAggregate();
593
594
595
596
597
598 void setAggregateName(String aggregateName);
599 }
600
601
602
603
604 abstract static class AbstractFdSource implements FdSource
605 {
606
607
608 private Set<FundamentalDiagram> fundamentalDiagrams = new LinkedHashSet<>();
609
610
611 @Override
612 public void addFundamentalDiagram(final FundamentalDiagram fundamentalDiagram)
613 {
614 this.fundamentalDiagrams.add(fundamentalDiagram);
615 }
616
617
618 @Override
619 public void clearFundamentalDiagrams()
620 {
621 this.fundamentalDiagrams.clear();
622 }
623
624
625 @Override
626 public ImmutableSet<FundamentalDiagram> getDiagrams()
627 {
628 return new ImmutableLinkedHashSet<>(this.fundamentalDiagrams);
629 }
630
631 }
632
633
634
635
636
637
638
639
640
641
642
643 @SuppressWarnings("methodlength")
644 public static <L extends LaneData<L>> FdSource sourceFromSampler(final Sampler<?, L> sampler,
645 final GraphCrossSection<L> crossSection, final boolean aggregateLanes, final Duration aggregationTime,
646 final boolean harmonic)
647 {
648 return new CrossSectionSamplerFdSource<>(sampler, crossSection, aggregateLanes, aggregationTime, harmonic);
649 }
650
651
652
653
654
655
656
657
658
659
660 public static <L extends LaneData<L>> FdSource sourceFromSampler(final Sampler<?, L> sampler, final GraphPath<L> path,
661 final boolean aggregateLanes, final Duration aggregationTime)
662 {
663 return new PathSamplerFdSource<>(sampler, path, aggregateLanes, aggregationTime);
664 }
665
666
667
668
669
670
671 public static FdSource combinedSource(final Map<String, FdSource> sources)
672 {
673 return new MultiFdSource(sources);
674 }
675
676
677
678
679
680
681 private static class CrossSectionSamplerFdSource<L extends LaneData<L>, S extends GraphCrossSection<L>>
682 extends AbstractSpaceSamplerFdSource<L, S>
683 {
684
685 private final boolean harmonic;
686
687
688
689
690
691
692
693
694
695 CrossSectionSamplerFdSource(final Sampler<?, L> sampler, final S crossSection, final boolean aggregateLanes,
696 final Duration aggregationPeriod, final boolean harmonic)
697 {
698 super(sampler, crossSection, aggregateLanes, aggregationPeriod);
699 this.harmonic = harmonic;
700 }
701
702
703 @Override
704 protected void getMeasurements(final Trajectory<?> trajectory, final Time startTime, final Time endTime,
705 final Length length, final int series, final double[] measurements)
706 {
707 Length x = getSpace().position(series);
708 if (GraphUtil.considerTrajectory(trajectory, x, x))
709 {
710
711 Time t = trajectory.getTimeAtPosition(x);
712 if (t.si >= startTime.si && t.si < endTime.si)
713 {
714 measurements[0] = 1;
715 measurements[1] =
716 this.harmonic ? 1.0 / trajectory.getSpeedAtPosition(x).si : trajectory.getSpeedAtPosition(x).si;
717 }
718 }
719 }
720
721
722 @Override
723 protected double getVehicleCount(final double first, final double second)
724 {
725 return first;
726 }
727
728
729 @Override
730 protected double getSpeed(final double first, final double second)
731 {
732 return this.harmonic ? first / second : second / first;
733 }
734
735
736 @Override
737 public String toString()
738 {
739 return "CrossSectionSamplerFdSource [harmonic=" + this.harmonic + "]";
740 }
741
742 }
743
744
745
746
747
748
749 private static class PathSamplerFdSource<L extends LaneData<L>, S extends GraphPath<L>>
750 extends AbstractSpaceSamplerFdSource<L, S>
751 {
752
753
754
755
756
757
758
759 PathSamplerFdSource(final Sampler<?, L> sampler, final S path, final boolean aggregateLanes,
760 final Duration aggregationPeriod)
761 {
762 super(sampler, path, aggregateLanes, aggregationPeriod);
763 }
764
765
766 @Override
767 protected void getMeasurements(final Trajectory<?> trajectory, final Time startTime, final Time endTime,
768 final Length length, final int sereies, final double[] measurements)
769 {
770 SpaceTimeView stv = trajectory.getSpaceTimeView(Length.ZERO, length, startTime, endTime);
771 measurements[0] = stv.getDistance().si;
772 measurements[1] = stv.getTime().si;
773 }
774
775
776 @Override
777 protected double getVehicleCount(final double first, final double second)
778 {
779 return first / getSpace().getTotalLength().si;
780 }
781
782
783 @Override
784 protected double getSpeed(final double first, final double second)
785 {
786 return first / second;
787 }
788
789
790 @Override
791 public String toString()
792 {
793 return "PathSamplerFdSource []";
794 }
795
796 }
797
798
799
800
801
802
803 private abstract static class AbstractSpaceSamplerFdSource<L extends LaneData<L>, S extends AbstractGraphSpace<L>>
804 extends AbstractFdSource
805 {
806
807 private int periodNumber = -1;
808
809
810 private Duration updateInterval;
811
812
813 private Duration aggregationPeriod;
814
815
816 private Time lastUpdateTime;
817
818
819 private final int nSeries;
820
821
822 private double[][] firstMeasurement;
823
824
825 private double[][] secondMeasurement;
826
827
828 private boolean invalid = false;
829
830
831 private final Sampler<?, L> sampler;
832
833
834 private final S space;
835
836
837 private final boolean aggregateLanes;
838
839
840 private String aggregateName = "Aggregate";
841
842
843 private Map<L, Integer> lastConsecutivelyAssignedTrajectories = new LinkedHashMap<>();
844
845
846 private Map<L, SortedSet<Integer>> assignedTrajectories = new LinkedHashMap<>();
847
848
849
850
851
852
853
854
855 AbstractSpaceSamplerFdSource(final Sampler<?, L> sampler, final S space, final boolean aggregateLanes,
856 final Duration aggregationPeriod)
857 {
858 this.sampler = sampler;
859 this.space = space;
860 this.aggregateLanes = aggregateLanes;
861 this.nSeries = aggregateLanes ? 1 : space.getNumberOfSeries();
862
863 for (L laneDirection : space)
864 {
865 sampler.registerSpaceTimeRegion(new SpaceTimeRegion<>(laneDirection, Length.ZERO, laneDirection.getLength(),
866 sampler.now(), Time.instantiateSI(Double.MAX_VALUE)));
867
868
869 this.lastConsecutivelyAssignedTrajectories.put(laneDirection, -1);
870 this.assignedTrajectories.put(laneDirection, new TreeSet<>());
871 }
872
873 this.updateInterval = aggregationPeriod;
874 this.aggregationPeriod = aggregationPeriod;
875 this.firstMeasurement = new double[this.nSeries][10];
876 this.secondMeasurement = new double[this.nSeries][10];
877 }
878
879
880
881
882
883 protected S getSpace()
884 {
885 return this.space;
886 }
887
888
889 @Override
890 public Duration getUpdateInterval()
891 {
892 return this.updateInterval;
893 }
894
895
896 @Override
897 public void setUpdateInterval(final Duration interval, final Time time)
898 {
899 if (this.updateInterval != interval)
900 {
901 this.updateInterval = interval;
902 recalculate(time);
903 }
904 }
905
906
907 @Override
908 public Duration getAggregationPeriod()
909 {
910 return this.aggregationPeriod;
911 }
912
913
914 @Override
915 public void setAggregationPeriod(final Duration period)
916 {
917 if (this.aggregationPeriod != period)
918 {
919 this.aggregationPeriod = period;
920 }
921 }
922
923
924 @Override
925 public void recalculate(final Time time)
926 {
927 new Thread(new Runnable()
928 {
929 @Override
930 @SuppressWarnings("synthetic-access")
931 public void run()
932 {
933 synchronized (AbstractSpaceSamplerFdSource.this)
934 {
935
936 AbstractSpaceSamplerFdSource.this.invalid = true;
937 AbstractSpaceSamplerFdSource.this.periodNumber = -1;
938 AbstractSpaceSamplerFdSource.this.updateInterval = getUpdateInterval();
939 AbstractSpaceSamplerFdSource.this.firstMeasurement =
940 new double[AbstractSpaceSamplerFdSource.this.nSeries][10];
941 AbstractSpaceSamplerFdSource.this.secondMeasurement =
942 new double[AbstractSpaceSamplerFdSource.this.nSeries][10];
943 AbstractSpaceSamplerFdSource.this.lastConsecutivelyAssignedTrajectories.clear();
944 AbstractSpaceSamplerFdSource.this.assignedTrajectories.clear();
945 for (L lane : AbstractSpaceSamplerFdSource.this.space)
946 {
947 AbstractSpaceSamplerFdSource.this.lastConsecutivelyAssignedTrajectories.put(lane, -1);
948 AbstractSpaceSamplerFdSource.this.assignedTrajectories.put(lane, new TreeSet<>());
949 }
950 AbstractSpaceSamplerFdSource.this.lastUpdateTime = null;
951 while ((AbstractSpaceSamplerFdSource.this.periodNumber + 1) * getUpdateInterval().si
952 + AbstractSpaceSamplerFdSource.this.aggregationPeriod.si <= time.si)
953 {
954 increaseTime(Time
955 .instantiateSI((AbstractSpaceSamplerFdSource.this.periodNumber + 1) * getUpdateInterval().si
956 + AbstractSpaceSamplerFdSource.this.aggregationPeriod.si));
957
958
959
960
961
962 }
963 AbstractSpaceSamplerFdSource.this.invalid = false;
964 }
965 }
966 }, "Fundamental diagram recalculation").start();
967 }
968
969
970 @Override
971 public Duration getDelay()
972 {
973 return Duration.instantiateSI(1.0);
974 }
975
976
977 @Override
978 public synchronized void increaseTime(final Time time)
979 {
980 if (time.si < this.aggregationPeriod.si)
981 {
982
983 return;
984 }
985
986 if (this.lastUpdateTime != null && time.le(this.lastUpdateTime))
987 {
988
989 return;
990 }
991 this.lastUpdateTime = time;
992
993
994 int nextPeriod = this.periodNumber + 1;
995 if (nextPeriod >= this.firstMeasurement[0].length - 1)
996 {
997 for (int i = 0; i < this.nSeries; i++)
998 {
999 this.firstMeasurement[i] = GraphUtil.ensureCapacity(this.firstMeasurement[i], nextPeriod + 1);
1000 this.secondMeasurement[i] = GraphUtil.ensureCapacity(this.secondMeasurement[i], nextPeriod + 1);
1001 }
1002 }
1003
1004
1005 Time startTime = time.minus(this.aggregationPeriod);
1006 double first = 0;
1007 double second = 0.0;
1008 for (int series = 0; series < this.space.getNumberOfSeries(); series++)
1009 {
1010 Iterator<L> it = this.space.iterator(series);
1011 while (it.hasNext())
1012 {
1013 L lane = it.next();
1014 if (!this.sampler.getSamplerData().contains(lane))
1015 {
1016
1017 continue;
1018 }
1019 TrajectoryGroup<?> trajectoryGroup = this.sampler.getSamplerData().getTrajectoryGroup(lane);
1020 int last = this.lastConsecutivelyAssignedTrajectories.get(lane);
1021 SortedSet<Integer> assigned = this.assignedTrajectories.get(lane);
1022 if (!this.aggregateLanes)
1023 {
1024 first = 0.0;
1025 second = 0.0;
1026 }
1027
1028
1029 int i = 0;
1030 for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
1031 {
1032
1033 try
1034 {
1035 if (i > last && !assigned.contains(i))
1036 {
1037
1038 if (GraphUtil.considerTrajectory(trajectory, startTime, time))
1039 {
1040 double[] measurements = new double[2];
1041 getMeasurements(trajectory, startTime, time, lane.getLength(), series, measurements);
1042 first += measurements[0];
1043 second += measurements[1];
1044 }
1045 if (trajectory.getT(trajectory.size() - 1) < startTime.si - getDelay().si)
1046 {
1047 assigned.add(i);
1048 }
1049 }
1050 i++;
1051 }
1052 catch (SamplingException exception)
1053 {
1054 throw new RuntimeException("Unexpected exception while counting trajectories.", exception);
1055 }
1056 }
1057 if (!this.aggregateLanes)
1058 {
1059 this.firstMeasurement[series][nextPeriod] = first;
1060 this.secondMeasurement[series][nextPeriod] = second;
1061 }
1062
1063
1064 if (!assigned.isEmpty())
1065 {
1066 int possibleNextLastAssigned = assigned.first();
1067 while (possibleNextLastAssigned == last + 1)
1068 {
1069 last = possibleNextLastAssigned;
1070 assigned.remove(possibleNextLastAssigned);
1071 possibleNextLastAssigned = assigned.isEmpty() ? -1 : assigned.first();
1072 }
1073 this.lastConsecutivelyAssignedTrajectories.put(lane, last);
1074 }
1075 }
1076 }
1077 if (this.aggregateLanes)
1078 {
1079
1080 this.firstMeasurement[0][nextPeriod] = first / this.space.getNumberOfSeries();
1081 this.secondMeasurement[0][nextPeriod] = second / this.space.getNumberOfSeries();
1082 }
1083 this.periodNumber = nextPeriod;
1084 }
1085
1086
1087 @Override
1088 public int getNumberOfSeries()
1089 {
1090
1091
1092 this.invalid = false;
1093 return this.nSeries;
1094 }
1095
1096
1097 @Override
1098 public void setAggregateName(final String aggregateName)
1099 {
1100 this.aggregateName = aggregateName;
1101 }
1102
1103
1104 @Override
1105 public String getName(final int series)
1106 {
1107 if (this.aggregateLanes)
1108 {
1109 return this.aggregateName;
1110 }
1111 return this.space.getName(series);
1112 }
1113
1114
1115 @Override
1116 public int getItemCount(final int series)
1117 {
1118 return this.periodNumber + 1;
1119 }
1120
1121
1122 @Override
1123 public final double getFlow(final int series, final int item)
1124 {
1125 if (this.invalid)
1126 {
1127 return Double.NaN;
1128 }
1129 return getVehicleCount(this.firstMeasurement[series][item], this.secondMeasurement[series][item])
1130 / this.aggregationPeriod.si;
1131 }
1132
1133
1134 @Override
1135 public final double getDensity(final int series, final int item)
1136 {
1137 return getFlow(series, item) / getSpeed(series, item);
1138 }
1139
1140
1141 @Override
1142 public final double getSpeed(final int series, final int item)
1143 {
1144 if (this.invalid)
1145 {
1146 return Double.NaN;
1147 }
1148 return getSpeed(this.firstMeasurement[series][item], this.secondMeasurement[series][item]);
1149 }
1150
1151
1152 @Override
1153 public final boolean isAggregate()
1154 {
1155 return this.aggregateLanes;
1156 }
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169 protected abstract void getMeasurements(Trajectory<?> trajectory, Time startTime, Time endTime, Length length,
1170 int series, double[] measurements);
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183 protected abstract double getVehicleCount(double first, double second);
1184
1185
1186
1187
1188
1189
1190
1191
1192 protected abstract double getSpeed(double first, double second);
1193
1194 }
1195
1196
1197
1198
1199
1200
1201 private static class MultiFdSource extends AbstractFdSource
1202 {
1203
1204
1205 private FdSource[] sources;
1206
1207
1208 private String[] sourceNames;
1209
1210
1211
1212
1213
1214 MultiFdSource(final Map<String, FdSource> sources)
1215 {
1216 Throw.when(sources == null || sources.size() == 0, IllegalArgumentException.class,
1217 "At least 1 source is required.");
1218 this.sources = new FdSource[sources.size()];
1219 this.sourceNames = new String[sources.size()];
1220 int index = 0;
1221 for (Entry<String, FdSource> entry : sources.entrySet())
1222 {
1223 this.sources[index] = entry.getValue();
1224 this.sourceNames[index] = entry.getKey();
1225 index++;
1226 }
1227 }
1228
1229
1230
1231
1232
1233
1234 private int[] getSourceAndSeries(final int series)
1235 {
1236 int source = 0;
1237 int sourceSeries = series;
1238 while (sourceSeries >= this.sources[source].getNumberOfSeries())
1239 {
1240 sourceSeries -= this.sources[source].getNumberOfSeries();
1241 source++;
1242 }
1243 return new int[] {source, sourceSeries};
1244 }
1245
1246
1247 @Override
1248 public Duration getUpdateInterval()
1249 {
1250 return this.sources[0].getUpdateInterval();
1251 }
1252
1253
1254 @Override
1255 public void setUpdateInterval(final Duration interval, final Time time)
1256 {
1257 for (FdSource source : this.sources)
1258 {
1259 source.setUpdateInterval(interval, time);
1260 }
1261 }
1262
1263
1264 @Override
1265 public Duration getAggregationPeriod()
1266 {
1267 return this.sources[0].getAggregationPeriod();
1268 }
1269
1270
1271 @Override
1272 public void setAggregationPeriod(final Duration period)
1273 {
1274 for (FdSource source : this.sources)
1275 {
1276 source.setAggregationPeriod(period);
1277 }
1278 }
1279
1280
1281 @Override
1282 public void recalculate(final Time time)
1283 {
1284 for (FdSource source : this.sources)
1285 {
1286 source.recalculate(time);
1287 }
1288 }
1289
1290
1291 @Override
1292 public Duration getDelay()
1293 {
1294 return this.sources[0].getDelay();
1295 }
1296
1297
1298 @Override
1299 public void increaseTime(final Time time)
1300 {
1301 for (FdSource source : this.sources)
1302 {
1303 source.increaseTime(time);
1304 }
1305 }
1306
1307
1308 @Override
1309 public int getNumberOfSeries()
1310 {
1311 int numberOfSeries = 0;
1312 for (FdSource source : this.sources)
1313 {
1314 numberOfSeries += source.getNumberOfSeries();
1315 }
1316 return numberOfSeries;
1317 }
1318
1319
1320 @Override
1321 public String getName(final int series)
1322 {
1323 int[] ss = getSourceAndSeries(series);
1324 return this.sourceNames[ss[0]]
1325 + (this.sources[ss[0]].isAggregate() ? "" : ": " + this.sources[ss[0]].getName(ss[1]));
1326 }
1327
1328
1329 @Override
1330 public int getItemCount(final int series)
1331 {
1332 int[] ss = getSourceAndSeries(series);
1333 return this.sources[ss[0]].getItemCount(ss[1]);
1334 }
1335
1336
1337 @Override
1338 public double getFlow(final int series, final int item)
1339 {
1340 int[] ss = getSourceAndSeries(series);
1341 return this.sources[ss[0]].getFlow(ss[1], item);
1342 }
1343
1344
1345 @Override
1346 public double getDensity(final int series, final int item)
1347 {
1348 int[] ss = getSourceAndSeries(series);
1349 return this.sources[ss[0]].getDensity(ss[1], item);
1350 }
1351
1352
1353 @Override
1354 public double getSpeed(final int series, final int item)
1355 {
1356 int[] ss = getSourceAndSeries(series);
1357 return this.sources[ss[0]].getSpeed(ss[1], item);
1358 }
1359
1360
1361 @Override
1362 public boolean isAggregate()
1363 {
1364 return false;
1365 }
1366
1367
1368 @Override
1369 public void setAggregateName(final String aggregateName)
1370 {
1371
1372 }
1373
1374 }
1375
1376
1377
1378
1379 public interface FdLine
1380 {
1381
1382
1383
1384
1385
1386 double[] getValues(Quantity quantity);
1387
1388
1389
1390
1391
1392 String getName();
1393 }
1394
1395
1396 @Override
1397 public String toString()
1398 {
1399 return "FundamentalDiagram [source=" + this.getSource() + ", domainQuantity=" + this.getDomainQuantity()
1400 + ", rangeQuantity=" + this.getRangeQuantity() + ", otherQuantity=" + this.getOtherQuantity()
1401 + ", seriesLabels=" + this.seriesLabels + ", graphUpdater=" + this.graphUpdater + ", timeInfo="
1402 + this.getTimeInfo() + ", legend=" + this.legend + ", laneVisible=" + this.laneVisible + "]";
1403 }
1404
1405
1406
1407
1408
1409 public FdSource getSource()
1410 {
1411 return this.source;
1412 }
1413
1414
1415
1416
1417
1418 public LegendItemCollection getLegend()
1419 {
1420 return this.legend;
1421 }
1422
1423
1424
1425
1426
1427 public List<Boolean> getLaneVisible()
1428 {
1429 return this.laneVisible;
1430 }
1431
1432
1433
1434
1435
1436 public Quantity getDomainQuantity()
1437 {
1438 return this.domainQuantity;
1439 }
1440
1441
1442
1443
1444
1445 public void setDomainQuantity(final Quantity domainQuantity)
1446 {
1447 this.domainQuantity = domainQuantity;
1448 }
1449
1450
1451
1452
1453
1454 public Quantity getOtherQuantity()
1455 {
1456 return this.otherQuantity;
1457 }
1458
1459
1460
1461
1462
1463 public void setOtherQuantity(final Quantity otherQuantity)
1464 {
1465 this.otherQuantity = otherQuantity;
1466 }
1467
1468
1469
1470
1471
1472 public Quantity getRangeQuantity()
1473 {
1474 return this.rangeQuantity;
1475 }
1476
1477
1478
1479
1480
1481 public void setRangeQuantity(final Quantity rangeQuantity)
1482 {
1483 this.rangeQuantity = rangeQuantity;
1484 }
1485
1486
1487
1488
1489
1490 public String getTimeInfo()
1491 {
1492 return this.timeInfo;
1493 }
1494
1495
1496
1497
1498
1499 public void setTimeInfo(final String timeInfo)
1500 {
1501 this.timeInfo = timeInfo;
1502 }
1503
1504
1505
1506
1507
1508 public boolean hasLineFD()
1509 {
1510 return this.fdLine != null;
1511 }
1512
1513 }