View Javadoc
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.Sampler;
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   * Class that contains data for contour plots. One data source can be shared between contour plots, in which case the
37   * granularity, path, sampler, update interval, and whether the data is smoothed (EGTF) are equal between the plots.
38   * <p>
39   * By default the source contains traveled time and traveled distance per cell.
40   * <p>
41   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
42   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
43   * <p>
44   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 5 okt. 2018 <br>
45   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
46   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
47   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
48   * @param <G> gtu type data
49   */
50  public class ContourDataSource<G extends GtuDataInterface>
51  {
52  
53      // *************************
54      // *** GLOBAL PROPERTIES ***
55      // *************************
56  
57      /** Space granularity values. */
58      protected static final double[] DEFAULT_SPACE_GRANULARITIES = {10, 20, 50, 100, 200, 500, 1000};
59  
60      /** Index of the initial space granularity. */
61      protected static final int DEFAULT_SPACE_GRANULARITY_INDEX = 3;
62  
63      /** Time granularity values. */
64      protected static final double[] DEFAULT_TIME_GRANULARITIES = {1, 2, 5, 10, 20, 30, 60, 120, 300, 600};
65  
66      /** Index of the initial time granularity. */
67      protected static final int DEFAULT_TIME_GRANULARITY_INDEX = 3;
68  
69      /** Initial lower bound for the time scale. */
70      protected static final Time DEFAULT_LOWER_TIME_BOUND = Time.ZERO;
71  
72      /**
73       * Total kernel size relative to sigma and tau. This factor is determined through -log(1 - p) with p ~= 99%. This means that
74       * the cumulative exponential distribution has 99% at 5 times sigma or tau. Note that due to a coordinate change in the
75       * Adaptive Smoothing Method, the actual cumulative distribution is slightly different. Hence, this is just a heuristic.
76       */
77      private static final int KERNEL_FACTOR = 5;
78  
79      /** Spatial kernel size. Larger value may be used when using a large granularity. */
80      private static final Length SIGMA = Length.instantiateSI(300);
81  
82      /** Temporal kernel size. Larger value may be used when using a large granularity. */
83      private static final Duration TAU = Duration.instantiateSI(30);
84  
85      /** Maximum free flow propagation speed. */
86      private static final Speed MAX_C_FREE = new Speed(80.0, SpeedUnit.KM_PER_HOUR);
87  
88      /** Factor on speed limit to determine vc, the flip over speed between congestion and free flow. */
89      private static final double VC_FACRTOR = 0.8;
90  
91      /** Congestion propagation speed. */
92      private static final Speed C_CONG = new Speed(-18.0, SpeedUnit.KM_PER_HOUR);
93  
94      /** Delta v, speed transition region around threshold. */
95      private static final Speed DELTA_V = new Speed(10.0, SpeedUnit.KM_PER_HOUR);
96  
97      // *****************************
98      // *** CONTEXTUAL PROPERTIES ***
99      // *****************************
100 
101     /** Sampler. */
102     private final Sampler<G> sampler;
103 
104     /** Update interval. */
105     private final Duration updateInterval;
106 
107     /** Delay so critical future events have occurred, e.g. GTU's next move's to extend trajectories. */
108     private final Duration delay;
109 
110     /** Path. */
111     private final GraphPath<KpiLaneDirection> path;
112 
113     /** Space axis. */
114     final Axis spaceAxis;
115 
116     /** Time axis. */
117     final Axis timeAxis;
118 
119     /** Registered plots. */
120     private Set<AbstractContourPlot<?>> plots = new LinkedHashSet<>();
121 
122     // *****************
123     // *** PLOT DATA ***
124     // *****************
125 
126     /** Total distance traveled per cell. */
127     private float[][] distance;
128 
129     /** Total time traveled per cell. */
130     private float[][] time;
131 
132     /** Data of other types. */
133     private final Map<ContourDataType<?, ?>, float[][]> additionalData = new LinkedHashMap<>();
134 
135     // ****************************
136     // *** SMOOTHING PROPERTIES ***
137     // ****************************
138 
139     /** Free flow propagation speed. */
140     private Speed cFree;
141 
142     /** Flip-over speed between congestion and free flow. */
143     private Speed vc;
144 
145     /** Smoothing filter. */
146     private EGTF egtf;
147 
148     /** Data stream for speed. */
149     private DataStream<Speed> speedStream;
150 
151     /** Data stream for travel time. */
152     private DataStream<Duration> travelTimeStream;
153 
154     /** Data stream for travel distance. */
155     private DataStream<Length> travelDistanceStream;
156 
157     /** Quantity for travel time. */
158     private final Quantity<Duration, double[][]> travelTimeQuantity = new Quantity<>("travel time", Converter.SI);
159 
160     /** Quantity for travel distance. */
161     private final Quantity<Length, double[][]> travelDistanceQuantity = new Quantity<>("travel distance", Converter.SI);
162 
163     /** Data streams for any additional data. */
164     private Map<ContourDataType<?, ?>, DataStream<?>> additionalStreams = new LinkedHashMap<>();
165 
166     // *****************************
167     // *** CONTINUITY PROPERTIES ***
168     // *****************************
169 
170     /** Updater for update times. */
171     private final GraphUpdater<Time> graphUpdater;
172 
173     /** Whether any command since or during the last update asks for a complete redo. */
174     private boolean redo = true;
175 
176     /** Time up to which to determine data. This is a multiple of the update interval, which is now, or recent on a redo. */
177     private Time toTime;
178 
179     /** Number of items that are ready. To return NaN values if not ready, and for operations between consecutive updates. */
180     private int readyItems = -1;
181 
182     /** Selected space granularity, to be set and taken on the next update. */
183     private Double desiredSpaceGranularity = null;
184 
185     /** Selected time granularity, to be set and taken on the next update. */
186     private Double desiredTimeGranularity = null;
187 
188     /** Whether to smooth data. */
189     private boolean smooth = false;
190 
191     // ********************
192     // *** CONSTRUCTORS ***
193     // ********************
194 
195     /**
196      * Constructor using default granularities.
197      * @param sampler Sampler&lt;G&gt;; sampler
198      * @param path GraphPath&lt;KpiLaneDirection&gt;; path
199      */
200     public ContourDataSource(final Sampler<G> sampler, final GraphPath<KpiLaneDirection> path)
201     {
202         this(sampler, 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      * Constructor for non-default input.
209      * @param sampler Sampler&lt;G&gt;; sampler
210      * @param delay Duration; delay so critical future events have occurred, e.g. GTU's next move's to extend trajectories
211      * @param path GraphPath&lt;KpiLaneDirection&gt;; path
212      * @param spaceGranularity double[]; granularity options for space dimension
213      * @param initSpaceIndex int; initial selected space granularity
214      * @param timeGranularity double[]; granularity options for time dimension
215      * @param initTimeIndex int; initial selected time granularity
216      * @param start Time; start time
217      * @param initialEnd Time; initial end time of plots, will be expanded if simulation time exceeds it
218      */
219     @SuppressWarnings("parameternumber")
220     public ContourDataSource(final Sampler<G> sampler, 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.sampler = sampler;
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         // get length-weighted mean speed limit from path to determine cFree and Vc for smoothing
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         // setup updater to do the actual work in another thread
236         this.graphUpdater = new GraphUpdater<>("Contour Data Source worker", Thread.currentThread(), (t) -> update(t));
237     }
238 
239     // ************************************
240     // *** PLOT INTERFACING AND GETTERS ***
241     // ************************************
242 
243     /**
244      * Returns the sampler for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
245      * @return Sampler&lt;G&gt;; the sampler
246      */
247     public final Sampler<G> getSampler()
248     {
249         return this.sampler;
250     }
251 
252     /**
253      * Returns the update interval for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
254      * @return Duration; update interval
255      */
256     final Duration getUpdateInterval()
257     {
258         return this.updateInterval;
259     }
260 
261     /**
262      * Returns the delay for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
263      * @return Duration; delay
264      */
265     final Duration getDelay()
266     {
267         return this.delay;
268     }
269 
270     /**
271      * Returns the path for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
272      * @return GraphPath&lt;KpiLaneDirection&gt;; the path
273      */
274     final GraphPath<KpiLaneDirection> getPath()
275     {
276         return this.path;
277     }
278 
279     /**
280      * Register a contour plot to this data pool. The contour constructor will do this.
281      * @param contourPlot AbstractContourPlot&lt;?&gt;; contour plot
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      * Returns the bin count.
295      * @param dimension Dimension; space or time
296      * @return int; bin count
297      */
298     final int getBinCount(final Dimension dimension)
299     {
300         return dimension.getAxis(this).getBinCount();
301     }
302 
303     /**
304      * Returns the size of a bin. Usually this is equal to the granularity, except for the last which is likely smaller.
305      * @param dimension Dimension; space or time
306      * @param item int; item number (cell number in contour plot)
307      * @return double; the size of a bin
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      * Returns the value on the axis of an item.
318      * @param dimension Dimension; space or time
319      * @param item int; item number (cell number in contour plot)
320      * @return double; the value on the axis of this item
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      * Returns the axis bin number of the given value.
333      * @param dimension Dimension; space or time
334      * @param value double; value
335      * @return int; axis bin number of the given value
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      * Returns the available granularities that a linked plot may use.
348      * @param dimension Dimension; space or time
349      * @return double[]; available granularities that a linked plot may use
350      */
351     @SuppressWarnings("synthetic-access")
352     public final double[] getGranularities(final Dimension dimension)
353     {
354         return dimension.getAxis(this).granularities;
355     }
356 
357     /**
358      * Returns the selected granularity that a linked plot should use.
359      * @param dimension Dimension; space or time
360      * @return double; granularity that a linked plot should use
361      */
362     @SuppressWarnings("synthetic-access")
363     public final double getGranularity(final Dimension dimension)
364     {
365         return dimension.getAxis(this).granularity;
366     }
367 
368     /**
369      * Called by {@code AbstractContourPlot} to update the time. This will invalidate the plot triggering a redraw.
370      * @param updateTime Time; current time
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) // null at initialization
384         {
385             invalidate(updateTime);
386         }
387     }
388 
389     /**
390      * Sets the granularity of the plot. This will invalidate the plot triggering a redraw.
391      * @param dimension Dimension; space or time
392      * @param granularity double; granularity in space or time (SI unit)
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      * Sets bi-linear interpolation enabled or disabled. This will invalidate the plot triggering a redraw.
418      * @param interpolate boolean; whether to enable interpolation
419      */
420     public final void setInterpolate(final boolean interpolate)
421     {
422         if (this.timeAxis.interpolate != interpolate)
423         {
424             synchronized (this)
425             {
426                 this.timeAxis.setInterpolate(interpolate);
427                 this.spaceAxis.setInterpolate(interpolate);
428                 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
429                 {
430                     contourPlot.setInterpolation(interpolate);
431                 }
432                 invalidate(null);
433             }
434         }
435     }
436 
437     /**
438      * Sets the adaptive smoothing enabled or disabled. This will invalidate the plot triggering a redraw.
439      * @param smooth boolean; whether to smooth the plor
440      */
441     public final void setSmooth(final boolean smooth)
442     {
443         if (this.smooth != smooth)
444         {
445             synchronized (this)
446             {
447                 this.smooth = smooth;
448                 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
449                 {
450                     System.out.println("not notifying plot " + contourPlot);
451                     // TODO work out what to do with this: contourPlot.setSmoothing(smooth);
452                 }
453                 invalidate(null);
454             }
455         }
456     }
457 
458     // ************************
459     // *** UPDATING METHODS ***
460     // ************************
461 
462     /**
463      * Each method that changes a setting such that the plot is no longer valid, should call this method after the setting was
464      * changed. If time is updated (increased), it should be given as input in to this method. The given time <i>should</i> be
465      * {@code null} if the plot is not valid for any other reason. In this case a full redo is initiated.
466      * <p>
467      * Every method calling this method should be {@code synchronized}, at least for the part where the setting is changed and
468      * this method is called. This method will in all cases add an update request to the updater, working in another thread. It
469      * will invoke method {@code update()}. That method utilizes a synchronized block to obtain all synchronization sensitive
470      * data, before starting the actual work.
471      * @param t Time; time up to which to show data
472      */
473     private synchronized void invalidate(final Time t)
474     {
475         if (t != null)
476         {
477             this.toTime = t;
478         }
479         else
480         {
481             this.redo = true;
482         }
483         if (this.toTime != null) // null at initialization
484         {
485             // either a later time was set, or time was null and a redo is required (will be picked up through the redo field)
486             // note that we cannot set {@code null}, hence we set the current to time, which may or may not have just changed
487             this.graphUpdater.offer(this.toTime);
488         }
489     }
490 
491     /**
492      * Heart of the data pool. This method is invoked regularly by the "DataPool worker" thread, as scheduled in a queue through
493      * planned updates at an interval, or by user action changing the plot appearance. No two invocations can happen at the same
494      * time, as the "DataPool worker" thread executes this method before the next update request from the queue is considered.
495      * <p>
496      * This method regularly checks conditions that indicate the update should be interrupted as for example a setting has
497      * changed and appearance should change. Whenever a new invalidation causes {@code redo = true}, this method can stop as the
498      * full data needs to be recalculated. This can be set by any change of e.g. granularity or smoothing, during the update.
499      * <p>
500      * During the data recalculation, a later update time may also trigger this method to stop, while the next update will pick
501      * up where this update left off. During the smoothing this method doesn't stop for an increased update time, as that will
502      * leave a gap in the smoothed data. Note that smoothing either smoothes all data (when {@code redo = true}), or only the
503      * last part that falls within the kernel.
504      * @param t Time; time up to which to show data
505      */
506     @SuppressWarnings({"synthetic-access", "methodlength"})
507     private void update(final Time t)
508     {
509         Throw.when(this.plots.isEmpty(), IllegalStateException.class, "ContourDataSource is used, but not by a contour plot!");
510 
511         if (t.si < this.toTime.si)
512         {
513             // skip this update as new updates were commanded, while this update was in the queue, and a previous was running
514             return;
515         }
516 
517         /**
518          * This method is executed once at a time by the worker Thread. Many properties, such as the data, are maintained by
519          * this method. Other properties, which other methods can change, are read first in a synchronized block, while those
520          * methods are also synchronized.
521          */
522         boolean redo0;
523         boolean smooth0;
524         boolean interpolate0;
525         double timeGranularity;
526         double spaceGranularity;
527         double[] spaceTicks;
528         double[] timeTicks;
529         int fromSpaceIndex = 0;
530         int fromTimeIndex = 0;
531         int toTimeIndex;
532         double tFromEgtf = 0;
533         int nFromEgtf = 0;
534         synchronized (this)
535         {
536             // save local copies so commands given during this execution can change it for the next execution
537             redo0 = this.redo;
538             smooth0 = this.smooth;
539             interpolate0 = this.timeAxis.interpolate;
540             // timeTicks may be longer than the simulation time, so we use the time bin for the required time of data
541             if (this.desiredTimeGranularity != null)
542             {
543                 this.timeAxis.setGranularity(this.desiredTimeGranularity);
544                 this.desiredTimeGranularity = null;
545             }
546             if (this.desiredSpaceGranularity != null)
547             {
548                 this.spaceAxis.setGranularity(this.desiredSpaceGranularity);
549                 this.desiredSpaceGranularity = null;
550             }
551             timeGranularity = this.timeAxis.granularity;
552             spaceGranularity = this.spaceAxis.granularity;
553             spaceTicks = this.spaceAxis.getTicks();
554             timeTicks = this.timeAxis.getTicks();
555             if (!redo0)
556             {
557                 // remember where we started, readyItems will be updated but we need to know where we started during the update
558                 fromSpaceIndex = getSpaceBin(this.readyItems + 1);
559                 fromTimeIndex = getTimeBin(this.readyItems + 1);
560             }
561             toTimeIndex = ((int) (t.si / timeGranularity)) - (interpolate0 ? 0 : 1);
562             if (smooth0)
563             {
564                 // time of current bin - kernel size, get bin of that time, get time (middle) of that bin
565                 tFromEgtf = this.timeAxis.getBinValue(redo0 ? 0 : this.timeAxis.getValueBin(
566                         this.timeAxis.getBinValue(fromTimeIndex) - Math.max(TAU.si, timeGranularity / 2) * KERNEL_FACTOR));
567                 nFromEgtf = this.timeAxis.getValueBin(tFromEgtf);
568             }
569             // starting execution, so reset redo trigger which any next command may set to true if needed
570             this.redo = false;
571         }
572 
573         // reset upon a redo
574         if (redo0)
575         {
576             this.readyItems = -1;
577 
578             // init all data arrays
579             int nSpace = spaceTicks.length - 1;
580             int nTime = timeTicks.length - 1;
581             this.distance = new float[nSpace][nTime];
582             this.time = new float[nSpace][nTime];
583             for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
584             {
585                 this.additionalData.put(contourDataType, new float[nSpace][nTime]);
586             }
587 
588             // setup the smoothing filter
589             if (smooth0)
590             {
591                 // create the filter
592                 this.egtf = new EGTF(C_CONG.si, this.cFree.si, DELTA_V.si, this.vc.si);
593 
594                 // create data source and its data streams for speed, distance traveled, time traveled, and additional
595                 DataSource generic = this.egtf.getDataSource("generic");
596                 generic.addStream(TypedQuantity.SPEED, Speed.instantiateSI(1.0), Speed.instantiateSI(1.0));
597                 generic.addStreamSI(this.travelTimeQuantity, 1.0, 1.0);
598                 generic.addStreamSI(this.travelDistanceQuantity, 1.0, 1.0);
599                 this.speedStream = generic.getStream(TypedQuantity.SPEED);
600                 this.travelTimeStream = generic.getStream(this.travelTimeQuantity);
601                 this.travelDistanceStream = generic.getStream(this.travelDistanceQuantity);
602                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
603                 {
604                     this.additionalStreams.put(contourDataType, generic.addStreamSI(contourDataType.getQuantity(), 1.0, 1.0));
605                 }
606 
607                 // in principle we use sigma and tau, unless the data is so rough, we need more (granularity / 2).
608                 double tau2 = Math.max(TAU.si, timeGranularity / 2);
609                 double sigma2 = Math.max(SIGMA.si, spaceGranularity / 2);
610                 // for maximum space and time range, increase sigma and tau by KERNEL_FACTOR, beyond which both kernels diminish
611                 this.egtf.setGaussKernelSI(sigma2 * KERNEL_FACTOR, tau2 * KERNEL_FACTOR, sigma2, tau2);
612 
613                 // add listener to provide a filter status update and to possibly stop the filter when the plot is invalidated
614                 this.egtf.addListener(new EgtfListener()
615                 {
616                     /** {@inheritDoc} */
617                     @Override
618                     public void notifyProgress(final EgtfEvent event)
619                     {
620                         // check stop (explicit use of property, not locally stored value)
621                         if (ContourDataSource.this.redo)
622                         {
623                             // plots need to be redone
624                             event.interrupt(); // stop the EGTF
625                             setStatusLabel(" "); // reset status label so no "ASM at 12.6%" remains there
626                             return;
627                         }
628                         String status =
629                                 event.getProgress() >= 1.0 ? " " : String.format("ASM at %.2f%%", event.getProgress() * 100);
630                         setStatusLabel(status);
631                     }
632                 });
633             }
634         }
635 
636         // discard any data from smoothing if we are not smoothing
637         if (!smooth0)
638         {
639             // free for garbage collector to remove the data
640             this.egtf = null;
641             this.speedStream = null;
642             this.travelTimeStream = null;
643             this.travelDistanceStream = null;
644             this.additionalStreams.clear();
645         }
646 
647         // ensure capacity
648         for (int i = 0; i < this.distance.length; i++)
649         {
650             this.distance[i] = GraphUtil.ensureCapacity(this.distance[i], toTimeIndex + 1);
651             this.time[i] = GraphUtil.ensureCapacity(this.time[i], toTimeIndex + 1);
652             for (float[][] additional : this.additionalData.values())
653             {
654                 additional[i] = GraphUtil.ensureCapacity(additional[i], toTimeIndex + 1);
655             }
656         }
657 
658         // loop cells to update data
659         for (int j = fromTimeIndex; j <= toTimeIndex; j++)
660         {
661             Time tFrom = Time.instantiateSI(timeTicks[j]);
662             Time tTo = Time.instantiateSI(timeTicks[j + 1]);
663 
664             // we never filter time, time always spans the entire simulation, it will contain tFrom till tTo
665 
666             for (int i = fromSpaceIndex; i < spaceTicks.length - 1; i++)
667             {
668                 // when interpolating, set the first row and column to NaN so colors representing 0 do not mess up the edges
669                 if ((j == 0 || i == 0) && interpolate0)
670                 {
671                     this.distance[i][j] = Float.NaN;
672                     this.time[i][j] = Float.NaN;
673                     this.readyItems++;
674                     continue;
675                 }
676 
677                 // only first loop with offset, later in time, none of the space was done in the previous update
678                 fromSpaceIndex = 0;
679                 Length xFrom = Length.instantiateSI(spaceTicks[i]);
680                 Length xTo = Length.instantiateSI(Math.min(spaceTicks[i + 1], this.path.getTotalLength().si));
681 
682                 // init cell data
683                 double totalDistance = 0.0;
684                 double totalTime = 0.0;
685                 Map<ContourDataType<?, ?>, Object> additionalIntermediate = new LinkedHashMap<>();
686                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
687                 {
688                     additionalIntermediate.put(contourDataType, contourDataType.identity());
689                 }
690 
691                 // aggregate series in cell
692                 for (int series = 0; series < this.path.getNumberOfSeries(); series++)
693                 {
694                     // obtain trajectories
695                     List<TrajectoryGroup<?>> trajectories = new ArrayList<>();
696                     for (Section<KpiLaneDirection> section : getPath().getSections())
697                     {
698                         trajectories.add(this.sampler.getTrajectoryGroup(section.getSource(series)));
699                     }
700 
701                     // filter groups (lanes) that overlap with section i
702                     List<TrajectoryGroup<?>> included = new ArrayList<>();
703                     List<Length> xStart = new ArrayList<>();
704                     List<Length> xEnd = new ArrayList<>();
705                     for (int k = 0; k < trajectories.size(); k++)
706                     {
707                         TrajectoryGroup<?> trajectoryGroup = trajectories.get(k);
708                         KpiLaneDirection lane = trajectoryGroup.getLaneDirection();
709                         Length startDistance = this.path.getStartDistance(this.path.get(k));
710                         if (startDistance.si + this.path.get(k).getLength().si > spaceTicks[i]
711                                 && startDistance.si < spaceTicks[i + 1])
712                         {
713                             included.add(trajectoryGroup);
714                             double scale = this.path.get(k).getLength().si / lane.getLaneData().getLength().si;
715                             // divide by scale, so we go from base length to section length
716                             xStart.add(Length.max(xFrom.minus(startDistance).divide(scale), Length.ZERO));
717                             xEnd.add(Length.min(xTo.minus(startDistance).divide(scale),
718                                     trajectoryGroup.getLaneDirection().getLaneData().getLength()));
719                         }
720                     }
721 
722                     // accumulate distance and time of trajectories
723                     for (int k = 0; k < included.size(); k++)
724                     {
725                         TrajectoryGroup<?> trajectoryGroup = included.get(k);
726                         for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
727                         {
728                             // for optimal operations, we first do quick-reject based on time, as by far most trajectories
729                             // during the entire time span of simulation will not apply to a particular cell in space-time
730                             if (GraphUtil.considerTrajectory(trajectory, tFrom, tTo))
731                             {
732                                 // again for optimal operations, we use a space-time view only (we don't need more)
733                                 SpaceTimeView spaceTimeView;
734                                 try
735                                 {
736                                     spaceTimeView = trajectory.getSpaceTimeView(xStart.get(k), xEnd.get(k), tFrom, tTo);
737                                 }
738                                 catch (IllegalArgumentException exception)
739                                 {
740                                     CategoryLogger.always().debug(exception,
741                                             "Unable to generate space-time view from x = {} to {} and t = {} to {}.",
742                                             xStart.get(k), xEnd.get(k), tFrom, tTo);
743                                     continue;
744                                 }
745                                 totalDistance += spaceTimeView.getDistance().si;
746                                 totalTime += spaceTimeView.getTime().si;
747                             }
748                         }
749                     }
750 
751                     // loop and set any additional data
752                     for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
753                     {
754                         addAdditional(additionalIntermediate, contourDataType, included, xStart, xEnd, tFrom, tTo);
755                     }
756 
757                 }
758 
759                 // scale values to the full size of a cell on a single lane, so the EGTF is interpolating comparable values
760                 double norm = spaceGranularity / (xTo.si - xFrom.si) / this.path.getNumberOfSeries();
761                 totalDistance *= norm;
762                 totalTime *= norm;
763                 this.distance[i][j] = (float) totalDistance;
764                 this.time[i][j] = (float) totalTime;
765                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
766                 {
767                     this.additionalData.get(contourDataType)[i][j] =
768                             finalizeAdditional(additionalIntermediate, contourDataType);
769                 }
770 
771                 // add data to EGTF (yes it's a copy, but our local data will be overwritten with smoothed data later)
772                 if (smooth0)
773                 {
774                     // center of cell
775                     double xDat = (xFrom.si + xTo.si) / 2.0;
776                     double tDat = (tFrom.si + tTo.si) / 2.0;
777                     // speed data is implicit as totalDistance/totalTime, but the EGTF needs it explicitly
778                     this.egtf.addPointDataSI(this.speedStream, xDat, tDat, totalDistance / totalTime);
779                     this.egtf.addPointDataSI(this.travelDistanceStream, xDat, tDat, totalDistance);
780                     this.egtf.addPointDataSI(this.travelTimeStream, xDat, tDat, totalTime);
781                     for (ContourDataType<?, ?> contourDataType : this.additionalStreams.keySet())
782                     {
783                         ContourDataSource.this.egtf.addPointDataSI(
784                                 ContourDataSource.this.additionalStreams.get(contourDataType), xDat, tDat,
785                                 this.additionalData.get(contourDataType)[i][j]);
786                     }
787                 }
788 
789                 // check stop (explicit use of properties, not locally stored values)
790                 if (this.redo)
791                 {
792                     // plots need to be redone, or time has increased meaning that a next call may continue further just as well
793                     return;
794                 }
795 
796                 // one more item is ready for plotting
797                 this.readyItems++;
798             }
799 
800             // notify changes for every time slice
801             this.plots.forEach((plot) -> plot.notifyPlotChange());
802         }
803 
804         // smooth all data that is as old as our kernel includes (or all data on a redo)
805         if (smooth0)
806         {
807             Set<Quantity<?, ?>> quantities = new LinkedHashSet<>();
808             quantities.add(this.travelDistanceQuantity);
809             quantities.add(this.travelTimeQuantity);
810             for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
811             {
812                 quantities.add(contourDataType.getQuantity());
813             }
814             Filter filter = this.egtf.filterFastSI(spaceTicks[0] + 0.5 * spaceGranularity, spaceGranularity,
815                     spaceTicks[0] + (-1.5 + spaceTicks.length) * spaceGranularity, tFromEgtf, timeGranularity, t.si,
816                     quantities.toArray(new Quantity<?, ?>[quantities.size()]));
817             if (filter != null) // null if interrupted
818             {
819                 overwriteSmoothed(this.distance, nFromEgtf, filter.getSI(this.travelDistanceQuantity));
820                 overwriteSmoothed(this.time, nFromEgtf, filter.getSI(this.travelTimeQuantity));
821                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
822                 {
823                     overwriteSmoothed(this.additionalData.get(contourDataType), nFromEgtf,
824                             filter.getSI(contourDataType.getQuantity()));
825                 }
826                 this.plots.forEach((plot) -> plot.notifyPlotChange());
827             }
828         }
829     }
830 
831     /**
832      * Add additional data to stored intermediate result.
833      * @param additionalIntermediate Map&lt;ContourDataType&lt;?, ?&gt;, Object&gt;; intermediate storage map
834      * @param contourDataType ContourDataType&lt;?, ?&gt;; additional data type
835      * @param included List&lt;TrajectoryGroup&lt;?&gt;&gt;; trajectories
836      * @param xStart List&lt;Length&gt;; start distance per trajectory group
837      * @param xEnd List&lt;Length&gt;; end distance per trajectory group
838      * @param tFrom Time; start time
839      * @param tTo Time; end time
840      * @param <I> intermediate data type
841      */
842     @SuppressWarnings("unchecked")
843     private <I> void addAdditional(final Map<ContourDataType<?, ?>, Object> additionalIntermediate,
844             final ContourDataType<?, ?> contourDataType, final List<TrajectoryGroup<?>> included, final List<Length> xStart,
845             final List<Length> xEnd, final Time tFrom, final Time tTo)
846     {
847         additionalIntermediate.put(contourDataType, ((ContourDataType<?, I>) contourDataType)
848                 .processSeries((I) additionalIntermediate.get(contourDataType), included, xStart, xEnd, tFrom, tTo));
849     }
850 
851     /**
852      * Stores a finalized result for additional data.
853      * @param additionalIntermediate Map&lt;ContourDataType&lt;?, ?&gt;, Object&gt;; intermediate storage map
854      * @param contourDataType ContourDataType&lt;?, ?&gt;; additional data type
855      * @return float; finalized results for a cell
856      * @param <I> intermediate data type
857      */
858     @SuppressWarnings("unchecked")
859     private <I> float finalizeAdditional(final Map<ContourDataType<?, ?>, Object> additionalIntermediate,
860             final ContourDataType<?, ?> contourDataType)
861     {
862         return ((ContourDataType<?, I>) contourDataType).finalize((I) additionalIntermediate.get(contourDataType)).floatValue();
863     }
864 
865     /**
866      * Helper method to fill smoothed data in to raw data.
867      * @param raw float[][]; the raw unsmoothed data
868      * @param rawCol int; column from which onward to fill smoothed data in to the raw data which is used for plotting
869      * @param smoothed double[][]; smoothed data returned by {@code EGTF}
870      */
871     private void overwriteSmoothed(final float[][] raw, final int rawCol, final double[][] smoothed)
872     {
873         for (int i = 0; i < raw.length; i++)
874         {
875             // can't use System.arraycopy due to float vs double
876             for (int j = 0; j < smoothed[i].length; j++)
877             {
878                 raw[i][j + rawCol] = (float) smoothed[i][j];
879             }
880         }
881     }
882 
883     /**
884      * Helper method used by an {@code EgtfListener} to present the filter progress.
885      * @param status String; progress report
886      */
887     private void setStatusLabel(final String status)
888     {
889         for (AbstractContourPlot<?> plot : ContourDataSource.this.plots)
890         {
891             // TODO what shall we do this this? plot.setStatusLabel(status);
892         }
893     }
894 
895     // ******************************
896     // *** DATA RETRIEVAL METHODS ***
897     // ******************************
898 
899     /**
900      * Returns the speed of the cell pertaining to plot item.
901      * @param item int; plot item
902      * @return double; speed of the cell, calculated as 'total distance' / 'total space'.
903      */
904     public double getSpeed(final int item)
905     {
906         if (item > this.readyItems)
907         {
908             return Double.NaN;
909         }
910         return getTotalDistance(item) / getTotalTime(item);
911     }
912 
913     /**
914      * Returns the total distance traveled in the cell pertaining to plot item.
915      * @param item int; plot item
916      * @return double; total distance traveled in the cell
917      */
918     public double getTotalDistance(final int item)
919     {
920         if (item > this.readyItems)
921         {
922             return Double.NaN;
923         }
924         return this.distance[getSpaceBin(item)][getTimeBin(item)];
925     }
926 
927     /**
928      * Returns the total time traveled in the cell pertaining to plot item.
929      * @param item int; plot item
930      * @return double; total time traveled in the cell
931      */
932     public double getTotalTime(final int item)
933     {
934         if (item > this.readyItems)
935         {
936             return Double.NaN;
937         }
938         return this.time[getSpaceBin(item)][getTimeBin(item)];
939     }
940 
941     /**
942      * Returns data of the given {@code ContourDataType} for a specific item.
943      * @param item int; plot item
944      * @param contourDataType ContourDataType&lt;?, ?&gt;; contour data type
945      * @return data of the given {@code ContourDataType} for a specific item
946      */
947     public double get(final int item, final ContourDataType<?, ?> contourDataType)
948     {
949         if (item > this.readyItems)
950         {
951             return Double.NaN;
952         }
953         return this.additionalData.get(contourDataType)[getSpaceBin(item)][getTimeBin(item)];
954     }
955 
956     /**
957      * Returns the time bin number of the item.
958      * @param item int; item number
959      * @return int; time bin number of the item
960      */
961     private int getTimeBin(final int item)
962     {
963         return item / this.spaceAxis.getBinCount();
964     }
965 
966     /**
967      * Returns the space bin number of the item.
968      * @param item int; item number
969      * @return int; space bin number of the item
970      */
971     private int getSpaceBin(final int item)
972     {
973         return item % this.spaceAxis.getBinCount();
974     }
975 
976     // **********************
977     // *** HELPER CLASSES ***
978     // **********************
979 
980     /**
981      * Enum to refer to either the distance or time axis.
982      * <p>
983      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
984      * <br>
985      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
986      * <p>
987      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 10 okt. 2018 <br>
988      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
989      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
990      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
991      */
992     public enum Dimension
993     {
994         /** Distance axis. */
995         DISTANCE
996         {
997             /** {@inheritDoc} */
998             @Override
999             protected Axis getAxis(final ContourDataSource<?> dataPool)
1000             {
1001                 return dataPool.spaceAxis;
1002             }
1003         },
1004 
1005         /** Time axis. */
1006         TIME
1007         {
1008             /** {@inheritDoc} */
1009             @Override
1010             protected Axis getAxis(final ContourDataSource<?> dataPool)
1011             {
1012                 return dataPool.timeAxis;
1013             }
1014         };
1015 
1016         /**
1017          * Returns the {@code Axis} object.
1018          * @param dataPool ContourDataSource&lt;?&gt;; data pool
1019          * @return Axis; axis
1020          */
1021         protected abstract Axis getAxis(ContourDataSource<?> dataPool);
1022     }
1023 
1024     /**
1025      * Class to store and determine axis information such as granularity, ticks, and range.
1026      * <p>
1027      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1028      * <br>
1029      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
1030      * <p>
1031      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 10 okt. 2018 <br>
1032      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
1033      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1034      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
1035      */
1036     static class Axis
1037     {
1038         /** Minimum value. */
1039         private final double minValue;
1040 
1041         /** Maximum value. */
1042         private double maxValue;
1043 
1044         /** Selected granularity. */
1045         private double granularity;
1046 
1047         /** Possible granularities. */
1048         private final double[] granularities;
1049 
1050         /** Whether the data pool is set to interpolate. */
1051         private boolean interpolate = true;
1052 
1053         /** Tick values. */
1054         private double[] ticks;
1055 
1056         /**
1057          * Constructor.
1058          * @param minValue double; minimum value
1059          * @param maxValue double; maximum value
1060          * @param granularity double; initial granularity
1061          * @param granularities double[]; possible granularities
1062          */
1063         Axis(final double minValue, final double maxValue, final double granularity, final double[] granularities)
1064         {
1065             this.minValue = minValue;
1066             this.maxValue = maxValue;
1067             this.granularity = granularity;
1068             this.granularities = granularities;
1069         }
1070 
1071         /**
1072          * Sets the maximum value.
1073          * @param maxValue double; maximum value
1074          */
1075         void setMaxValue(final double maxValue)
1076         {
1077             if (this.maxValue != maxValue)
1078             {
1079                 this.maxValue = maxValue;
1080                 this.ticks = null;
1081             }
1082         }
1083 
1084         /**
1085          * Sets the granularity.
1086          * @param granularity double; granularity
1087          */
1088         void setGranularity(final double granularity)
1089         {
1090             if (this.granularity != granularity)
1091             {
1092                 this.granularity = granularity;
1093                 this.ticks = null;
1094             }
1095         }
1096 
1097         /**
1098          * Returns the ticks, which are calculated if needed.
1099          * @return double[]; ticks
1100          */
1101         double[] getTicks()
1102         {
1103             if (this.ticks == null)
1104             {
1105                 int n = getBinCount() + 1;
1106                 this.ticks = new double[n];
1107                 int di = this.interpolate ? 1 : 0;
1108                 for (int i = 0; i < n; i++)
1109                 {
1110                     if (i == n - 1)
1111                     {
1112                         this.ticks[i] = Math.min((i - di) * this.granularity, this.maxValue);
1113                     }
1114                     else
1115                     {
1116                         this.ticks[i] = (i - di) * this.granularity;
1117                     }
1118                 }
1119             }
1120             return this.ticks;
1121         }
1122 
1123         /**
1124          * Calculates the number of bins.
1125          * @return int; number of bins
1126          */
1127         int getBinCount()
1128         {
1129             return (int) Math.ceil((this.maxValue - this.minValue) / this.granularity) + (this.interpolate ? 1 : 0);
1130         }
1131 
1132         /**
1133          * Calculates the center value of a bin.
1134          * @param bin int; bin number
1135          * @return double; center value of the bin
1136          */
1137         double getBinValue(final int bin)
1138         {
1139             return this.minValue + (0.5 + bin - (this.interpolate ? 1 : 0)) * this.granularity;
1140         }
1141 
1142         /**
1143          * Looks up the bin number of the value.
1144          * @param value double; value
1145          * @return int; bin number
1146          */
1147         int getValueBin(final double value)
1148         {
1149             getTicks();
1150             if (value > this.ticks[this.ticks.length - 1])
1151             {
1152                 return this.ticks.length - 1;
1153             }
1154             int i = 0;
1155             while (i < this.ticks.length - 1 && this.ticks[i + 1] < value + 1e-9)
1156             {
1157                 i++;
1158             }
1159             return i;
1160         }
1161 
1162         /**
1163          * Sets interpolation, important is it required the data to have an additional row or column.
1164          * @param interpolate boolean; interpolation
1165          */
1166         void setInterpolate(final boolean interpolate)
1167         {
1168             if (this.interpolate != interpolate)
1169             {
1170                 this.interpolate = interpolate;
1171                 this.ticks = null;
1172             }
1173         }
1174 
1175         /**
1176          * Retrieve the interpolate flag.
1177          * @return boolean; true if interpolation is on; false if interpolation is off
1178          */
1179         public boolean isInterpolate()
1180         {
1181             return interpolate;
1182         }
1183 
1184         /** {@inheritDoc} */
1185         @Override
1186         public String toString()
1187         {
1188             return "Axis [minValue=" + this.minValue + ", maxValue=" + this.maxValue + ", granularity=" + this.granularity
1189                     + ", granularities=" + Arrays.toString(this.granularities) + ", interpolate=" + this.interpolate
1190                     + ", ticks=" + Arrays.toString(this.ticks) + "]";
1191         }
1192 
1193     }
1194 
1195     /**
1196      * Interface for data types of which a contour plot can be made. Using this class, the data pool can determine and store
1197      * cell values for a variable set of additional data types (besides total distance, total time and speed).
1198      * <p>
1199      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1200      * <br>
1201      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
1202      * <p>
1203      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 10 okt. 2018 <br>
1204      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
1205      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
1206      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
1207      * @param <Z> value type
1208      * @param <I> intermediate data type
1209      */
1210     public interface ContourDataType<Z extends Number, I>
1211     {
1212         /**
1213          * Returns the initial value for intermediate result.
1214          * @return I, initial intermediate value
1215          */
1216         I identity();
1217 
1218         /**
1219          * Calculate value from provided trajectories that apply to a single grid cell on a single series (lane).
1220          * @param intermediate I; intermediate value of previous series, starts as the identity
1221          * @param trajectories List&lt;TrajectoryGroup&lt;?&gt;&gt;; trajectories, all groups overlap the requested space-time
1222          * @param xFrom List&lt;Length&gt;; start location of cell on the section
1223          * @param xTo List&lt;Length&gt;; end location of cell on the section.
1224          * @param tFrom Time; start time of cell
1225          * @param tTo Time; end time of cell
1226          * @return I; intermediate value
1227          */
1228         I processSeries(I intermediate, List<TrajectoryGroup<?>> trajectories, List<Length> xFrom, List<Length> xTo, Time tFrom,
1229                 Time tTo);
1230 
1231         /**
1232          * Returns the final value of the intermediate result after all lanes.
1233          * @param intermediate I; intermediate result after all lanes
1234          * @return Z; final value
1235          */
1236         Z finalize(I intermediate);
1237 
1238         /**
1239          * Returns the quantity that is being plotted on the z-axis for the EGTF filter.
1240          * @return Quantity&lt;Z, ?&gt;; quantity that is being plotted on the z-axis for the EGTF filter
1241          */
1242         Quantity<Z, ?> getQuantity();
1243     }
1244 
1245     /** {@inheritDoc} */
1246     @Override
1247     public String toString()
1248     {
1249         return "ContourDataSource [sampler=" + this.sampler + ", updateInterval=" + this.updateInterval + ", delay="
1250                 + this.delay + ", path=" + this.path + ", spaceAxis=" + this.spaceAxis + ", timeAxis=" + this.timeAxis
1251                 + ", plots=" + this.plots + ", distance=" + Arrays.toString(this.distance) + ", time="
1252                 + Arrays.toString(this.time) + ", additionalData=" + this.additionalData + ", smooth=" + this.smooth
1253                 + ", cFree=" + this.cFree + ", vc=" + this.vc + ", egtf=" + this.egtf + ", speedStream=" + this.speedStream
1254                 + ", travelTimeStream=" + this.travelTimeStream + ", travelDistanceStream=" + this.travelDistanceStream
1255                 + ", travelTimeQuantity=" + this.travelTimeQuantity + ", travelDistanceQuantity=" + this.travelDistanceQuantity
1256                 + ", additionalStreams=" + this.additionalStreams + ", graphUpdater=" + this.graphUpdater + ", redo="
1257                 + this.redo + ", toTime=" + this.toTime + ", readyItems=" + this.readyItems + ", desiredSpaceGranularity="
1258                 + this.desiredSpaceGranularity + ", desiredTimeGranularity=" + this.desiredTimeGranularity + "]";
1259     }
1260 
1261 }