ContourDataSource.java

  1. package org.opentrafficsim.draw.graphs;

  2. import java.util.ArrayList;
  3. import java.util.Arrays;
  4. import java.util.LinkedHashMap;
  5. import java.util.LinkedHashSet;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Set;

  9. import org.djunits.unit.SpeedUnit;
  10. import org.djunits.value.vdouble.scalar.Duration;
  11. import org.djunits.value.vdouble.scalar.Length;
  12. import org.djunits.value.vdouble.scalar.Speed;
  13. import org.djunits.value.vdouble.scalar.Time;
  14. import org.djutils.exceptions.Throw;
  15. import org.djutils.logger.CategoryLogger;
  16. import org.opentrafficsim.core.egtf.Converter;
  17. import org.opentrafficsim.core.egtf.DataSource;
  18. import org.opentrafficsim.core.egtf.DataStream;
  19. import org.opentrafficsim.core.egtf.EGTF;
  20. import org.opentrafficsim.core.egtf.EgtfEvent;
  21. import org.opentrafficsim.core.egtf.EgtfListener;
  22. import org.opentrafficsim.core.egtf.Filter;
  23. import org.opentrafficsim.core.egtf.Quantity;
  24. import org.opentrafficsim.core.egtf.typed.TypedQuantity;
  25. import org.opentrafficsim.draw.graphs.GraphPath.Section;
  26. import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
  27. import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
  28. import org.opentrafficsim.kpi.sampling.SamplerData;
  29. import org.opentrafficsim.kpi.sampling.Trajectory;
  30. import org.opentrafficsim.kpi.sampling.Trajectory.SpaceTimeView;
  31. import org.opentrafficsim.kpi.sampling.TrajectoryGroup;

  32. /**
  33.  * Class that contains data for contour plots. One data source can be shared between contour plots, in which case the
  34.  * granularity, path, sampler, update interval, and whether the data is smoothed (EGTF) are equal between the plots.
  35.  * <p>
  36.  * By default the source contains traveled time and traveled distance per cell.
  37.  * <p>
  38.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  39.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  40.  * <p>
  41.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version 5 okt. 2018 <br>
  42.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  43.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  44.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  45.  * @param <G> gtu type data
  46.  */
  47. public class ContourDataSource<G extends GtuDataInterface>
  48. {

  49.     // *************************
  50.     // *** GLOBAL PROPERTIES ***
  51.     // *************************

  52.     /** Space granularity values. */
  53.     protected static final double[] DEFAULT_SPACE_GRANULARITIES = { 10, 20, 50, 100, 200, 500, 1000 };

  54.     /** Index of the initial space granularity. */
  55.     protected static final int DEFAULT_SPACE_GRANULARITY_INDEX = 3;

  56.     /** Time granularity values. */
  57.     protected static final double[] DEFAULT_TIME_GRANULARITIES = { 1, 2, 5, 10, 20, 30, 60, 120, 300, 600 };

  58.     /** Index of the initial time granularity. */
  59.     protected static final int DEFAULT_TIME_GRANULARITY_INDEX = 3;

  60.     /** Initial lower bound for the time scale. */
  61.     protected static final Time DEFAULT_LOWER_TIME_BOUND = Time.ZERO;

  62.     /**
  63.      * Total kernel size relative to sigma and tau. This factor is determined through -log(1 - p) with p ~= 99%. This means that
  64.      * the cumulative exponential distribution has 99% at 5 times sigma or tau. Note that due to a coordinate change in the
  65.      * Adaptive Smoothing Method, the actual cumulative distribution is slightly different. Hence, this is just a heuristic.
  66.      */
  67.     private static final int KERNEL_FACTOR = 5;

  68.     /** Spatial kernel size. Larger value may be used when using a large granularity. */
  69.     private static final Length SIGMA = Length.instantiateSI(300);

  70.     /** Temporal kernel size. Larger value may be used when using a large granularity. */
  71.     private static final Duration TAU = Duration.instantiateSI(30);

  72.     /** Maximum free flow propagation speed. */
  73.     private static final Speed MAX_C_FREE = new Speed(80.0, SpeedUnit.KM_PER_HOUR);

  74.     /** Factor on speed limit to determine vc, the flip over speed between congestion and free flow. */
  75.     private static final double VC_FACRTOR = 0.8;

  76.     /** Congestion propagation speed. */
  77.     private static final Speed C_CONG = new Speed(-18.0, SpeedUnit.KM_PER_HOUR);

  78.     /** Delta v, speed transition region around threshold. */
  79.     private static final Speed DELTA_V = new Speed(10.0, SpeedUnit.KM_PER_HOUR);

  80.     // *****************************
  81.     // *** CONTEXTUAL PROPERTIES ***
  82.     // *****************************

  83.     /** Sampler data. */
  84.     private final SamplerData<G> samplerData;

  85.     /** Update interval. */
  86.     private final Duration updateInterval;

  87.     /** Delay so critical future events have occurred, e.g. GTU's next move's to extend trajectories. */
  88.     private final Duration delay;

  89.     /** Path. */
  90.     private final GraphPath<KpiLaneDirection> path;

  91.     /** Space axis. */
  92.     final Axis spaceAxis;

  93.     /** Time axis. */
  94.     final Axis timeAxis;

  95.     /** Registered plots. */
  96.     private Set<AbstractContourPlot<?>> plots = new LinkedHashSet<>();

  97.     // *****************
  98.     // *** PLOT DATA ***
  99.     // *****************

  100.     /** Total distance traveled per cell. */
  101.     private float[][] distance;

  102.     /** Total time traveled per cell. */
  103.     private float[][] time;

  104.     /** Data of other types. */
  105.     private final Map<ContourDataType<?, ?>, float[][]> additionalData = new LinkedHashMap<>();

  106.     // ****************************
  107.     // *** SMOOTHING PROPERTIES ***
  108.     // ****************************

  109.     /** Free flow propagation speed. */
  110.     private Speed cFree;

  111.     /** Flip-over speed between congestion and free flow. */
  112.     private Speed vc;

  113.     /** Smoothing filter. */
  114.     private EGTF egtf;

  115.     /** Data stream for speed. */
  116.     private DataStream<Speed> speedStream;

  117.     /** Data stream for travel time. */
  118.     private DataStream<Duration> travelTimeStream;

  119.     /** Data stream for travel distance. */
  120.     private DataStream<Length> travelDistanceStream;

  121.     /** Quantity for travel time. */
  122.     private final Quantity<Duration, double[][]> travelTimeQuantity = new Quantity<>("travel time", Converter.SI);

  123.     /** Quantity for travel distance. */
  124.     private final Quantity<Length, double[][]> travelDistanceQuantity = new Quantity<>("travel distance", Converter.SI);

  125.     /** Data streams for any additional data. */
  126.     private Map<ContourDataType<?, ?>, DataStream<?>> additionalStreams = new LinkedHashMap<>();

  127.     // *****************************
  128.     // *** CONTINUITY PROPERTIES ***
  129.     // *****************************

  130.     /** Updater for update times. */
  131.     private final GraphUpdater<Time> graphUpdater;

  132.     /** Whether any command since or during the last update asks for a complete redo. */
  133.     private boolean redo = true;

  134.     /** Time up to which to determine data. This is a multiple of the update interval, which is now, or recent on a redo. */
  135.     private Time toTime;

  136.     /** Number of items that are ready. To return NaN values if not ready, and for operations between consecutive updates. */
  137.     private int readyItems = -1;

  138.     /** Selected space granularity, to be set and taken on the next update. */
  139.     private Double desiredSpaceGranularity = null;

  140.     /** Selected time granularity, to be set and taken on the next update. */
  141.     private Double desiredTimeGranularity = null;

  142.     /** Whether to smooth data. */
  143.     private boolean smooth = false;

  144.     // ********************
  145.     // *** CONSTRUCTORS ***
  146.     // ********************

  147.     /**
  148.      * Constructor using default granularities.
  149.      * @param samplerData SamplerData&lt;G&gt;; sampler data
  150.      * @param path GraphPath&lt;KpiLaneDirection&gt;; path
  151.      */
  152.     public ContourDataSource(final SamplerData<G> samplerData, final GraphPath<KpiLaneDirection> path)
  153.     {
  154.         this(samplerData, Duration.instantiateSI(1.0), path, DEFAULT_SPACE_GRANULARITIES, DEFAULT_SPACE_GRANULARITY_INDEX,
  155.                 DEFAULT_TIME_GRANULARITIES, DEFAULT_TIME_GRANULARITY_INDEX, DEFAULT_LOWER_TIME_BOUND,
  156.                 AbstractPlot.DEFAULT_INITIAL_UPPER_TIME_BOUND);
  157.     }

  158.     /**
  159.      * Constructor for non-default input.
  160.      * @param samplerData SamplerData&lt;G&gt;; sampler data
  161.      * @param delay Duration; delay so critical future events have occurred, e.g. GTU's next move's to extend trajectories
  162.      * @param path GraphPath&lt;KpiLaneDirection&gt;; path
  163.      * @param spaceGranularity double[]; granularity options for space dimension
  164.      * @param initSpaceIndex int; initial selected space granularity
  165.      * @param timeGranularity double[]; granularity options for time dimension
  166.      * @param initTimeIndex int; initial selected time granularity
  167.      * @param start Time; start time
  168.      * @param initialEnd Time; initial end time of plots, will be expanded if simulation time exceeds it
  169.      */
  170.     @SuppressWarnings("parameternumber")
  171.     public ContourDataSource(final SamplerData<G> samplerData, final Duration delay, final GraphPath<KpiLaneDirection> path,
  172.             final double[] spaceGranularity, final int initSpaceIndex, final double[] timeGranularity, final int initTimeIndex,
  173.             final Time start, final Time initialEnd)
  174.     {
  175.         this.samplerData = samplerData;
  176.         this.updateInterval = Duration.instantiateSI(timeGranularity[initTimeIndex]);
  177.         this.delay = delay;
  178.         this.path = path;
  179.         this.spaceAxis = new Axis(0.0, path.getTotalLength().si, spaceGranularity[initSpaceIndex], spaceGranularity);
  180.         this.timeAxis = new Axis(start.si, initialEnd.si, timeGranularity[initTimeIndex], timeGranularity);

  181.         // get length-weighted mean speed limit from path to determine cFree and Vc for smoothing
  182.         this.cFree = Speed.min(path.getSpeedLimit(), MAX_C_FREE);
  183.         this.vc = Speed.min(path.getSpeedLimit().times(VC_FACRTOR), MAX_C_FREE);

  184.         // setup updater to do the actual work in another thread
  185.         this.graphUpdater = new GraphUpdater<>("Contour Data Source worker", Thread.currentThread(), (t) -> update(t));
  186.     }

  187.     // ************************************
  188.     // *** PLOT INTERFACING AND GETTERS ***
  189.     // ************************************

  190.     /**
  191.      * Returns the sampler data for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
  192.      * @return SamplerData&lt;G&gt;; the sampler
  193.      */
  194.     public final SamplerData<G> getSamplerData()
  195.     {
  196.         return this.samplerData;
  197.     }

  198.     /**
  199.      * Returns the update interval for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
  200.      * @return Duration; update interval
  201.      */
  202.     final Duration getUpdateInterval()
  203.     {
  204.         return this.updateInterval;
  205.     }

  206.     /**
  207.      * Returns the delay for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
  208.      * @return Duration; delay
  209.      */
  210.     final Duration getDelay()
  211.     {
  212.         return this.delay;
  213.     }

  214.     /**
  215.      * Returns the path for an {@code AbstractContourPlot} using this {@code ContourDataSource}.
  216.      * @return GraphPath&lt;KpiLaneDirection&gt;; the path
  217.      */
  218.     final GraphPath<KpiLaneDirection> getPath()
  219.     {
  220.         return this.path;
  221.     }

  222.     /**
  223.      * Register a contour plot to this data pool. The contour constructor will do this.
  224.      * @param contourPlot AbstractContourPlot&lt;?&gt;; contour plot
  225.      */
  226.     final void registerContourPlot(final AbstractContourPlot<?> contourPlot)
  227.     {
  228.         ContourDataType<?, ?> contourDataType = contourPlot.getContourDataType();
  229.         if (contourDataType != null)
  230.         {
  231.             this.additionalData.put(contourDataType, null);
  232.         }
  233.         this.plots.add(contourPlot);
  234.     }

  235.     /**
  236.      * Returns the bin count.
  237.      * @param dimension Dimension; space or time
  238.      * @return int; bin count
  239.      */
  240.     final int getBinCount(final Dimension dimension)
  241.     {
  242.         return dimension.getAxis(this).getBinCount();
  243.     }

  244.     /**
  245.      * Returns the size of a bin. Usually this is equal to the granularity, except for the last which is likely smaller.
  246.      * @param dimension Dimension; space or time
  247.      * @param item int; item number (cell number in contour plot)
  248.      * @return double; the size of a bin
  249.      */
  250.     final synchronized double getBinSize(final Dimension dimension, final int item)
  251.     {
  252.         int n = dimension.equals(Dimension.DISTANCE) ? getSpaceBin(item) : getTimeBin(item);
  253.         double[] ticks = dimension.getAxis(this).getTicks();
  254.         return ticks[n + 1] - ticks[n];
  255.     }

  256.     /**
  257.      * Returns the value on the axis of an item.
  258.      * @param dimension Dimension; space or time
  259.      * @param item int; item number (cell number in contour plot)
  260.      * @return double; the value on the axis of this item
  261.      */
  262.     final double getAxisValue(final Dimension dimension, final int item)
  263.     {
  264.         if (dimension.equals(Dimension.DISTANCE))
  265.         {
  266.             return this.spaceAxis.getBinValue(getSpaceBin(item));
  267.         }
  268.         return this.timeAxis.getBinValue(getTimeBin(item));
  269.     }

  270.     /**
  271.      * Returns the axis bin number of the given value.
  272.      * @param dimension Dimension; space or time
  273.      * @param value double; value
  274.      * @return int; axis bin number of the given value
  275.      */
  276.     final int getAxisBin(final Dimension dimension, final double value)
  277.     {
  278.         if (dimension.equals(Dimension.DISTANCE))
  279.         {
  280.             return this.spaceAxis.getValueBin(value);
  281.         }
  282.         return this.timeAxis.getValueBin(value);
  283.     }

  284.     /**
  285.      * Returns the available granularities that a linked plot may use.
  286.      * @param dimension Dimension; space or time
  287.      * @return double[]; available granularities that a linked plot may use
  288.      */
  289.     @SuppressWarnings("synthetic-access")
  290.     public final double[] getGranularities(final Dimension dimension)
  291.     {
  292.         return dimension.getAxis(this).granularities;
  293.     }

  294.     /**
  295.      * Returns the selected granularity that a linked plot should use.
  296.      * @param dimension Dimension; space or time
  297.      * @return double; granularity that a linked plot should use
  298.      */
  299.     @SuppressWarnings("synthetic-access")
  300.     public final double getGranularity(final Dimension dimension)
  301.     {
  302.         return dimension.getAxis(this).granularity;
  303.     }

  304.     /**
  305.      * Called by {@code AbstractContourPlot} to update the time. This will invalidate the plot triggering a redraw.
  306.      * @param updateTime Time; current time
  307.      */
  308.     @SuppressWarnings("synthetic-access")
  309.     final synchronized void increaseTime(final Time updateTime)
  310.     {
  311.         if (updateTime.si > this.timeAxis.maxValue)
  312.         {
  313.             this.timeAxis.setMaxValue(updateTime.si);
  314.             for (AbstractContourPlot<?> plot : this.plots)
  315.             {
  316.                 plot.setUpperDomainBound(updateTime.si);
  317.             }
  318.         }
  319.         if (this.toTime == null || updateTime.si > this.toTime.si) // null at initialization
  320.         {
  321.             invalidate(updateTime);
  322.         }
  323.     }

  324.     /**
  325.      * Sets the granularity of the plot. This will invalidate the plot triggering a redraw.
  326.      * @param dimension Dimension; space or time
  327.      * @param granularity double; granularity in space or time (SI unit)
  328.      */
  329.     public final synchronized void setGranularity(final Dimension dimension, final double granularity)
  330.     {
  331.         if (dimension.equals(Dimension.DISTANCE))
  332.         {
  333.             this.desiredSpaceGranularity = granularity;
  334.             for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
  335.             {
  336.                 contourPlot.setSpaceGranularity(granularity);
  337.             }
  338.         }
  339.         else
  340.         {
  341.             this.desiredTimeGranularity = granularity;
  342.             for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
  343.             {
  344.                 contourPlot.setUpdateInterval(Duration.instantiateSI(granularity));
  345.                 contourPlot.setTimeGranularity(granularity);
  346.             }
  347.         }
  348.         invalidate(null);
  349.     }

  350.     /**
  351.      * Sets bi-linear interpolation enabled or disabled. This will invalidate the plot triggering a redraw.
  352.      * @param interpolate boolean; whether to enable interpolation
  353.      */
  354.     @SuppressWarnings("synthetic-access")
  355.     public final void setInterpolate(final boolean interpolate)
  356.     {
  357.         if (this.timeAxis.interpolate != interpolate)
  358.         {
  359.             synchronized (this)
  360.             {
  361.                 this.timeAxis.setInterpolate(interpolate);
  362.                 this.spaceAxis.setInterpolate(interpolate);
  363.                 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
  364.                 {
  365.                     contourPlot.setInterpolation(interpolate);
  366.                 }
  367.                 invalidate(null);
  368.             }
  369.         }
  370.     }

  371.     /**
  372.      * Sets the adaptive smoothing enabled or disabled. This will invalidate the plot triggering a redraw.
  373.      * @param smooth boolean; whether to smooth the plor
  374.      */
  375.     public final void setSmooth(final boolean smooth)
  376.     {
  377.         if (this.smooth != smooth)
  378.         {
  379.             synchronized (this)
  380.             {
  381.                 this.smooth = smooth;
  382.                 for (AbstractContourPlot<?> contourPlot : ContourDataSource.this.plots)
  383.                 {
  384.                     System.out.println("not notifying plot " + contourPlot);
  385.                     // TODO work out what to do with this: contourPlot.setSmoothing(smooth);
  386.                 }
  387.                 invalidate(null);
  388.             }
  389.         }
  390.     }

  391.     // ************************
  392.     // *** UPDATING METHODS ***
  393.     // ************************

  394.     /**
  395.      * Each method that changes a setting such that the plot is no longer valid, should call this method after the setting was
  396.      * changed. If time is updated (increased), it should be given as input in to this method. The given time <i>should</i> be
  397.      * {@code null} if the plot is not valid for any other reason. In this case a full redo is initiated.
  398.      * <p>
  399.      * Every method calling this method should be {@code synchronized}, at least for the part where the setting is changed and
  400.      * this method is called. This method will in all cases add an update request to the updater, working in another thread. It
  401.      * will invoke method {@code update()}. That method utilizes a synchronized block to obtain all synchronization sensitive
  402.      * data, before starting the actual work.
  403.      * @param t Time; time up to which to show data
  404.      */
  405.     private synchronized void invalidate(final Time t)
  406.     {
  407.         if (t != null)
  408.         {
  409.             this.toTime = t;
  410.         }
  411.         else
  412.         {
  413.             this.redo = true;
  414.         }
  415.         if (this.toTime != null) // null at initialization
  416.         {
  417.             // either a later time was set, or time was null and a redo is required (will be picked up through the redo field)
  418.             // note that we cannot set {@code null}, hence we set the current to time, which may or may not have just changed
  419.             this.graphUpdater.offer(this.toTime);
  420.         }
  421.     }

  422.     /**
  423.      * Heart of the data pool. This method is invoked regularly by the "DataPool worker" thread, as scheduled in a queue through
  424.      * planned updates at an interval, or by user action changing the plot appearance. No two invocations can happen at the same
  425.      * time, as the "DataPool worker" thread executes this method before the next update request from the queue is considered.
  426.      * <p>
  427.      * This method regularly checks conditions that indicate the update should be interrupted as for example a setting has
  428.      * changed and appearance should change. Whenever a new invalidation causes {@code redo = true}, this method can stop as the
  429.      * full data needs to be recalculated. This can be set by any change of e.g. granularity or smoothing, during the update.
  430.      * <p>
  431.      * During the data recalculation, a later update time may also trigger this method to stop, while the next update will pick
  432.      * up where this update left off. During the smoothing this method doesn't stop for an increased update time, as that will
  433.      * leave a gap in the smoothed data. Note that smoothing either smoothes all data (when {@code redo = true}), or only the
  434.      * last part that falls within the kernel.
  435.      * @param t Time; time up to which to show data
  436.      */
  437.     @SuppressWarnings({ "synthetic-access", "methodlength" })
  438.     private void update(final Time t)
  439.     {
  440.         Throw.when(this.plots.isEmpty(), IllegalStateException.class, "ContourDataSource is used, but not by a contour plot!");

  441.         if (t.si < this.toTime.si)
  442.         {
  443.             // skip this update as new updates were commanded, while this update was in the queue, and a previous was running
  444.             return;
  445.         }

  446.         /**
  447.          * This method is executed once at a time by the worker Thread. Many properties, such as the data, are maintained by
  448.          * this method. Other properties, which other methods can change, are read first in a synchronized block, while those
  449.          * methods are also synchronized.
  450.          */
  451.         boolean redo0;
  452.         boolean smooth0;
  453.         boolean interpolate0;
  454.         double timeGranularity;
  455.         double spaceGranularity;
  456.         double[] spaceTicks;
  457.         double[] timeTicks;
  458.         int fromSpaceIndex = 0;
  459.         int fromTimeIndex = 0;
  460.         int toTimeIndex;
  461.         double tFromEgtf = 0;
  462.         int nFromEgtf = 0;
  463.         synchronized (this)
  464.         {
  465.             // save local copies so commands given during this execution can change it for the next execution
  466.             redo0 = this.redo;
  467.             smooth0 = this.smooth;
  468.             interpolate0 = this.timeAxis.interpolate;
  469.             // timeTicks may be longer than the simulation time, so we use the time bin for the required time of data
  470.             if (this.desiredTimeGranularity != null)
  471.             {
  472.                 this.timeAxis.setGranularity(this.desiredTimeGranularity);
  473.                 this.desiredTimeGranularity = null;
  474.             }
  475.             if (this.desiredSpaceGranularity != null)
  476.             {
  477.                 this.spaceAxis.setGranularity(this.desiredSpaceGranularity);
  478.                 this.desiredSpaceGranularity = null;
  479.             }
  480.             timeGranularity = this.timeAxis.granularity;
  481.             spaceGranularity = this.spaceAxis.granularity;
  482.             spaceTicks = this.spaceAxis.getTicks();
  483.             timeTicks = this.timeAxis.getTicks();
  484.             if (!redo0)
  485.             {
  486.                 // remember where we started, readyItems will be updated but we need to know where we started during the update
  487.                 fromSpaceIndex = getSpaceBin(this.readyItems + 1);
  488.                 fromTimeIndex = getTimeBin(this.readyItems + 1);
  489.             }
  490.             toTimeIndex = ((int) (t.si / timeGranularity)) - (interpolate0 ? 0 : 1);
  491.             if (smooth0)
  492.             {
  493.                 // time of current bin - kernel size, get bin of that time, get time (middle) of that bin
  494.                 tFromEgtf = this.timeAxis.getBinValue(redo0 ? 0 : this.timeAxis.getValueBin(
  495.                         this.timeAxis.getBinValue(fromTimeIndex) - Math.max(TAU.si, timeGranularity / 2) * KERNEL_FACTOR));
  496.                 nFromEgtf = this.timeAxis.getValueBin(tFromEgtf);
  497.             }
  498.             // starting execution, so reset redo trigger which any next command may set to true if needed
  499.             this.redo = false;
  500.         }

  501.         // reset upon a redo
  502.         if (redo0)
  503.         {
  504.             this.readyItems = -1;

  505.             // init all data arrays
  506.             int nSpace = spaceTicks.length - 1;
  507.             int nTime = timeTicks.length - 1;
  508.             this.distance = new float[nSpace][nTime];
  509.             this.time = new float[nSpace][nTime];
  510.             for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  511.             {
  512.                 this.additionalData.put(contourDataType, new float[nSpace][nTime]);
  513.             }

  514.             // setup the smoothing filter
  515.             if (smooth0)
  516.             {
  517.                 // create the filter
  518.                 this.egtf = new EGTF(C_CONG.si, this.cFree.si, DELTA_V.si, this.vc.si);

  519.                 // create data source and its data streams for speed, distance traveled, time traveled, and additional
  520.                 DataSource generic = this.egtf.getDataSource("generic");
  521.                 generic.addStream(TypedQuantity.SPEED, Speed.instantiateSI(1.0), Speed.instantiateSI(1.0));
  522.                 generic.addStreamSI(this.travelTimeQuantity, 1.0, 1.0);
  523.                 generic.addStreamSI(this.travelDistanceQuantity, 1.0, 1.0);
  524.                 this.speedStream = generic.getStream(TypedQuantity.SPEED);
  525.                 this.travelTimeStream = generic.getStream(this.travelTimeQuantity);
  526.                 this.travelDistanceStream = generic.getStream(this.travelDistanceQuantity);
  527.                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  528.                 {
  529.                     this.additionalStreams.put(contourDataType, generic.addStreamSI(contourDataType.getQuantity(), 1.0, 1.0));
  530.                 }

  531.                 // in principle we use sigma and tau, unless the data is so rough, we need more (granularity / 2).
  532.                 double tau2 = Math.max(TAU.si, timeGranularity / 2);
  533.                 double sigma2 = Math.max(SIGMA.si, spaceGranularity / 2);
  534.                 // for maximum space and time range, increase sigma and tau by KERNEL_FACTOR, beyond which both kernels diminish
  535.                 this.egtf.setGaussKernelSI(sigma2 * KERNEL_FACTOR, tau2 * KERNEL_FACTOR, sigma2, tau2);

  536.                 // add listener to provide a filter status update and to possibly stop the filter when the plot is invalidated
  537.                 this.egtf.addListener(new EgtfListener()
  538.                 {
  539.                     /** {@inheritDoc} */
  540.                     @Override
  541.                     public void notifyProgress(final EgtfEvent event)
  542.                     {
  543.                         // check stop (explicit use of property, not locally stored value)
  544.                         if (ContourDataSource.this.redo)
  545.                         {
  546.                             // plots need to be redone
  547.                             event.interrupt(); // stop the EGTF
  548.                             setStatusLabel(" "); // reset status label so no "ASM at 12.6%" remains there
  549.                             return;
  550.                         }
  551.                         String status =
  552.                                 event.getProgress() >= 1.0 ? " " : String.format("ASM at %.2f%%", event.getProgress() * 100);
  553.                         setStatusLabel(status);
  554.                     }
  555.                 });
  556.             }
  557.         }

  558.         // discard any data from smoothing if we are not smoothing
  559.         if (!smooth0)
  560.         {
  561.             // free for garbage collector to remove the data
  562.             this.egtf = null;
  563.             this.speedStream = null;
  564.             this.travelTimeStream = null;
  565.             this.travelDistanceStream = null;
  566.             this.additionalStreams.clear();
  567.         }

  568.         // ensure capacity
  569.         for (int i = 0; i < this.distance.length; i++)
  570.         {
  571.             this.distance[i] = GraphUtil.ensureCapacity(this.distance[i], toTimeIndex + 1);
  572.             this.time[i] = GraphUtil.ensureCapacity(this.time[i], toTimeIndex + 1);
  573.             for (float[][] additional : this.additionalData.values())
  574.             {
  575.                 additional[i] = GraphUtil.ensureCapacity(additional[i], toTimeIndex + 1);
  576.             }
  577.         }

  578.         // loop cells to update data
  579.         for (int j = fromTimeIndex; j <= toTimeIndex; j++)
  580.         {
  581.             Time tFrom = Time.instantiateSI(timeTicks[j]);
  582.             Time tTo = Time.instantiateSI(timeTicks[j + 1]);

  583.             // we never filter time, time always spans the entire simulation, it will contain tFrom till tTo

  584.             for (int i = fromSpaceIndex; i < spaceTicks.length - 1; i++)
  585.             {
  586.                 // when interpolating, set the first row and column to NaN so colors representing 0 do not mess up the edges
  587.                 if ((j == 0 || i == 0) && interpolate0)
  588.                 {
  589.                     this.distance[i][j] = Float.NaN;
  590.                     this.time[i][j] = Float.NaN;
  591.                     this.readyItems++;
  592.                     continue;
  593.                 }

  594.                 // only first loop with offset, later in time, none of the space was done in the previous update
  595.                 fromSpaceIndex = 0;
  596.                 Length xFrom = Length.instantiateSI(spaceTicks[i]);
  597.                 Length xTo = Length.instantiateSI(Math.min(spaceTicks[i + 1], this.path.getTotalLength().si));

  598.                 // init cell data
  599.                 double totalDistance = 0.0;
  600.                 double totalTime = 0.0;
  601.                 Map<ContourDataType<?, ?>, Object> additionalIntermediate = new LinkedHashMap<>();
  602.                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  603.                 {
  604.                     additionalIntermediate.put(contourDataType, contourDataType.identity());
  605.                 }

  606.                 // aggregate series in cell
  607.                 for (int series = 0; series < this.path.getNumberOfSeries(); series++)
  608.                 {
  609.                     // obtain trajectories
  610.                     List<TrajectoryGroup<?>> trajectories = new ArrayList<>();
  611.                     for (Section<KpiLaneDirection> section : getPath().getSections())
  612.                     {
  613.                         trajectories.add(this.samplerData.getTrajectoryGroup(section.getSource(series)));
  614.                     }

  615.                     // filter groups (lanes) that overlap with section i
  616.                     List<TrajectoryGroup<?>> included = new ArrayList<>();
  617.                     List<Length> xStart = new ArrayList<>();
  618.                     List<Length> xEnd = new ArrayList<>();
  619.                     for (int k = 0; k < trajectories.size(); k++)
  620.                     {
  621.                         TrajectoryGroup<?> trajectoryGroup = trajectories.get(k);
  622.                         KpiLaneDirection lane = trajectoryGroup.getLaneDirection();
  623.                         Length startDistance = this.path.getStartDistance(this.path.get(k));
  624.                         if (startDistance.si + this.path.get(k).getLength().si > spaceTicks[i]
  625.                                 && startDistance.si < spaceTicks[i + 1])
  626.                         {
  627.                             included.add(trajectoryGroup);
  628.                             double scale = this.path.get(k).getLength().si / lane.getLaneData().getLength().si;
  629.                             // divide by scale, so we go from base length to section length
  630.                             xStart.add(Length.max(xFrom.minus(startDistance).divide(scale), Length.ZERO));
  631.                             xEnd.add(Length.min(xTo.minus(startDistance).divide(scale),
  632.                                     trajectoryGroup.getLaneDirection().getLaneData().getLength()));
  633.                         }
  634.                     }

  635.                     // accumulate distance and time of trajectories
  636.                     for (int k = 0; k < included.size(); k++)
  637.                     {
  638.                         TrajectoryGroup<?> trajectoryGroup = included.get(k);
  639.                         for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
  640.                         {
  641.                             // for optimal operations, we first do quick-reject based on time, as by far most trajectories
  642.                             // during the entire time span of simulation will not apply to a particular cell in space-time
  643.                             if (GraphUtil.considerTrajectory(trajectory, tFrom, tTo))
  644.                             {
  645.                                 // again for optimal operations, we use a space-time view only (we don't need more)
  646.                                 SpaceTimeView spaceTimeView;
  647.                                 try
  648.                                 {
  649.                                     spaceTimeView = trajectory.getSpaceTimeView(xStart.get(k), xEnd.get(k), tFrom, tTo);
  650.                                 }
  651.                                 catch (IllegalArgumentException exception)
  652.                                 {
  653.                                     CategoryLogger.always().debug(exception,
  654.                                             "Unable to generate space-time view from x = {} to {} and t = {} to {}.",
  655.                                             xStart.get(k), xEnd.get(k), tFrom, tTo);
  656.                                     continue;
  657.                                 }
  658.                                 totalDistance += spaceTimeView.getDistance().si;
  659.                                 totalTime += spaceTimeView.getTime().si;
  660.                             }
  661.                         }
  662.                     }

  663.                     // loop and set any additional data
  664.                     for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  665.                     {
  666.                         addAdditional(additionalIntermediate, contourDataType, included, xStart, xEnd, tFrom, tTo);
  667.                     }

  668.                 }

  669.                 // scale values to the full size of a cell on a single lane, so the EGTF is interpolating comparable values
  670.                 double norm = spaceGranularity / (xTo.si - xFrom.si) / this.path.getNumberOfSeries();
  671.                 totalDistance *= norm;
  672.                 totalTime *= norm;
  673.                 this.distance[i][j] = (float) totalDistance;
  674.                 this.time[i][j] = (float) totalTime;
  675.                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  676.                 {
  677.                     this.additionalData.get(contourDataType)[i][j] =
  678.                             finalizeAdditional(additionalIntermediate, contourDataType);
  679.                 }

  680.                 // add data to EGTF (yes it's a copy, but our local data will be overwritten with smoothed data later)
  681.                 if (smooth0)
  682.                 {
  683.                     // center of cell
  684.                     double xDat = (xFrom.si + xTo.si) / 2.0;
  685.                     double tDat = (tFrom.si + tTo.si) / 2.0;
  686.                     // speed data is implicit as totalDistance/totalTime, but the EGTF needs it explicitly
  687.                     this.egtf.addPointDataSI(this.speedStream, xDat, tDat, totalDistance / totalTime);
  688.                     this.egtf.addPointDataSI(this.travelDistanceStream, xDat, tDat, totalDistance);
  689.                     this.egtf.addPointDataSI(this.travelTimeStream, xDat, tDat, totalTime);
  690.                     for (ContourDataType<?, ?> contourDataType : this.additionalStreams.keySet())
  691.                     {
  692.                         ContourDataSource.this.egtf.addPointDataSI(
  693.                                 ContourDataSource.this.additionalStreams.get(contourDataType), xDat, tDat,
  694.                                 this.additionalData.get(contourDataType)[i][j]);
  695.                     }
  696.                 }

  697.                 // check stop (explicit use of properties, not locally stored values)
  698.                 if (this.redo)
  699.                 {
  700.                     // plots need to be redone, or time has increased meaning that a next call may continue further just as well
  701.                     return;
  702.                 }

  703.                 // one more item is ready for plotting
  704.                 this.readyItems++;
  705.             }

  706.             // notify changes for every time slice
  707.             this.plots.forEach((plot) -> plot.notifyPlotChange());
  708.         }

  709.         // smooth all data that is as old as our kernel includes (or all data on a redo)
  710.         if (smooth0)
  711.         {
  712.             Set<Quantity<?, ?>> quantities = new LinkedHashSet<>();
  713.             quantities.add(this.travelDistanceQuantity);
  714.             quantities.add(this.travelTimeQuantity);
  715.             for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  716.             {
  717.                 quantities.add(contourDataType.getQuantity());
  718.             }
  719.             Filter filter = this.egtf.filterFastSI(spaceTicks[0] + 0.5 * spaceGranularity, spaceGranularity,
  720.                     spaceTicks[0] + (-1.5 + spaceTicks.length) * spaceGranularity, tFromEgtf, timeGranularity, t.si,
  721.                     quantities.toArray(new Quantity<?, ?>[quantities.size()]));
  722.             if (filter != null) // null if interrupted
  723.             {
  724.                 overwriteSmoothed(this.distance, nFromEgtf, filter.getSI(this.travelDistanceQuantity));
  725.                 overwriteSmoothed(this.time, nFromEgtf, filter.getSI(this.travelTimeQuantity));
  726.                 for (ContourDataType<?, ?> contourDataType : this.additionalData.keySet())
  727.                 {
  728.                     overwriteSmoothed(this.additionalData.get(contourDataType), nFromEgtf,
  729.                             filter.getSI(contourDataType.getQuantity()));
  730.                 }
  731.                 this.plots.forEach((plot) -> plot.notifyPlotChange());
  732.             }
  733.         }
  734.     }

  735.     /**
  736.      * Add additional data to stored intermediate result.
  737.      * @param additionalIntermediate Map&lt;ContourDataType&lt;?, ?&gt;, Object&gt;; intermediate storage map
  738.      * @param contourDataType ContourDataType&lt;?, ?&gt;; additional data type
  739.      * @param included List&lt;TrajectoryGroup&lt;?&gt;&gt;; trajectories
  740.      * @param xStart List&lt;Length&gt;; start distance per trajectory group
  741.      * @param xEnd List&lt;Length&gt;; end distance per trajectory group
  742.      * @param tFrom Time; start time
  743.      * @param tTo Time; end time
  744.      * @param <I> intermediate data type
  745.      */
  746.     @SuppressWarnings("unchecked")
  747.     private <I> void addAdditional(final Map<ContourDataType<?, ?>, Object> additionalIntermediate,
  748.             final ContourDataType<?, ?> contourDataType, final List<TrajectoryGroup<?>> included, final List<Length> xStart,
  749.             final List<Length> xEnd, final Time tFrom, final Time tTo)
  750.     {
  751.         additionalIntermediate.put(contourDataType, ((ContourDataType<?, I>) contourDataType)
  752.                 .processSeries((I) additionalIntermediate.get(contourDataType), included, xStart, xEnd, tFrom, tTo));
  753.     }

  754.     /**
  755.      * Stores a finalized result for additional data.
  756.      * @param additionalIntermediate Map&lt;ContourDataType&lt;?, ?&gt;, Object&gt;; intermediate storage map
  757.      * @param contourDataType ContourDataType&lt;?, ?&gt;; additional data type
  758.      * @return float; finalized results for a cell
  759.      * @param <I> intermediate data type
  760.      */
  761.     @SuppressWarnings("unchecked")
  762.     private <I> float finalizeAdditional(final Map<ContourDataType<?, ?>, Object> additionalIntermediate,
  763.             final ContourDataType<?, ?> contourDataType)
  764.     {
  765.         return ((ContourDataType<?, I>) contourDataType).finalize((I) additionalIntermediate.get(contourDataType)).floatValue();
  766.     }

  767.     /**
  768.      * Helper method to fill smoothed data in to raw data.
  769.      * @param raw float[][]; the raw unsmoothed data
  770.      * @param rawCol int; column from which onward to fill smoothed data in to the raw data which is used for plotting
  771.      * @param smoothed double[][]; smoothed data returned by {@code EGTF}
  772.      */
  773.     private void overwriteSmoothed(final float[][] raw, final int rawCol, final double[][] smoothed)
  774.     {
  775.         for (int i = 0; i < raw.length; i++)
  776.         {
  777.             // can't use System.arraycopy due to float vs double
  778.             for (int j = 0; j < smoothed[i].length; j++)
  779.             {
  780.                 raw[i][j + rawCol] = (float) smoothed[i][j];
  781.             }
  782.         }
  783.     }

  784.     /**
  785.      * Helper method used by an {@code EgtfListener} to present the filter progress.
  786.      * @param status String; progress report
  787.      */
  788.     private void setStatusLabel(final String status)
  789.     {
  790.         for (AbstractContourPlot<?> plot : ContourDataSource.this.plots)
  791.         {
  792.             // TODO what shall we do this this? plot.setStatusLabel(status);
  793.         }
  794.     }

  795.     // ******************************
  796.     // *** DATA RETRIEVAL METHODS ***
  797.     // ******************************

  798.     /**
  799.      * Returns the speed of the cell pertaining to plot item.
  800.      * @param item int; plot item
  801.      * @return double; speed of the cell, calculated as 'total distance' / 'total space'.
  802.      */
  803.     public double getSpeed(final int item)
  804.     {
  805.         if (item > this.readyItems)
  806.         {
  807.             return Double.NaN;
  808.         }
  809.         return getTotalDistance(item) / getTotalTime(item);
  810.     }

  811.     /**
  812.      * Returns the total distance traveled in the cell pertaining to plot item.
  813.      * @param item int; plot item
  814.      * @return double; total distance traveled in the cell
  815.      */
  816.     public double getTotalDistance(final int item)
  817.     {
  818.         if (item > this.readyItems)
  819.         {
  820.             return Double.NaN;
  821.         }
  822.         return this.distance[getSpaceBin(item)][getTimeBin(item)];
  823.     }

  824.     /**
  825.      * Returns the total time traveled in the cell pertaining to plot item.
  826.      * @param item int; plot item
  827.      * @return double; total time traveled in the cell
  828.      */
  829.     public double getTotalTime(final int item)
  830.     {
  831.         if (item > this.readyItems)
  832.         {
  833.             return Double.NaN;
  834.         }
  835.         return this.time[getSpaceBin(item)][getTimeBin(item)];
  836.     }

  837.     /**
  838.      * Returns data of the given {@code ContourDataType} for a specific item.
  839.      * @param item int; plot item
  840.      * @param contourDataType ContourDataType&lt;?, ?&gt;; contour data type
  841.      * @return data of the given {@code ContourDataType} for a specific item
  842.      */
  843.     public double get(final int item, final ContourDataType<?, ?> contourDataType)
  844.     {
  845.         if (item > this.readyItems)
  846.         {
  847.             return Double.NaN;
  848.         }
  849.         return this.additionalData.get(contourDataType)[getSpaceBin(item)][getTimeBin(item)];
  850.     }

  851.     /**
  852.      * Returns the time bin number of the item.
  853.      * @param item int; item number
  854.      * @return int; time bin number of the item
  855.      */
  856.     private int getTimeBin(final int item)
  857.     {
  858.         Throw.when(item < 0 || item >= this.spaceAxis.getBinCount() * this.timeAxis.getBinCount(),
  859.                 IndexOutOfBoundsException.class, "Item out of range");
  860.         return item / this.spaceAxis.getBinCount();
  861.     }

  862.     /**
  863.      * Returns the space bin number of the item.
  864.      * @param item int; item number
  865.      * @return int; space bin number of the item
  866.      */
  867.     private int getSpaceBin(final int item)
  868.     {
  869.         return item % this.spaceAxis.getBinCount();
  870.     }

  871.     // **********************
  872.     // *** HELPER CLASSES ***
  873.     // **********************

  874.     /**
  875.      * Enum to refer to either the distance or time axis.
  876.      * <p>
  877.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  878.      * <br>
  879.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  880.      * <p>
  881.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 10 okt. 2018 <br>
  882.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  883.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  884.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  885.      */
  886.     public enum Dimension
  887.     {
  888.         /** Distance axis. */
  889.         DISTANCE
  890.         {
  891.             /** {@inheritDoc} */
  892.             @Override
  893.             protected Axis getAxis(final ContourDataSource<?> dataPool)
  894.             {
  895.                 return dataPool.spaceAxis;
  896.             }
  897.         },

  898.         /** Time axis. */
  899.         TIME
  900.         {
  901.             /** {@inheritDoc} */
  902.             @Override
  903.             protected Axis getAxis(final ContourDataSource<?> dataPool)
  904.             {
  905.                 return dataPool.timeAxis;
  906.             }
  907.         };

  908.         /**
  909.          * Returns the {@code Axis} object.
  910.          * @param dataPool ContourDataSource&lt;?&gt;; data pool
  911.          * @return Axis; axis
  912.          */
  913.         protected abstract Axis getAxis(ContourDataSource<?> dataPool);
  914.     }

  915.     /**
  916.      * Class to store and determine axis information such as granularity, ticks, and range.
  917.      * <p>
  918.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  919.      * <br>
  920.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  921.      * <p>
  922.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 10 okt. 2018 <br>
  923.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  924.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  925.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  926.      */
  927.     static class Axis
  928.     {
  929.         /** Minimum value. */
  930.         private final double minValue;

  931.         /** Maximum value. */
  932.         private double maxValue;

  933.         /** Selected granularity. */
  934.         private double granularity;

  935.         /** Possible granularities. */
  936.         private final double[] granularities;

  937.         /** Whether the data pool is set to interpolate. */
  938.         private boolean interpolate = true;

  939.         /** Tick values. */
  940.         private double[] ticks;

  941.         /**
  942.          * Constructor.
  943.          * @param minValue double; minimum value
  944.          * @param maxValue double; maximum value
  945.          * @param granularity double; initial granularity
  946.          * @param granularities double[]; possible granularities
  947.          */
  948.         Axis(final double minValue, final double maxValue, final double granularity, final double[] granularities)
  949.         {
  950.             this.minValue = minValue;
  951.             this.maxValue = maxValue;
  952.             this.granularity = granularity;
  953.             this.granularities = granularities;
  954.         }

  955.         /**
  956.          * Sets the maximum value.
  957.          * @param maxValue double; maximum value
  958.          */
  959.         void setMaxValue(final double maxValue)
  960.         {
  961.             if (this.maxValue != maxValue)
  962.             {
  963.                 this.maxValue = maxValue;
  964.                 this.ticks = null;
  965.             }
  966.         }

  967.         /**
  968.          * Sets the granularity.
  969.          * @param granularity double; granularity
  970.          */
  971.         void setGranularity(final double granularity)
  972.         {
  973.             if (this.granularity != granularity)
  974.             {
  975.                 this.granularity = granularity;
  976.                 this.ticks = null;
  977.             }
  978.         }

  979.         /**
  980.          * Returns the ticks, which are calculated if needed.
  981.          * @return double[]; ticks
  982.          */
  983.         double[] getTicks()
  984.         {
  985.             if (this.ticks == null)
  986.             {
  987.                 int n = getBinCount() + 1;
  988.                 this.ticks = new double[n];
  989.                 int di = this.interpolate ? 1 : 0;
  990.                 for (int i = 0; i < n; i++)
  991.                 {
  992.                     if (i == n - 1)
  993.                     {
  994.                         this.ticks[i] = Math.min((i - di) * this.granularity, this.maxValue);
  995.                     }
  996.                     else
  997.                     {
  998.                         this.ticks[i] = (i - di) * this.granularity;
  999.                     }
  1000.                 }
  1001.             }
  1002.             return this.ticks;
  1003.         }

  1004.         /**
  1005.          * Calculates the number of bins.
  1006.          * @return int; number of bins
  1007.          */
  1008.         int getBinCount()
  1009.         {
  1010.             return (int) Math.ceil((this.maxValue - this.minValue) / this.granularity) + (this.interpolate ? 1 : 0);
  1011.         }

  1012.         /**
  1013.          * Calculates the center value of a bin.
  1014.          * @param bin int; bin number
  1015.          * @return double; center value of the bin
  1016.          */
  1017.         double getBinValue(final int bin)
  1018.         {
  1019.             return this.minValue + (0.5 + bin - (this.interpolate ? 1 : 0)) * this.granularity;
  1020.         }

  1021.         /**
  1022.          * Looks up the bin number of the value.
  1023.          * @param value double; value
  1024.          * @return int; bin number
  1025.          */
  1026.         int getValueBin(final double value)
  1027.         {
  1028.             getTicks();
  1029.             if (value > this.ticks[this.ticks.length - 1])
  1030.             {
  1031.                 return this.ticks.length - 1;
  1032.             }
  1033.             int i = 0;
  1034.             while (i < this.ticks.length - 1 && this.ticks[i + 1] < value + 1e-9)
  1035.             {
  1036.                 i++;
  1037.             }
  1038.             return i;
  1039.         }

  1040.         /**
  1041.          * Sets interpolation, important is it required the data to have an additional row or column.
  1042.          * @param interpolate boolean; interpolation
  1043.          */
  1044.         void setInterpolate(final boolean interpolate)
  1045.         {
  1046.             if (this.interpolate != interpolate)
  1047.             {
  1048.                 this.interpolate = interpolate;
  1049.                 this.ticks = null;
  1050.             }
  1051.         }

  1052.         /**
  1053.          * Retrieve the interpolate flag.
  1054.          * @return boolean; true if interpolation is on; false if interpolation is off
  1055.          */
  1056.         public boolean isInterpolate()
  1057.         {
  1058.             return this.interpolate;
  1059.         }

  1060.         /** {@inheritDoc} */
  1061.         @Override
  1062.         public String toString()
  1063.         {
  1064.             return "Axis [minValue=" + this.minValue + ", maxValue=" + this.maxValue + ", granularity=" + this.granularity
  1065.                     + ", granularities=" + Arrays.toString(this.granularities) + ", interpolate=" + this.interpolate
  1066.                     + ", ticks=" + Arrays.toString(this.ticks) + "]";
  1067.         }

  1068.     }

  1069.     /**
  1070.      * Interface for data types of which a contour plot can be made. Using this class, the data pool can determine and store
  1071.      * cell values for a variable set of additional data types (besides total distance, total time and speed).
  1072.      * <p>
  1073.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  1074.      * <br>
  1075.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  1076.      * <p>
  1077.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 10 okt. 2018 <br>
  1078.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  1079.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  1080.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  1081.      * @param <Z> value type
  1082.      * @param <I> intermediate data type
  1083.      */
  1084.     public interface ContourDataType<Z extends Number, I>
  1085.     {
  1086.         /**
  1087.          * Returns the initial value for intermediate result.
  1088.          * @return I, initial intermediate value
  1089.          */
  1090.         I identity();

  1091.         /**
  1092.          * Calculate value from provided trajectories that apply to a single grid cell on a single series (lane).
  1093.          * @param intermediate I; intermediate value of previous series, starts as the identity
  1094.          * @param trajectories List&lt;TrajectoryGroup&lt;?&gt;&gt;; trajectories, all groups overlap the requested space-time
  1095.          * @param xFrom List&lt;Length&gt;; start location of cell on the section
  1096.          * @param xTo List&lt;Length&gt;; end location of cell on the section.
  1097.          * @param tFrom Time; start time of cell
  1098.          * @param tTo Time; end time of cell
  1099.          * @return I; intermediate value
  1100.          */
  1101.         I processSeries(I intermediate, List<TrajectoryGroup<?>> trajectories, List<Length> xFrom, List<Length> xTo, Time tFrom,
  1102.                 Time tTo);

  1103.         /**
  1104.          * Returns the final value of the intermediate result after all lanes.
  1105.          * @param intermediate I; intermediate result after all lanes
  1106.          * @return Z; final value
  1107.          */
  1108.         Z finalize(I intermediate);

  1109.         /**
  1110.          * Returns the quantity that is being plotted on the z-axis for the EGTF filter.
  1111.          * @return Quantity&lt;Z, ?&gt;; quantity that is being plotted on the z-axis for the EGTF filter
  1112.          */
  1113.         Quantity<Z, ?> getQuantity();
  1114.     }

  1115.     /** {@inheritDoc} */
  1116.     @Override
  1117.     public String toString()
  1118.     {
  1119.         return "ContourDataSource [samplerData=" + this.samplerData + ", updateInterval=" + this.updateInterval + ", delay="
  1120.                 + this.delay + ", path=" + this.path + ", spaceAxis=" + this.spaceAxis + ", timeAxis=" + this.timeAxis
  1121.                 + ", plots=" + this.plots + ", distance=" + Arrays.toString(this.distance) + ", time="
  1122.                 + Arrays.toString(this.time) + ", additionalData=" + this.additionalData + ", smooth=" + this.smooth
  1123.                 + ", cFree=" + this.cFree + ", vc=" + this.vc + ", egtf=" + this.egtf + ", speedStream=" + this.speedStream
  1124.                 + ", travelTimeStream=" + this.travelTimeStream + ", travelDistanceStream=" + this.travelDistanceStream
  1125.                 + ", travelTimeQuantity=" + this.travelTimeQuantity + ", travelDistanceQuantity=" + this.travelDistanceQuantity
  1126.                 + ", additionalStreams=" + this.additionalStreams + ", graphUpdater=" + this.graphUpdater + ", redo="
  1127.                 + this.redo + ", toTime=" + this.toTime + ", readyItems=" + this.readyItems + ", desiredSpaceGranularity="
  1128.                 + this.desiredSpaceGranularity + ", desiredTimeGranularity=" + this.desiredTimeGranularity + "]";
  1129.     }

  1130. }