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