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