1 package org.opentrafficsim.draw.graphs;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.LinkedHashMap;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10
11 import org.djunits.unit.SpeedUnit;
12 import org.djunits.value.vdouble.scalar.Duration;
13 import org.djunits.value.vdouble.scalar.Length;
14 import org.djunits.value.vdouble.scalar.Speed;
15 import org.djunits.value.vdouble.scalar.Time;
16 import org.djutils.exceptions.Throw;
17 import org.djutils.logger.CategoryLogger;
18 import org.opentrafficsim.draw.egtf.Converter;
19 import org.opentrafficsim.draw.egtf.DataSource;
20 import org.opentrafficsim.draw.egtf.DataStream;
21 import org.opentrafficsim.draw.egtf.Egtf;
22 import org.opentrafficsim.draw.egtf.EgtfEvent;
23 import org.opentrafficsim.draw.egtf.EgtfListener;
24 import org.opentrafficsim.draw.egtf.Filter;
25 import org.opentrafficsim.draw.egtf.Quantity;
26 import org.opentrafficsim.draw.egtf.typed.TypedQuantity;
27 import org.opentrafficsim.draw.graphs.GraphPath.Section;
28 import org.opentrafficsim.kpi.interfaces.LaneData;
29 import org.opentrafficsim.kpi.sampling.SamplerData;
30 import org.opentrafficsim.kpi.sampling.Trajectory;
31 import org.opentrafficsim.kpi.sampling.Trajectory.SpaceTimeView;
32 import org.opentrafficsim.kpi.sampling.TrajectoryGroup;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 public class ContourDataSource
48 {
49
50
51
52
53
54
55 protected static final double[] DEFAULT_SPACE_GRANULARITIES = {10, 20, 50, 100, 200, 500, 1000};
56
57
58 protected static final int DEFAULT_SPACE_GRANULARITY_INDEX = 3;
59
60
61 protected static final double[] DEFAULT_TIME_GRANULARITIES = {1, 2, 5, 10, 20, 30, 60, 120, 300, 600};
62
63
64 protected static final int DEFAULT_TIME_GRANULARITY_INDEX = 3;
65
66
67 protected static final Time DEFAULT_LOWER_TIME_BOUND = Time.ZERO;
68
69
70
71
72
73
74 private static final int KERNEL_FACTOR = 5;
75
76
77 private static final Length SIGMA = Length.instantiateSI(300);
78
79
80 private static final Duration TAU = Duration.instantiateSI(30);
81
82
83 private static final Speed MAX_C_FREE = new Speed(80.0, SpeedUnit.KM_PER_HOUR);
84
85
86 private static final double VC_FACRTOR = 0.8;
87
88
89 private static final Speed C_CONG = new Speed(-18.0, SpeedUnit.KM_PER_HOUR);
90
91
92 private static final Speed DELTA_V = new Speed(10.0, SpeedUnit.KM_PER_HOUR);
93
94
95
96
97
98
99 private final SamplerData<?> samplerData;
100
101
102 private final Duration updateInterval;
103
104
105 private final Duration delay;
106
107
108 private final GraphPath<? extends LaneData<?>> path;
109
110
111 final Axis spaceAxis;
112
113
114 final Axis timeAxis;
115
116
117 private Set<AbstractContourPlot<?>> plots = new LinkedHashSet<>();
118
119
120
121
122
123
124 private float[][] distance;
125
126
127 private float[][] time;
128
129
130 private final Map<ContourDataType<?, ?>, float[][]> additionalData = new LinkedHashMap<>();
131
132
133
134
135
136
137 private Speed cFree;
138
139
140 private Speed vc;
141
142
143 private Egtf egtf;
144
145
146 private DataStream<Speed> speedStream;
147
148
149 private DataStream<Duration> travelTimeStream;
150
151
152 private DataStream<Length> travelDistanceStream;
153
154
155 private final Quantity<Duration, double[][]> travelTimeQuantity = new Quantity<>("travel time", Converter.SI);
156
157
158 private final Quantity<Length, double[][]> travelDistanceQuantity = new Quantity<>("travel distance", Converter.SI);
159
160
161 private Map<ContourDataType<?, ?>, DataStream<?>> additionalStreams = new LinkedHashMap<>();
162
163
164
165
166
167
168 private final GraphUpdater<Time> graphUpdater;
169
170
171 private boolean redo = true;
172
173
174 private Time toTime;
175
176
177 private int readyItems = -1;
178
179
180 private Double desiredSpaceGranularity = null;
181
182
183 private Double desiredTimeGranularity = null;
184
185
186 private boolean smooth = false;
187
188
189
190
191
192
193
194
195
196
197 public ContourDataSource(final SamplerData<?> samplerData, final GraphPath<? extends LaneData<?>> path)
198 {
199 this(samplerData, Duration.instantiateSI(1.0), path, DEFAULT_SPACE_GRANULARITIES, DEFAULT_SPACE_GRANULARITY_INDEX,
200 DEFAULT_TIME_GRANULARITIES, DEFAULT_TIME_GRANULARITY_INDEX, DEFAULT_LOWER_TIME_BOUND,
201 AbstractPlot.DEFAULT_INITIAL_UPPER_TIME_BOUND);
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
216 @SuppressWarnings("parameternumber")
217 public ContourDataSource(final SamplerData<?> samplerData, final Duration delay, final GraphPath<? extends LaneData<?>> path,
218 final double[] spaceGranularity, final int initSpaceIndex, final double[] timeGranularity, final int initTimeIndex,
219 final Time start, final Time initialEnd)
220 {
221 this.samplerData = samplerData;
222 this.updateInterval = Duration.instantiateSI(timeGranularity[initTimeIndex]);
223 this.delay = delay;
224 this.path = path;
225 this.spaceAxis = new Axis(0.0, path.getTotalLength().si, spaceGranularity[initSpaceIndex], spaceGranularity);
226 this.timeAxis = new Axis(start.si, initialEnd.si, timeGranularity[initTimeIndex], timeGranularity);
227
228
229 this.cFree = Speed.min(path.getSpeedLimit(), MAX_C_FREE);
230 this.vc = Speed.min(path.getSpeedLimit().times(VC_FACRTOR), MAX_C_FREE);
231
232
233 this.graphUpdater = new GraphUpdater<>("Contour Data Source worker", Thread.currentThread(), (t) -> update(t));
234 }
235
236
237
238
239
240
241
242
243
244 public final SamplerData<?> getSamplerData()
245 {
246 return this.samplerData;
247 }
248
249
250
251
252
253 final Duration getUpdateInterval()
254 {
255 return this.updateInterval;
256 }
257
258
259
260
261
262 final Duration getDelay()
263 {
264 return this.delay;
265 }
266
267
268
269
270
271 final GraphPath<? extends LaneData<?>> getPath()
272 {
273 return this.path;
274 }
275
276
277
278
279
280 final void registerContourPlot(final AbstractContourPlot<?> contourPlot)
281 {
282 ContourDataType<?, ?> contourDataType = contourPlot.getContourDataType();
283 if (contourDataType != null)
284 {
285 this.additionalData.put(contourDataType, null);
286 }
287 this.plots.add(contourPlot);
288 }
289
290
291
292
293
294
295 final int getBinCount(final Dimension dimension)
296 {
297 return dimension.getAxis(this).getBinCount();
298 }
299
300
301
302
303
304
305
306 final synchronized double getBinSize(final Dimension dimension, final int item)
307 {
308 int n = dimension.equals(Dimension.DISTANCE) ? getSpaceBin(item) : getTimeBin(item);
309 double[] ticks = dimension.getAxis(this).getTicks();
310 return ticks[n + 1] - ticks[n];
311 }
312
313
314
315
316
317
318
319 final double getAxisValue(final Dimension dimension, final int item)
320 {
321 if (dimension.equals(Dimension.DISTANCE))
322 {
323 return this.spaceAxis.getBinValue(getSpaceBin(item));
324 }
325 return this.timeAxis.getBinValue(getTimeBin(item));
326 }
327
328
329
330
331
332
333
334 final int getAxisBin(final Dimension dimension, final double value)
335 {
336 if (dimension.equals(Dimension.DISTANCE))
337 {
338 return this.spaceAxis.getValueBin(value);
339 }
340 return this.timeAxis.getValueBin(value);
341 }
342
343
344
345
346
347
348 @SuppressWarnings("synthetic-access")
349 public final double[] getGranularities(final Dimension dimension)
350 {
351 return dimension.getAxis(this).granularities;
352 }
353
354
355
356
357
358
359 @SuppressWarnings("synthetic-access")
360 public final double getGranularity(final Dimension dimension)
361 {
362 return dimension.getAxis(this).granularity;
363 }
364
365
366
367
368
369 @SuppressWarnings("synthetic-access")
370 final synchronized void increaseTime(final Time updateTime)
371 {
372 if (updateTime.si > this.timeAxis.maxValue)
373 {
374 this.timeAxis.setMaxValue(updateTime.si);
375 for (AbstractContourPlot<?> plot : this.plots)
376 {
377 plot.setUpperDomainBound(updateTime.si);
378 }
379 }
380 if (this.toTime == null || updateTime.si > this.toTime.si)
381 {
382 invalidate(updateTime);
383 }
384 }
385
386
387
388
389
390
391 public final synchronized void setGranularity(final Dimension dimension, final double granularity)
392 {
393 if (dimension.equals(Dimension.DISTANCE))
394 {
395 this.desiredSpaceGranularity = granularity;
396 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
397 {
398 contourPlot.setSpaceGranularity(granularity);
399 }
400 }
401 else
402 {
403 this.desiredTimeGranularity = granularity;
404 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
405 {
406 contourPlot.setUpdateInterval(Duration.instantiateSI(granularity));
407 contourPlot.setTimeGranularity(granularity);
408 }
409 }
410 invalidate(null);
411 }
412
413
414
415
416
417 @SuppressWarnings("synthetic-access")
418 public final void setInterpolate(final boolean interpolate)
419 {
420 if (this.timeAxis.interpolate != interpolate)
421 {
422 synchronized (this)
423 {
424 this.timeAxis.setInterpolate(interpolate);
425 this.spaceAxis.setInterpolate(interpolate);
426 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
427 {
428 contourPlot.setInterpolation(interpolate);
429 }
430 invalidate(null);
431 }
432 }
433 }
434
435
436
437
438
439 public final void setSmooth(final boolean smooth)
440 {
441 if (this.smooth != smooth)
442 {
443 synchronized (this)
444 {
445 this.smooth = smooth;
446 invalidate(null);
447 }
448 }
449 }
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466 private synchronized void invalidate(final Time t)
467 {
468 if (t != null)
469 {
470 this.toTime = t;
471 }
472 else
473 {
474 this.redo = true;
475 }
476 if (this.toTime != null)
477 {
478
479
480 this.graphUpdater.offer(this.toTime);
481 }
482 }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499 @SuppressWarnings({"synthetic-access", "methodlength"})
500 private void update(final Time t)
501 {
502 Throw.when(this.plots.isEmpty(), IllegalStateException.class, "ContourDataSource is used, but not by a contour plot!");
503
504 if (t.si < this.toTime.si)
505 {
506
507 return;
508 }
509
510
511
512
513
514
515 boolean redo0;
516 boolean smooth0;
517 boolean interpolate0;
518 double timeGranularity;
519 double spaceGranularity;
520 double[] spaceTicks;
521 double[] timeTicks;
522 int fromSpaceIndex = 0;
523 int fromTimeIndex = 0;
524 int toTimeIndex;
525 double tFromEgtf = 0;
526 int nFromEgtf = 0;
527 synchronized (this)
528 {
529
530 redo0 = this.redo;
531 smooth0 = this.smooth;
532 interpolate0 = this.timeAxis.interpolate;
533
534 if (this.desiredTimeGranularity != null)
535 {
536 this.timeAxis.setGranularity(this.desiredTimeGranularity);
537 this.desiredTimeGranularity = null;
538 }
539 if (this.desiredSpaceGranularity != null)
540 {
541 this.spaceAxis.setGranularity(this.desiredSpaceGranularity);
542 this.desiredSpaceGranularity = null;
543 }
544 timeGranularity = this.timeAxis.granularity;
545 spaceGranularity = this.spaceAxis.granularity;
546 spaceTicks = this.spaceAxis.getTicks();
547 timeTicks = this.timeAxis.getTicks();
548 if (!redo0)
549 {
550
551 fromSpaceIndex = getSpaceBin(this.readyItems + 1);
552 fromTimeIndex = getTimeBin(this.readyItems + 1);
553 }
554 toTimeIndex = ((int) (t.si / timeGranularity)) - (interpolate0 ? 0 : 1);
555 if (smooth0)
556 {
557
558 tFromEgtf = this.timeAxis.getBinValue(redo0 ? 0 : this.timeAxis.getValueBin(
559 this.timeAxis.getBinValue(fromTimeIndex) - Math.max(TAU.si, timeGranularity / 2) * KERNEL_FACTOR));
560 nFromEgtf = this.timeAxis.getValueBin(tFromEgtf);
561 }
562
563 this.redo = false;
564 }
565
566
567 if (redo0)
568 {
569 this.readyItems = -1;
570
571
572 int nSpace = spaceTicks.length - 1;
573 int nTime = timeTicks.length - 1;
574 this.distance = new float[nSpace][nTime];
575 this.time = new float[nSpace][nTime];
576 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
577 {
578 this.additionalData.put(contourDataType, new float[nSpace][nTime]);
579 }
580
581
582 if (smooth0)
583 {
584
585 this.egtf = new Egtf(C_CONG.si, this.cFree.si, DELTA_V.si, this.vc.si);
586
587
588 DataSource generic = this.egtf.getDataSource("generic");
589 generic.addStream(TypedQuantity.SPEED, Speed.instantiateSI(1.0), Speed.instantiateSI(1.0));
590 generic.addStreamSI(this.travelTimeQuantity, 1.0, 1.0);
591 generic.addStreamSI(this.travelDistanceQuantity, 1.0, 1.0);
592 this.speedStream = generic.getStream(TypedQuantity.SPEED);
593 this.travelTimeStream = generic.getStream(this.travelTimeQuantity);
594 this.travelDistanceStream = generic.getStream(this.travelDistanceQuantity);
595 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
596 {
597 this.additionalStreams.put(contourDataType, generic.addStreamSI(contourDataType.getQuantity(), 1.0, 1.0));
598 }
599
600
601 double tau2 = Math.max(TAU.si, timeGranularity / 2);
602 double sigma2 = Math.max(SIGMA.si, spaceGranularity / 2);
603
604 this.egtf.setGaussKernelSI(sigma2 * KERNEL_FACTOR, tau2 * KERNEL_FACTOR, sigma2, tau2);
605
606
607 this.egtf.addListener(new EgtfListener()
608 {
609
610 @Override
611 public void notifyProgress(final EgtfEvent event)
612 {
613
614 if (ContourDataSource.this.redo)
615 {
616
617 event.interrupt();
618 }
619 }
620 });
621 }
622 }
623
624
625 if (!smooth0)
626 {
627
628 this.egtf = null;
629 this.speedStream = null;
630 this.travelTimeStream = null;
631 this.travelDistanceStream = null;
632 this.additionalStreams.clear();
633 }
634
635
636 for (int i = 0; i < this.distance.length; i++)
637 {
638 this.distance[i] = GraphUtil.ensureCapacity(this.distance[i], toTimeIndex + 1);
639 this.time[i] = GraphUtil.ensureCapacity(this.time[i], toTimeIndex + 1);
640 for (float[][] additional : this.additionalData.values())
641 {
642 additional[i] = GraphUtil.ensureCapacity(additional[i], toTimeIndex + 1);
643 }
644 }
645
646
647 for (int j = fromTimeIndex; j <= toTimeIndex; j++)
648 {
649 Time tFrom = Time.instantiateSI(timeTicks[j]);
650 Time tTo = Time.instantiateSI(timeTicks[j + 1]);
651
652
653
654 for (int i = fromSpaceIndex; i < spaceTicks.length - 1; i++)
655 {
656
657 if ((j == 0 || i == 0) && interpolate0)
658 {
659 this.distance[i][j] = Float.NaN;
660 this.time[i][j] = Float.NaN;
661 this.readyItems++;
662 continue;
663 }
664
665
666 fromSpaceIndex = 0;
667 Length xFrom = Length.instantiateSI(spaceTicks[i]);
668 Length xTo = Length.instantiateSI(Math.min(spaceTicks[i + 1], this.path.getTotalLength().si));
669
670
671 double totalDistance = 0.0;
672 double totalTime = 0.0;
673 Map<ContourDataType<?, ?>, Object> additionalIntermediate = new LinkedHashMap<>();
674 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
675 {
676 additionalIntermediate.put(contourDataType, contourDataType.identity());
677 }
678
679
680 for (int series = 0; series < this.path.getNumberOfSeries(); series++)
681 {
682
683 List<TrajectoryGroup<?>> trajectories = new ArrayList<>();
684 for (Section<? extends LaneData<?>> section : getPath().getSections())
685 {
686 TrajectoryGroup<?> trajectoryGroup = this.samplerData.getTrajectoryGroup(section.getSource(series));
687 if (null == trajectoryGroup)
688 {
689 CategoryLogger.always().error("trajectoryGroup {} is null", series);
690 }
691 trajectories.add(trajectoryGroup);
692 }
693
694
695 List<TrajectoryGroup<?>> included = new ArrayList<>();
696 List<Length> xStart = new ArrayList<>();
697 List<Length> xEnd = new ArrayList<>();
698 for (int k = 0; k < trajectories.size(); k++)
699 {
700 TrajectoryGroup<?> trajectoryGroup = trajectories.get(k);
701 LaneData<?> lane = trajectoryGroup.getLane();
702 Length startDistance = this.path.getStartDistance(this.path.get(k));
703 if (startDistance.si + this.path.get(k).length().si > spaceTicks[i]
704 && startDistance.si < spaceTicks[i + 1])
705 {
706 included.add(trajectoryGroup);
707 double scale = this.path.get(k).length().si / lane.getLength().si;
708
709 xStart.add(Length.max(xFrom.minus(startDistance).divide(scale), Length.ZERO));
710 xEnd.add(Length.min(xTo.minus(startDistance).divide(scale), trajectoryGroup.getLane().getLength()));
711 }
712 }
713
714
715 for (int k = 0; k < included.size(); k++)
716 {
717 TrajectoryGroup<?> trajectoryGroup = included.get(k);
718 for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
719 {
720
721
722 if (GraphUtil.considerTrajectory(trajectory, tFrom, tTo))
723 {
724
725 SpaceTimeView spaceTimeView;
726 try
727 {
728 spaceTimeView = trajectory.getSpaceTimeView(xStart.get(k), xEnd.get(k), tFrom, tTo);
729 }
730 catch (IllegalArgumentException exception)
731 {
732 CategoryLogger.always().debug(exception,
733 "Unable to generate space-time view from x = {} to {} and t = {} to {}.",
734 xStart.get(k), xEnd.get(k), tFrom, tTo);
735 continue;
736 }
737 totalDistance += spaceTimeView.getDistance().si;
738 totalTime += spaceTimeView.getTime().si;
739 }
740 }
741 }
742
743
744 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
745 {
746 addAdditional(additionalIntermediate, contourDataType, included, xStart, xEnd, tFrom, tTo);
747 }
748
749 }
750
751
752 double norm = spaceGranularity / (xTo.si - xFrom.si) / this.path.getNumberOfSeries();
753 totalDistance *= norm;
754 totalTime *= norm;
755 this.distance[i][j] = (float) totalDistance;
756 this.time[i][j] = (float) totalTime;
757 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
758 {
759 this.additionalData.get(contourDataType)[i][j] =
760 finalizeAdditional(additionalIntermediate, contourDataType);
761 }
762
763
764 if (smooth0)
765 {
766
767 double xDat = (xFrom.si + xTo.si) / 2.0;
768 double tDat = (tFrom.si + tTo.si) / 2.0;
769
770 this.egtf.addPointDataSI(this.speedStream, xDat, tDat, totalDistance / totalTime);
771 this.egtf.addPointDataSI(this.travelDistanceStream, xDat, tDat, totalDistance);
772 this.egtf.addPointDataSI(this.travelTimeStream, xDat, tDat, totalTime);
773 for (ContourDataType<?, ?> contourDataType : this.additionalStreams.keySet())
774 {
775 ContourDataSource.this.egtf.addPointDataSI(
776 ContourDataSource.this.additionalStreams.get(contourDataType), xDat, tDat,
777 this.additionalData.get(contourDataType)[i][j]);
778 }
779 }
780
781
782 if (this.redo)
783 {
784
785 return;
786 }
787
788
789 this.readyItems++;
790 }
791
792
793 this.plots.forEach((plot) -> plot.notifyPlotChange());
794 }
795
796
797 if (smooth0)
798 {
799 Set<Quantity<?, ?>> quantities = new LinkedHashSet<>();
800 quantities.add(this.travelDistanceQuantity);
801 quantities.add(this.travelTimeQuantity);
802 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
803 {
804 quantities.add(contourDataType.getQuantity());
805 }
806 Filter filter = this.egtf.filterFastSI(spaceTicks[0] + 0.5 * spaceGranularity, spaceGranularity,
807 spaceTicks[0] + (-1.5 + spaceTicks.length) * spaceGranularity, tFromEgtf, timeGranularity, t.si,
808 quantities.toArray(new Quantity<?, ?>[quantities.size()]));
809 if (filter != null)
810 {
811 overwriteSmoothed(this.distance, nFromEgtf, filter.getSI(this.travelDistanceQuantity));
812 overwriteSmoothed(this.time, nFromEgtf, filter.getSI(this.travelTimeQuantity));
813 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
814 {
815 overwriteSmoothed(this.additionalData.get(contourDataType), nFromEgtf,
816 filter.getSI(contourDataType.getQuantity()));
817 }
818 this.plots.forEach((plot) -> plot.notifyPlotChange());
819 }
820 }
821 }
822
823
824
825
826
827
828
829
830
831
832
833
834 @SuppressWarnings("unchecked")
835 private <I> void addAdditional(final Map<ContourDataType<?, ?>, Object> additionalIntermediate,
836 final ContourDataType<?, ?> contourDataType, final List<TrajectoryGroup<?>> included, final List<Length> xStart,
837 final List<Length> xEnd, final Time tFrom, final Time tTo)
838 {
839 additionalIntermediate.put(contourDataType, ((ContourDataType<?, I>) contourDataType)
840 .processSeries((I) additionalIntermediate.get(contourDataType), included, xStart, xEnd, tFrom, tTo));
841 }
842
843
844
845
846
847
848
849
850 @SuppressWarnings("unchecked")
851 private <I> float finalizeAdditional(final Map<ContourDataType<?, ?>, Object> additionalIntermediate,
852 final ContourDataType<?, ?> contourDataType)
853 {
854 return ((ContourDataType<?, I>) contourDataType).finalize((I) additionalIntermediate.get(contourDataType)).floatValue();
855 }
856
857
858
859
860
861
862
863 private void overwriteSmoothed(final float[][] raw, final int rawCol, final double[][] smoothed)
864 {
865 for (int i = 0; i < raw.length; i++)
866 {
867
868 for (int j = 0; j < smoothed[i].length; j++)
869 {
870 raw[i][j + rawCol] = (float) smoothed[i][j];
871 }
872 }
873 }
874
875
876
877
878
879
880
881
882
883
884 public double getSpeed(final int item)
885 {
886 if (item > this.readyItems)
887 {
888 return Double.NaN;
889 }
890 return getTotalDistance(item) / getTotalTime(item);
891 }
892
893
894
895
896
897
898 public double getTotalDistance(final int item)
899 {
900 if (item > this.readyItems)
901 {
902 return Double.NaN;
903 }
904 return this.distance[getSpaceBin(item)][getTimeBin(item)];
905 }
906
907
908
909
910
911
912 public double getTotalTime(final int item)
913 {
914 if (item > this.readyItems)
915 {
916 return Double.NaN;
917 }
918 return this.time[getSpaceBin(item)][getTimeBin(item)];
919 }
920
921
922
923
924
925
926
927 public double get(final int item, final ContourDataType<?, ?> contourDataType)
928 {
929 if (item > this.readyItems)
930 {
931 return Double.NaN;
932 }
933 return this.additionalData.get(contourDataType)[getSpaceBin(item)][getTimeBin(item)];
934 }
935
936
937
938
939
940
941 private int getTimeBin(final int item)
942 {
943 Throw.when(item < 0 || item >= this.spaceAxis.getBinCount() * this.timeAxis.getBinCount(),
944 IndexOutOfBoundsException.class, "Item out of range");
945 return item / this.spaceAxis.getBinCount();
946 }
947
948
949
950
951
952
953 private int getSpaceBin(final int item)
954 {
955 return item % this.spaceAxis.getBinCount();
956 }
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973 public enum Dimension
974 {
975
976 DISTANCE
977 {
978
979 @Override
980 protected Axis getAxis(final ContourDataSource dataPool)
981 {
982 return dataPool.spaceAxis;
983 }
984 },
985
986
987 TIME
988 {
989
990 @Override
991 protected Axis getAxis(final ContourDataSource dataPool)
992 {
993 return dataPool.timeAxis;
994 }
995 };
996
997
998
999
1000
1001
1002 protected abstract Axis getAxis(ContourDataSource dataPool);
1003 }
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016 static class Axis
1017 {
1018
1019 private final double minValue;
1020
1021
1022 private double maxValue;
1023
1024
1025 private double granularity;
1026
1027
1028 private final double[] granularities;
1029
1030
1031 private boolean interpolate = true;
1032
1033
1034 private double[] ticks;
1035
1036
1037
1038
1039
1040
1041
1042
1043 Axis(final double minValue, final double maxValue, final double granularity, final double[] granularities)
1044 {
1045 this.minValue = minValue;
1046 this.maxValue = maxValue;
1047 this.granularity = granularity;
1048 this.granularities = granularities;
1049 }
1050
1051
1052
1053
1054
1055 void setMaxValue(final double maxValue)
1056 {
1057 if (this.maxValue != maxValue)
1058 {
1059 this.maxValue = maxValue;
1060 this.ticks = null;
1061 }
1062 }
1063
1064
1065
1066
1067
1068 void setGranularity(final double granularity)
1069 {
1070 if (this.granularity != granularity)
1071 {
1072 this.granularity = granularity;
1073 this.ticks = null;
1074 }
1075 }
1076
1077
1078
1079
1080
1081 double[] getTicks()
1082 {
1083 if (this.ticks == null)
1084 {
1085 int n = getBinCount() + 1;
1086 this.ticks = new double[n];
1087 int di = this.interpolate ? 1 : 0;
1088 for (int i = 0; i < n; i++)
1089 {
1090 if (i == n - 1)
1091 {
1092 this.ticks[i] = Math.min((i - di) * this.granularity, this.maxValue);
1093 }
1094 else
1095 {
1096 this.ticks[i] = (i - di) * this.granularity;
1097 }
1098 }
1099 }
1100 return this.ticks;
1101 }
1102
1103
1104
1105
1106
1107 int getBinCount()
1108 {
1109 return (int) Math.ceil((this.maxValue - this.minValue) / this.granularity) + (this.interpolate ? 1 : 0);
1110 }
1111
1112
1113
1114
1115
1116
1117 double getBinValue(final int bin)
1118 {
1119 return this.minValue + (0.5 + bin - (this.interpolate ? 1 : 0)) * this.granularity;
1120 }
1121
1122
1123
1124
1125
1126
1127 int getValueBin(final double value)
1128 {
1129 getTicks();
1130 if (value > this.ticks[this.ticks.length - 1])
1131 {
1132 return this.ticks.length - 1;
1133 }
1134 int i = 0;
1135 while (i < this.ticks.length - 1 && this.ticks[i + 1] < value + 1e-9)
1136 {
1137 i++;
1138 }
1139 return i;
1140 }
1141
1142
1143
1144
1145
1146 void setInterpolate(final boolean interpolate)
1147 {
1148 if (this.interpolate != interpolate)
1149 {
1150 this.interpolate = interpolate;
1151 this.ticks = null;
1152 }
1153 }
1154
1155
1156
1157
1158
1159 public boolean isInterpolate()
1160 {
1161 return this.interpolate;
1162 }
1163
1164
1165 @Override
1166 public String toString()
1167 {
1168 return "Axis [minValue=" + this.minValue + ", maxValue=" + this.maxValue + ", granularity=" + this.granularity
1169 + ", granularities=" + Arrays.toString(this.granularities) + ", interpolate=" + this.interpolate
1170 + ", ticks=" + Arrays.toString(this.ticks) + "]";
1171 }
1172
1173 }
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189 public interface ContourDataType<Z extends Number, I>
1190 {
1191
1192
1193
1194
1195 I identity();
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207 I processSeries(I intermediate, List<TrajectoryGroup<?>> trajectories, List<Length> xFrom, List<Length> xTo, Time tFrom,
1208 Time tTo);
1209
1210
1211
1212
1213
1214
1215 Z finalize(I intermediate);
1216
1217
1218
1219
1220
1221 Quantity<Z, ?> getQuantity();
1222 }
1223
1224
1225 @Override
1226 public String toString()
1227 {
1228 return "ContourDataSource [samplerData=" + this.samplerData + ", updateInterval=" + this.updateInterval + ", delay="
1229 + this.delay + ", path=" + this.path + ", spaceAxis=" + this.spaceAxis + ", timeAxis=" + this.timeAxis
1230 + ", plots=" + this.plots + ", distance=" + Arrays.toString(this.distance) + ", time="
1231 + Arrays.toString(this.time) + ", additionalData=" + this.additionalData + ", smooth=" + this.smooth
1232 + ", cFree=" + this.cFree + ", vc=" + this.vc + ", egtf=" + this.egtf + ", speedStream=" + this.speedStream
1233 + ", travelTimeStream=" + this.travelTimeStream + ", travelDistanceStream=" + this.travelDistanceStream
1234 + ", travelTimeQuantity=" + this.travelTimeQuantity + ", travelDistanceQuantity=" + this.travelDistanceQuantity
1235 + ", additionalStreams=" + this.additionalStreams + ", graphUpdater=" + this.graphUpdater + ", redo="
1236 + this.redo + ", toTime=" + this.toTime + ", readyItems=" + this.readyItems + ", desiredSpaceGranularity="
1237 + this.desiredSpaceGranularity + ", desiredTimeGranularity=" + this.desiredTimeGranularity + "]";
1238 }
1239
1240 }