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
37
38
39
40
41
42
43
44
45
46 public class FundamentalDiagram extends AbstractBoundedPlot implements XYDataset
47 {
48
49
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
53 public static final int[] DEFAULT_UPDATE_FREQUENCIES = new int[] {1, 2, 3, 5, 10};
54
55
56 private final FdSource source;
57
58
59 private Quantity domainQuantity;
60
61
62 private Quantity rangeQuantity;
63
64
65 private Quantity otherQuantity;
66
67
68 private final List<String> seriesLabels = new ArrayList<>();
69
70
71 private final GraphUpdater<Time> graphUpdater;
72
73
74 private String timeInfo = "";
75
76
77 private LegendItemCollection legend;
78
79
80 private final List<Boolean> laneVisible = new ArrayList<>();
81
82
83
84
85
86
87
88
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
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
125
126
127
128
129
130
131
132
133
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
147
148
149
150
151
152
153
154
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
167
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
179 @SuppressWarnings("synthetic-access")
180 @Override
181 public boolean isSeriesVisible(final int series)
182 {
183 return FundamentalDiagram.this.laneVisible.get(series);
184 }
185
186 };
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);
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
213 @Override
214 protected void increaseTime(final Time time)
215 {
216 if (this.graphUpdater != null && time.si >= this.getSource().getAggregationPeriod().si)
217 {
218 this.graphUpdater.offer(time);
219 }
220 }
221
222
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
234 @Override
235 public Comparable<String> getSeriesKey(final int series)
236 {
237 return this.seriesLabels.get(series);
238 }
239
240
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
250 @Override
251 public DomainOrder getDomainOrder()
252 {
253 return DomainOrder.NONE;
254 }
255
256
257 @Override
258 public int getItemCount(final int series)
259 {
260 return this.getSource().getItemCount(series);
261 }
262
263
264 @Override
265 public Number getX(final int series, final int item)
266 {
267 return getXValue(series, item);
268 }
269
270
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
278 @Override
279 public Number getY(final int series, final int item)
280 {
281 return getYValue(series, item);
282 }
283
284
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
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().format(this.getDomainQuantity().computeOther(this.getRangeQuantity(), domainValue, rangeValue))
304 + this.getTimeInfo();
305 }
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
465 public interface FdSource
466 {
467
468
469
470
471 default double[] getPossibleAggregationPeriods()
472 {
473 return DEFAULT_PERIODS;
474 }
475
476
477
478
479
480 default int[] getPossibleUpdateFrequencies()
481 {
482 return DEFAULT_UPDATE_FREQUENCIES;
483 }
484
485
486
487
488
489 Duration getUpdateInterval();
490
491
492
493
494
495
496
497 void setUpdateInterval(Duration interval, Time time, FundamentalDiagram fd);
498
499
500
501
502
503 Duration getAggregationPeriod();
504
505
506
507
508
509 void setAggregationPeriod(Duration period);
510
511
512
513
514
515 Duration getDelay();
516
517
518
519
520
521 void increaseTime(Time time);
522
523
524
525
526
527 int getNumberOfSeries();
528
529
530
531
532
533
534 String getName(int series);
535
536
537
538
539
540
541 int getItemCount(int series);
542
543
544
545
546
547
548
549 double getFlow(int series, int item);
550
551
552
553
554
555
556
557 double getDensity(int series, int item);
558
559
560
561
562
563
564
565 double getSpeed(int series, int item);
566 }
567
568
569
570
571
572
573
574
575
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
586
587
588
589
590
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
600
601
602
603
604
605
606
607
608
609
610
611 private static class CrossSectionSamplerFdSource<S extends GraphCrossSection<? extends KpiLaneDirection>>
612 extends AbstractSpaceSamplerFdSource<S>
613 {
614
615 private final boolean harmonic;
616
617
618
619
620
621
622
623
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
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
641 Time t = trajectory.getTimeAtPosition(x);
642 if (t.si >= startTime.si && t.si < endTime.si)
643 {
644 measurements[0] = 1;
645 measurements[1] =
646 this.harmonic ? 1.0 / trajectory.getSpeedAtPosition(x).si : trajectory.getSpeedAtPosition(x).si;
647 }
648 }
649 }
650
651
652 @Override
653 protected double getVehicleCount(final double first, final double second)
654 {
655 return first;
656 }
657
658
659 @Override
660 protected double getSpeed(final double first, final double second)
661 {
662 return this.harmonic ? first / second : second / first;
663 }
664
665
666 @Override
667 public String toString()
668 {
669 return "CrossSectionSamplerFdSource [harmonic=" + this.harmonic + "]";
670 }
671
672 }
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687 private static class PathSamplerFdSource<S extends GraphPath<? extends KpiLaneDirection>>
688 extends AbstractSpaceSamplerFdSource<S>
689 {
690
691
692
693
694
695
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
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;
710 measurements[1] = stv.getTime().si;
711 }
712
713
714 @Override
715 protected double getVehicleCount(final double first, final double second)
716 {
717 return first / getSpace().getTotalLength().si;
718 }
719
720
721 @Override
722 protected double getSpeed(final double first, final double second)
723 {
724 return first / second;
725 }
726
727
728 @Override
729 public String toString()
730 {
731 return "PathSamplerFdSource []";
732 }
733
734 }
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749 private abstract static class AbstractSpaceSamplerFdSource<S extends AbstractGraphSpace<? extends KpiLaneDirection>>
750 implements FdSource
751 {
752
753 private int periodNumber = -1;
754
755
756 private Duration updateInterval;
757
758
759 private Duration aggregationPeriod;
760
761
762 private final int nSeries;
763
764
765 private double[][] firstMeasurement;
766
767
768 private double[][] secondMeasurement;
769
770
771 private boolean invalid = false;
772
773
774 private final Sampler<?> sampler;
775
776
777 private final S space;
778
779
780 private final boolean aggregateLanes;
781
782
783 private Map<KpiLaneDirection, Integer> lastConsecutivelyAssignedTrajectories = new LinkedHashMap<>();
784
785
786 private Map<KpiLaneDirection, SortedSet<Integer>> assignedTrajectories = new LinkedHashMap<>();
787
788
789
790
791
792
793
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
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
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
821
822
823 protected S getSpace()
824 {
825 return this.space;
826 }
827
828
829 @Override
830 public Duration getUpdateInterval()
831 {
832 return this.updateInterval;
833 }
834
835
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
847 @Override
848 public Duration getAggregationPeriod()
849 {
850 return this.aggregationPeriod;
851 }
852
853
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
865
866
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
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
908 @Override
909 public Duration getDelay()
910 {
911 return Duration.instantiateSI(1.0);
912 }
913
914
915 @Override
916 public synchronized void increaseTime(final Time time)
917 {
918 if (time.si < this.aggregationPeriod.si)
919 {
920
921 return;
922 }
923
924
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
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
955 int i = 0;
956 for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
957 {
958
959 try
960 {
961 if (i > last && !assigned.contains(i))
962 {
963
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
991 if (!assigned.isEmpty())
992 {
993 int possibleNextLastAssigned = assigned.first();
994 while (possibleNextLastAssigned == last + 1)
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
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
1014 @Override
1015 public int getNumberOfSeries()
1016 {
1017
1018
1019 this.invalid = false;
1020 return this.nSeries;
1021 }
1022
1023
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
1035 @Override
1036 public int getItemCount(final int series)
1037 {
1038 return this.periodNumber + 1;
1039 }
1040
1041
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
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
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
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082 protected abstract void getMeasurements(Trajectory<?> trajectory, Time startTime, Time endTime, Length length,
1083 int series, double[] measurements);
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096 protected abstract double getVehicleCount(double first, double second);
1097
1098
1099
1100
1101
1102
1103
1104
1105 protected abstract double getSpeed(double first, double second);
1106
1107 }
1108
1109
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
1121
1122
1123 public FdSource getSource()
1124 {
1125 return source;
1126 }
1127
1128
1129
1130
1131
1132 public LegendItemCollection getLegend()
1133 {
1134 return legend;
1135 }
1136
1137
1138
1139
1140
1141 public List<Boolean> getLaneVisible()
1142 {
1143 return laneVisible;
1144 }
1145
1146
1147
1148
1149
1150 public Quantity getDomainQuantity()
1151 {
1152 return domainQuantity;
1153 }
1154
1155
1156
1157
1158
1159 public void setDomainQuantity(final Quantity domainQuantity)
1160 {
1161 this.domainQuantity = domainQuantity;
1162 }
1163
1164
1165
1166
1167
1168 public Quantity getOtherQuantity()
1169 {
1170 return otherQuantity;
1171 }
1172
1173
1174
1175
1176
1177 public void setOtherQuantity(final Quantity otherQuantity)
1178 {
1179 this.otherQuantity = otherQuantity;
1180 }
1181
1182
1183
1184
1185
1186 public Quantity getRangeQuantity()
1187 {
1188 return rangeQuantity;
1189 }
1190
1191
1192
1193
1194
1195 public void setRangeQuantity(final Quantity rangeQuantity)
1196 {
1197 this.rangeQuantity = rangeQuantity;
1198 }
1199
1200
1201
1202
1203
1204 public String getTimeInfo()
1205 {
1206 return timeInfo;
1207 }
1208
1209
1210
1211
1212
1213 public void setTimeInfo(final String timeInfo)
1214 {
1215 this.timeInfo = timeInfo;
1216 }
1217
1218 }