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