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