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