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