FundamentalDiagram.java

  1. package org.opentrafficsim.draw.graphs;

  2. import java.awt.Color;
  3. import java.time.Period;
  4. import java.util.ArrayList;
  5. import java.util.EnumSet;
  6. import java.util.Iterator;
  7. import java.util.LinkedHashMap;
  8. import java.util.LinkedHashSet;
  9. import java.util.List;
  10. import java.util.Map;
  11. import java.util.Map.Entry;
  12. import java.util.Set;
  13. import java.util.SortedSet;
  14. import java.util.TreeSet;

  15. import org.djunits.value.vdouble.scalar.Duration;
  16. import org.djunits.value.vdouble.scalar.Length;
  17. import org.djunits.value.vdouble.scalar.Time;
  18. import org.djutils.exceptions.Throw;
  19. import org.djutils.immutablecollections.ImmutableLinkedHashSet;
  20. import org.djutils.immutablecollections.ImmutableSet;
  21. import org.jfree.chart.JFreeChart;
  22. import org.jfree.chart.LegendItem;
  23. import org.jfree.chart.LegendItemCollection;
  24. import org.jfree.chart.axis.NumberAxis;
  25. import org.jfree.chart.plot.XYPlot;
  26. import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
  27. import org.jfree.data.DomainOrder;
  28. import org.jfree.data.xy.XYDataset;
  29. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
  30. import org.opentrafficsim.kpi.sampling.KpiLaneDirection;
  31. import org.opentrafficsim.kpi.sampling.Sampler;
  32. import org.opentrafficsim.kpi.sampling.SamplingException;
  33. import org.opentrafficsim.kpi.sampling.SpaceTimeRegion;
  34. import org.opentrafficsim.kpi.sampling.Trajectory;
  35. import org.opentrafficsim.kpi.sampling.Trajectory.SpaceTimeView;
  36. import org.opentrafficsim.kpi.sampling.TrajectoryGroup;

  37. /**
  38.  * Fundamental diagram from various sources.
  39.  * <p>
  40.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  41.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  42.  * <p>
  43.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 okt. 2018 <br>
  44.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  45.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  46.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  47.  */
  48. public class FundamentalDiagram extends AbstractBoundedPlot implements XYDataset
  49. {

  50.     /** Aggregation periods. */
  51.     public static final double[] DEFAULT_PERIODS = new double[] {5.0, 10.0, 30.0, 60.0, 120.0, 300.0, 900.0};

  52.     /** Update frequencies (n * 1/period). */
  53.     public static final int[] DEFAULT_UPDATE_FREQUENCIES = new int[] {1, 2, 3, 5, 10};

  54.     /** Source providing the data. */
  55.     private final FdSource source;

  56.     /** Fundamental diagram line. */
  57.     private final FdLine fdLine;

  58.     /** Quantity on domain axis. */
  59.     private Quantity domainQuantity;

  60.     /** Quantity on range axis. */
  61.     private Quantity rangeQuantity;

  62.     /** The other, 3rd quantity. */
  63.     private Quantity otherQuantity;

  64.     /** Labels of series. */
  65.     private final List<String> seriesLabels = new ArrayList<>();

  66.     /** Updater for update times. */
  67.     private final GraphUpdater<Time> graphUpdater;

  68.     /** Property for chart listener to provide time info for status label. */
  69.     private String timeInfo = "";

  70.     /** Legend to change text color to indicate visibility. */
  71.     private LegendItemCollection legend;

  72.     /** Whether each lane is visible or not. */
  73.     private final List<Boolean> laneVisible = new ArrayList<>();

  74.     /**
  75.      * Constructor.
  76.      * @param caption String; caption
  77.      * @param domainQuantity Quantity; initial quantity on the domain axis
  78.      * @param rangeQuantity Quantity; initial quantity on the range axis
  79.      * @param simulator OTSSimulatorInterface; simulator
  80.      * @param source FdSource; source providing the data
  81.      * @param fdLine fundamental diagram line, may be {@code null}
  82.      */
  83.     public FundamentalDiagram(final String caption, final Quantity domainQuantity, final Quantity rangeQuantity,
  84.             final OTSSimulatorInterface simulator, final FdSource source, final FdLine fdLine)
  85.     {
  86.         super(simulator, caption, source.getUpdateInterval(), source.getDelay());
  87.         Throw.when(domainQuantity.equals(rangeQuantity), IllegalArgumentException.class,
  88.             "Domain and range quantity should not be equal.");
  89.         this.fdLine = fdLine;
  90.         this.setDomainQuantity(domainQuantity);
  91.         this.setRangeQuantity(rangeQuantity);
  92.         Set<Quantity> quantities = EnumSet.allOf(Quantity.class);
  93.         quantities.remove(domainQuantity);
  94.         quantities.remove(rangeQuantity);
  95.         this.setOtherQuantity(quantities.iterator().next());
  96.         this.source = source;
  97.         int d = 0;
  98.         if (fdLine != null)
  99.         {
  100.             d = 1;
  101.             this.seriesLabels.add(fdLine.getName());
  102.             this.laneVisible.add(true);
  103.         }
  104.         for (int series = 0; series < source.getNumberOfSeries(); series++)
  105.         {
  106.             this.seriesLabels.add(series + d, source.getName(series));
  107.             this.laneVisible.add(true);
  108.         }
  109.         setChart(createChart());
  110.         setLowerDomainBound(0.0);
  111.         setLowerRangeBound(0.0);

  112.         // setup updater to do the actual work in another thread
  113.         this.graphUpdater = new GraphUpdater<>("Fundamental diagram worker", Thread.currentThread(), (t) ->
  114.         {
  115.             if (this.getSource() != null)
  116.             {
  117.                 this.getSource().increaseTime(t);
  118.                 notifyPlotChange();
  119.             }
  120.         });

  121.         // let this diagram be notified by the source
  122.         source.addFundamentalDiagram(this);
  123.     }

  124.     /**
  125.      * Create a chart.
  126.      * @return JFreeChart; chart
  127.      */
  128.     private JFreeChart createChart()
  129.     {
  130.         NumberAxis xAxis = new NumberAxis(this.getDomainQuantity().label());
  131.         NumberAxis yAxis = new NumberAxis(this.getRangeQuantity().label());
  132.         XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer()
  133.         {
  134.             /** */
  135.             private static final long serialVersionUID = 20181022L;

  136.             /** {@inheritDoc} */
  137.             @SuppressWarnings("synthetic-access")
  138.             @Override
  139.             public boolean isSeriesVisible(final int series)
  140.             {
  141.                 return FundamentalDiagram.this.laneVisible.get(series);
  142.             }
  143.         }; // XYDotRenderer doesn't support different markers
  144.         renderer.setDefaultLinesVisible(false);
  145.         if (hasLineFD())
  146.         {
  147.             int series = this.getSource().getNumberOfSeries();
  148.             renderer.setSeriesLinesVisible(series, true);
  149.             renderer.setSeriesPaint(series, Color.BLACK);
  150.             renderer.setSeriesShapesVisible(series, false);
  151.         }
  152.         XYPlot plot = new XYPlot(this, xAxis, yAxis, renderer);
  153.         boolean showLegend = true;
  154.         if (!hasLineFD() && this.getSource().getNumberOfSeries() < 2)
  155.         {
  156.             plot.setFixedLegendItems(null);
  157.             showLegend = false;
  158.         }
  159.         else
  160.         {
  161.             this.legend = new LegendItemCollection();
  162.             for (int i = 0; i < this.getSource().getNumberOfSeries(); i++)
  163.             {
  164.                 LegendItem li = new LegendItem(this.getSource().getName(i));
  165.                 li.setSeriesKey(i); // lane series, not curve series
  166.                 li.setShape(renderer.lookupLegendShape(i));
  167.                 li.setFillPaint(renderer.lookupSeriesPaint(i));
  168.                 this.legend.add(li);
  169.             }
  170.             if (hasLineFD())
  171.             {
  172.                 LegendItem li = new LegendItem(this.fdLine.getName());
  173.                 li.setSeriesKey(-1);
  174.                 this.legend.add(li);
  175.             }
  176.             plot.setFixedLegendItems(this.legend);
  177.             showLegend = true;
  178.         }
  179.         return new JFreeChart(getCaption(), JFreeChart.DEFAULT_TITLE_FONT, plot, showLegend);
  180.     }

  181.     /** {@inheritDoc} */
  182.     @Override
  183.     protected void increaseTime(final Time time)
  184.     {
  185.         if (this.graphUpdater != null && time.si >= this.getSource().getAggregationPeriod().si) // null during construction
  186.         {
  187.             this.graphUpdater.offer(time);
  188.         }
  189.     }

  190.     /** {@inheritDoc} */
  191.     @Override
  192.     public int getSeriesCount()
  193.     {
  194.         if (this.getSource() == null)
  195.         {
  196.             return 0;
  197.         }
  198.         return this.getSource().getNumberOfSeries() + (hasLineFD() ? 1 : 0);
  199.     }

  200.     /** {@inheritDoc} */
  201.     @Override
  202.     public Comparable<String> getSeriesKey(final int series)
  203.     {
  204.         return this.seriesLabels.get(series);
  205.     }

  206.     /** {@inheritDoc} */
  207.     @SuppressWarnings("rawtypes")
  208.     @Override
  209.     public int indexOf(final Comparable seriesKey)
  210.     {
  211.         int index = this.seriesLabels.indexOf(seriesKey);
  212.         return index < 0 ? 0 : index;
  213.     }

  214.     /** {@inheritDoc} */
  215.     @Override
  216.     public DomainOrder getDomainOrder()
  217.     {
  218.         return DomainOrder.NONE;
  219.     }

  220.     /** {@inheritDoc} */
  221.     @Override
  222.     public int getItemCount(final int series)
  223.     {
  224.         if (hasLineFD() && series == getSeriesCount() - 1)
  225.         {
  226.             return this.fdLine.getValues(this.domainQuantity).length;
  227.         }
  228.         return this.getSource().getItemCount(series);
  229.     }

  230.     /** {@inheritDoc} */
  231.     @Override
  232.     public Number getX(final int series, final int item)
  233.     {
  234.         return getXValue(series, item);
  235.     }

  236.     /** {@inheritDoc} */
  237.     @Override
  238.     public double getXValue(final int series, final int item)
  239.     {
  240.         if (hasLineFD() && series == getSeriesCount() - 1)
  241.         {
  242.             return this.fdLine.getValues(this.domainQuantity)[item];
  243.         }
  244.         return this.getDomainQuantity().getValue(this.getSource(), series, item);
  245.     }

  246.     /** {@inheritDoc} */
  247.     @Override
  248.     public Number getY(final int series, final int item)
  249.     {
  250.         return getYValue(series, item);
  251.     }

  252.     /** {@inheritDoc} */
  253.     @Override
  254.     public double getYValue(final int series, final int item)
  255.     {
  256.         if (hasLineFD() && series == getSeriesCount() - 1)
  257.         {
  258.             return this.fdLine.getValues(this.rangeQuantity)[item];
  259.         }
  260.         return this.getRangeQuantity().getValue(this.getSource(), series, item);
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     public GraphType getGraphType()
  265.     {
  266.         return GraphType.FUNDAMENTAL_DIAGRAM;
  267.     }

  268.     /** {@inheritDoc} */
  269.     @Override
  270.     public String getStatusLabel(final double domainValue, final double rangeValue)
  271.     {
  272.         return this.getDomainQuantity().format(domainValue) + ", " + this.getRangeQuantity().format(rangeValue) + ", " + this
  273.             .getOtherQuantity().format(this.getDomainQuantity().computeOther(this.getRangeQuantity(), domainValue, rangeValue))
  274.             + this.getTimeInfo();
  275.     }

  276.     /**
  277.      * Quantity enum defining density, flow and speed.
  278.      * <p>
  279.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  280.      * <br>
  281.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  282.      * <p>
  283.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 okt. 2018 <br>
  284.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  285.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  286.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  287.      */
  288.     public enum Quantity
  289.     {
  290.         /** Density. */
  291.         DENSITY
  292.         {
  293.             /** {@inheritDoc} */
  294.             @Override
  295.             public String label()
  296.             {
  297.                 return "Density [veh/km] \u2192";
  298.             }

  299.             /** {@inheritDoc} */
  300.             @Override
  301.             public String format(final double value)
  302.             {
  303.                 return String.format("%.0f veh/km", value);
  304.             }

  305.             /** {@inheritDoc} */
  306.             @Override
  307.             public double getValue(final FdSource src, final int series, final int item)
  308.             {
  309.                 return 1000 * src.getDensity(series, item);
  310.             }

  311.             /** {@inheritDoc} */
  312.             @Override
  313.             public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
  314.             {
  315.                 // .......................... speed = flow / density .. flow = density * speed
  316.                 return pairing.equals(FLOW) ? pairedValue / thisValue : thisValue * pairedValue;
  317.             }
  318.         },

  319.         /** Flow. */
  320.         FLOW
  321.         {
  322.             /** {@inheritDoc} */
  323.             @Override
  324.             public String label()
  325.             {
  326.                 return "Flow [veh/h] \u2192";
  327.             }

  328.             /** {@inheritDoc} */
  329.             @Override
  330.             public String format(final double value)
  331.             {
  332.                 return String.format("%.0f veh/h", value);
  333.             }

  334.             /** {@inheritDoc} */
  335.             @Override
  336.             public double getValue(final FdSource src, final int series, final int item)
  337.             {
  338.                 return 3600 * src.getFlow(series, item);
  339.             }

  340.             /** {@inheritDoc} */
  341.             @Override
  342.             public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
  343.             {
  344.                 // speed = flow * density ... density = flow / speed
  345.                 return thisValue / pairedValue;
  346.             }
  347.         },

  348.         /** Speed. */
  349.         SPEED
  350.         {
  351.             /** {@inheritDoc} */
  352.             @Override
  353.             public String label()
  354.             {
  355.                 return "Speed [km/h] \u2192";
  356.             }

  357.             /** {@inheritDoc} */
  358.             @Override
  359.             public String format(final double value)
  360.             {
  361.                 return String.format("%.1f km/h", value);
  362.             }

  363.             /** {@inheritDoc} */
  364.             @Override
  365.             public double getValue(final FdSource src, final int series, final int item)
  366.             {
  367.                 return 3.6 * src.getSpeed(series, item);
  368.             }

  369.             /** {@inheritDoc} */
  370.             @Override
  371.             public double computeOther(final Quantity pairing, final double thisValue, final double pairedValue)
  372.             {
  373.                 // ............................. flow = speed * density .. density = flow / speed
  374.                 return pairing.equals(DENSITY) ? thisValue * pairedValue : pairedValue / thisValue;
  375.             }
  376.         };

  377.         /**
  378.          * Returns an axis label of the quantity.
  379.          * @return String; axis label of the quantity
  380.          */
  381.         public abstract String label();

  382.         /**
  383.          * Formats a value for status display.
  384.          * @param value double; value
  385.          * @return String; formatted string including quantity
  386.          */
  387.         public abstract String format(double value);

  388.         /**
  389.          * Get scaled value in presentation unit.
  390.          * @param src FdSource; the data source
  391.          * @param series int; series number
  392.          * @param item int; item number in series
  393.          * @return double; scaled value in presentation unit
  394.          */
  395.         public abstract double getValue(FdSource src, int series, int item);

  396.         /**
  397.          * Compute the value of the 3rd quantity.
  398.          * @param pairing Quantity; quantity on other axis
  399.          * @param thisValue double; value of this quantity
  400.          * @param pairedValue double; value of the paired quantity on the other axis
  401.          * @return double; value of the 3rd quantity
  402.          */
  403.         public abstract double computeOther(Quantity pairing, double thisValue, double pairedValue);

  404.     }

  405.     /**
  406.      * Data source for a fundamental diagram.
  407.      * <p>
  408.      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  409.      * <br>
  410.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  411.      * <p>
  412.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 16 okt. 2018 <br>
  413.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  414.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  415.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  416.      */
  417.     public interface FdSource
  418.     {
  419.         /**
  420.          * Returns the possible intervals.
  421.          * @return double[]; possible intervals
  422.          */
  423.         default double[] getPossibleAggregationPeriods()
  424.         {
  425.             return DEFAULT_PERIODS;
  426.         }

  427.         /**
  428.          * Returns the possible frequencies, as a factor on 1 / 'aggregation interval'.
  429.          * @return int[]; possible frequencies
  430.          */
  431.         default int[] getPossibleUpdateFrequencies()
  432.         {
  433.             return DEFAULT_UPDATE_FREQUENCIES;
  434.         }

  435.         /**
  436.          * Add fundamental diagram. Used to notify diagrams when data has changed.
  437.          * @param fundamentalDiagram FundamentalDiagram; fundamental diagram
  438.          */
  439.         void addFundamentalDiagram(FundamentalDiagram fundamentalDiagram);

  440.         /**
  441.          * Clears all connected fundamental diagrams.
  442.          */
  443.         void clearFundamentalDiagrams();

  444.         /**
  445.          * Returns the diagrams.
  446.          * @return ImmutableSet&lt;FundamentalDiagram&gt; diagrams
  447.          */
  448.         ImmutableSet<FundamentalDiagram> getDiagrams();

  449.         /**
  450.          * The update interval.
  451.          * @return Duration; update interval
  452.          */
  453.         Duration getUpdateInterval();

  454.         /**
  455.          * Changes the update interval.
  456.          * @param interval Duration; update interval
  457.          * @param time Time; time until which data has to be recalculated
  458.          */
  459.         void setUpdateInterval(Duration interval, Time time);

  460.         /**
  461.          * The aggregation period.
  462.          * @return Duration; aggregation period
  463.          */
  464.         Duration getAggregationPeriod();

  465.         /**
  466.          * Changes the aggregation period.
  467.          * @param period Duration; aggregation period
  468.          */
  469.         void setAggregationPeriod(Duration period);

  470.         /**
  471.          * Recalculates the data after the aggregation or update time was changed.
  472.          * @param time Time; time up to which recalculation is required
  473.          */
  474.         void recalculate(Time time);

  475.         /**
  476.          * Return the delay for graph updates so future influencing events have occurred, e.d. GTU move's.
  477.          * @return Duration; graph delay
  478.          */
  479.         Duration getDelay();

  480.         /**
  481.          * Increase the time span.
  482.          * @param time Time; time to increase to
  483.          */
  484.         void increaseTime(Time time);

  485.         /**
  486.          * Returns the number of series (i.e. lanes or 1 for aggregated).
  487.          * @return int; number of series
  488.          */
  489.         int getNumberOfSeries();

  490.         /**
  491.          * Returns a name of the series.
  492.          * @param series int; series number
  493.          * @return String; name of the series
  494.          */
  495.         String getName(int series);

  496.         /**
  497.          * Returns the number of items in the series.
  498.          * @param series int; series number
  499.          * @return int; number of items in the series
  500.          */
  501.         int getItemCount(int series);

  502.         /**
  503.          * Return the SI flow value of item in series.
  504.          * @param series int; series number
  505.          * @param item int; item number in the series
  506.          * @return double; SI flow value of item in series
  507.          */
  508.         double getFlow(int series, int item);

  509.         /**
  510.          * Return the SI density value of item in series.
  511.          * @param series int; series number
  512.          * @param item int; item number in the series
  513.          * @return double; SI density value of item in series
  514.          */
  515.         double getDensity(int series, int item);

  516.         /**
  517.          * Return the SI speed value of item in series.
  518.          * @param series int; series number
  519.          * @param item int; item number in the series
  520.          * @return double; SI speed value of item in series
  521.          */
  522.         double getSpeed(int series, int item);

  523.         /**
  524.          * Returns whether this source aggregates lanes.
  525.          * @return boolean; whether this source aggregates lanes
  526.          */
  527.         boolean isAggregate();

  528.         /**
  529.          * Sets the name of the series when aggregated, e.g. for legend. Default is "Aggregate".
  530.          * @param aggregateName String; name of the series when aggregated
  531.          */
  532.         void setAggregateName(String aggregateName);
  533.     }

  534.     /**
  535.      * Abstract implementation to link to fundamental diagrams.
  536.      */
  537.     abstract static class AbstractFdSource implements FdSource
  538.     {

  539.         /** Fundamental diagrams. */
  540.         private Set<FundamentalDiagram> fundamentalDiagrams = new LinkedHashSet<>();

  541.         /** {@inheritDoc} */
  542.         @Override
  543.         public void addFundamentalDiagram(final FundamentalDiagram fundamentalDiagram)
  544.         {
  545.             this.fundamentalDiagrams.add(fundamentalDiagram);
  546.         }

  547.         /** {@inheritDoc} */
  548.         @Override
  549.         public void clearFundamentalDiagrams()
  550.         {
  551.             this.fundamentalDiagrams.clear();
  552.         }

  553.         /** {@inheritDoc} */
  554.         @Override
  555.         public ImmutableSet<FundamentalDiagram> getDiagrams()
  556.         {
  557.             return new ImmutableLinkedHashSet<>(this.fundamentalDiagrams);
  558.         }

  559.     }

  560.     /**
  561.      * Creates a {@code Source} from a sampler and positions.
  562.      * @param sampler Sampler&lt;?&gt;; sampler
  563.      * @param crossSection GraphCrossSection&lt;KpiLaneDirection&gt;; cross section
  564.      * @param aggregateLanes boolean; whether to aggregate the positions
  565.      * @param aggregationTime Duration; aggregation time (and update time)
  566.      * @param harmonic boolean; harmonic mean
  567.      * @return Source; source for a fundamental diagram from a sampler and positions
  568.      */
  569.     @SuppressWarnings("methodlength")
  570.     public static FdSource sourceFromSampler(final Sampler<?> sampler, final GraphCrossSection<KpiLaneDirection> crossSection,
  571.             final boolean aggregateLanes, final Duration aggregationTime, final boolean harmonic)
  572.     {
  573.         return new CrossSectionSamplerFdSource<>(sampler, crossSection, aggregateLanes, aggregationTime, harmonic);
  574.     }

  575.     /**
  576.      * Creates a {@code Source} from a sampler and positions.
  577.      * @param sampler Sampler&lt;?&gt;; sampler
  578.      * @param path GraphPath&lt;KpiLaneDirection&gt;; cross section
  579.      * @param aggregateLanes boolean; whether to aggregate the positions
  580.      * @param aggregationTime Duration; aggregation time (and update time)
  581.      * @return Source; source for a fundamental diagram from a sampler and positions
  582.      */
  583.     public static FdSource sourceFromSampler(final Sampler<?> sampler, final GraphPath<KpiLaneDirection> path,
  584.             final boolean aggregateLanes, final Duration aggregationTime)
  585.     {
  586.         return new PathSamplerFdSource<>(sampler, path, aggregateLanes, aggregationTime);
  587.     }

  588.     /**
  589.      * Combines multiple sources in to one source.
  590.      * @param sources Map&lt;String, FdSource&gt;; sources coupled to their names for in the legend
  591.      * @return FdSource; combined source
  592.      */
  593.     public static FdSource combinedSource(final Map<String, FdSource> sources)
  594.     {
  595.         return new MultiFdSource(sources);
  596.     }

  597.     /**
  598.      * Fundamental diagram source based on a cross section.
  599.      * @param <S> underlying source type
  600.      */
  601.     private static class CrossSectionSamplerFdSource<S extends GraphCrossSection<? extends KpiLaneDirection>> extends
  602.         AbstractSpaceSamplerFdSource<S>
  603.     {
  604.         /** Harmonic mean. */
  605.         private final boolean harmonic;

  606.         /**
  607.          * Constructor.
  608.          * @param sampler Sampler&lt;?&gt;; sampler
  609.          * @param crossSection S; cross section
  610.          * @param aggregateLanes boolean; whether to aggregate the lanes
  611.          * @param aggregationPeriod Duration; initial aggregation {@link Period}
  612.          * @param harmonic boolean; harmonic mean
  613.          */
  614.         CrossSectionSamplerFdSource(final Sampler<?> sampler, final S crossSection, final boolean aggregateLanes,
  615.                 final Duration aggregationPeriod, final boolean harmonic)
  616.         {
  617.             super(sampler, crossSection, aggregateLanes, aggregationPeriod);
  618.             this.harmonic = harmonic;
  619.         }

  620.         /** {@inheritDoc} */
  621.         @Override
  622.         protected void getMeasurements(final Trajectory<?> trajectory, final Time startTime, final Time endTime,
  623.                 final Length length, final int series, final double[] measurements)
  624.         {
  625.             Length x = getSpace().position(series);
  626.             if (GraphUtil.considerTrajectory(trajectory, x, x))
  627.             {
  628.                 // detailed check
  629.                 Time t = trajectory.getTimeAtPosition(x);
  630.                 if (t.si >= startTime.si && t.si < endTime.si)
  631.                 {
  632.                     measurements[0] = 1; // first = count
  633.                     measurements[1] = // second = sum of (inverted) speeds
  634.                             this.harmonic ? 1.0 / trajectory.getSpeedAtPosition(x).si : trajectory.getSpeedAtPosition(x).si;
  635.                 }
  636.             }
  637.         }

  638.         /** {@inheritDoc} */
  639.         @Override
  640.         protected double getVehicleCount(final double first, final double second)
  641.         {
  642.             return first; // is divided by aggregation period by caller
  643.         }

  644.         /** {@inheritDoc} */
  645.         @Override
  646.         protected double getSpeed(final double first, final double second)
  647.         {
  648.             return this.harmonic ? first / second : second / first;
  649.         }

  650.         /** {@inheritDoc} */
  651.         @Override
  652.         public String toString()
  653.         {
  654.             return "CrossSectionSamplerFdSource [harmonic=" + this.harmonic + "]";
  655.         }

  656.     }

  657.     /**
  658.      * Fundamental diagram source based on a path. Density, speed and flow over the entire path are calculated per lane.
  659.      * @param <S> underlying source type
  660.      */
  661.     private static class PathSamplerFdSource<S extends GraphPath<? extends KpiLaneDirection>> extends
  662.         AbstractSpaceSamplerFdSource<S>
  663.     {
  664.         /**
  665.          * Constructor.
  666.          * @param sampler Sampler&lt;?&gt;; sampler
  667.          * @param path S; path
  668.          * @param aggregateLanes boolean; whether to aggregate the lanes
  669.          * @param aggregationPeriod Duration; initial aggregation period
  670.          */
  671.         PathSamplerFdSource(final Sampler<?> sampler, final S path, final boolean aggregateLanes,
  672.                 final Duration aggregationPeriod)
  673.         {
  674.             super(sampler, path, aggregateLanes, aggregationPeriod);
  675.         }

  676.         /** {@inheritDoc} */
  677.         @Override
  678.         protected void getMeasurements(final Trajectory<?> trajectory, final Time startTime, final Time endTime,
  679.                 final Length length, final int sereies, final double[] measurements)
  680.         {
  681.             SpaceTimeView stv = trajectory.getSpaceTimeView(Length.ZERO, length, startTime, endTime);
  682.             measurements[0] = stv.getDistance().si; // first = total traveled distance
  683.             measurements[1] = stv.getTime().si; // second = total traveled time
  684.         }

  685.         /** {@inheritDoc} */
  686.         @Override
  687.         protected double getVehicleCount(final double first, final double second)
  688.         {
  689.             return first / getSpace().getTotalLength().si; // is divided by aggregation period by caller
  690.         }

  691.         /** {@inheritDoc} */
  692.         @Override
  693.         protected double getSpeed(final double first, final double second)
  694.         {
  695.             return first / second;
  696.         }

  697.         /** {@inheritDoc} */
  698.         @Override
  699.         public String toString()
  700.         {
  701.             return "PathSamplerFdSource []";
  702.         }

  703.     }

  704.     /**
  705.      * Abstract class that deals with updating and recalculating the fundamental diagram.
  706.      * @param <S> underlying source type
  707.      */
  708.     private abstract static class AbstractSpaceSamplerFdSource<S extends AbstractGraphSpace<? extends KpiLaneDirection>> extends
  709.         AbstractFdSource
  710.     {
  711.         /** Period number of last calculated period. */
  712.         private int periodNumber = -1;

  713.         /** Update interval. */
  714.         private Duration updateInterval;

  715.         /** Aggregation period. */
  716.         private Duration aggregationPeriod;

  717.         /** Last update time. */
  718.         private Time lastUpdateTime;

  719.         /** Number of series. */
  720.         private final int nSeries;

  721.         /** First data. */
  722.         private double[][] firstMeasurement;

  723.         /** Second data. */
  724.         private double[][] secondMeasurement;

  725.         /** Whether the plot is in a process such that the data is invalid for the current draw of the plot. */
  726.         private boolean invalid = false;

  727.         /** The sampler. */
  728.         private final Sampler<?> sampler;

  729.         /** Space. */
  730.         private final S space;

  731.         /** Whether to aggregate the lanes. */
  732.         private final boolean aggregateLanes;

  733.         /** Name of the series when aggregated. */
  734.         private String aggregateName = "Aggregate";

  735.         /** For each series (lane), the highest trajectory number (n) below which all trajectories were also handled (0:n). */
  736.         private Map<KpiLaneDirection, Integer> lastConsecutivelyAssignedTrajectories = new LinkedHashMap<>();

  737.         /** For each series (lane), a list of handled trajectories above n, excluding n+1. */
  738.         private Map<KpiLaneDirection, SortedSet<Integer>> assignedTrajectories = new LinkedHashMap<>();

  739.         /**
  740.          * Constructor.
  741.          * @param sampler Sampler&lt;?&gt;; sampler
  742.          * @param space S; space
  743.          * @param aggregateLanes boolean; whether to aggregate the lanes
  744.          * @param aggregationPeriod Duration; initial aggregation period
  745.          */
  746.         AbstractSpaceSamplerFdSource(final Sampler<?> sampler, final S space, final boolean aggregateLanes,
  747.                 final Duration aggregationPeriod)
  748.         {
  749.             this.sampler = sampler;
  750.             this.space = space;
  751.             this.aggregateLanes = aggregateLanes;
  752.             this.nSeries = aggregateLanes ? 1 : space.getNumberOfSeries();
  753.             // create and register kpi lane directions
  754.             for (KpiLaneDirection laneDirection : space)
  755.             {
  756.                 sampler.registerSpaceTimeRegion(new SpaceTimeRegion(laneDirection, Length.ZERO, laneDirection.getLaneData()
  757.                     .getLength(), sampler.now(), Time.instantiateSI(Double.MAX_VALUE)));

  758.                 // info per kpi lane direction
  759.                 this.lastConsecutivelyAssignedTrajectories.put(laneDirection, -1);
  760.                 this.assignedTrajectories.put(laneDirection, new TreeSet<>());
  761.             }

  762.             this.updateInterval = aggregationPeriod;
  763.             this.aggregationPeriod = aggregationPeriod;
  764.             this.firstMeasurement = new double[this.nSeries][10];
  765.             this.secondMeasurement = new double[this.nSeries][10];
  766.         }

  767.         /**
  768.          * Returns the space.
  769.          * @return S; space
  770.          */
  771.         protected S getSpace()
  772.         {
  773.             return this.space;
  774.         }

  775.         /** {@inheritDoc} */
  776.         @Override
  777.         public Duration getUpdateInterval()
  778.         {
  779.             return this.updateInterval;
  780.         }

  781.         /** {@inheritDoc} */
  782.         @Override
  783.         public void setUpdateInterval(final Duration interval, final Time time)
  784.         {
  785.             if (this.updateInterval != interval)
  786.             {
  787.                 this.updateInterval = interval;
  788.                 recalculate(time);
  789.             }
  790.         }

  791.         /** {@inheritDoc} */
  792.         @Override
  793.         public Duration getAggregationPeriod()
  794.         {
  795.             return this.aggregationPeriod;
  796.         }

  797.         /** {@inheritDoc} */
  798.         @Override
  799.         public void setAggregationPeriod(final Duration period)
  800.         {
  801.             if (this.aggregationPeriod != period)
  802.             {
  803.                 this.aggregationPeriod = period;
  804.             }
  805.         }

  806.         /** {@inheritDoc} */
  807.         @Override
  808.         public void recalculate(final Time time)
  809.         {
  810.             new Thread(new Runnable()
  811.             {
  812.                 @Override
  813.                 @SuppressWarnings("synthetic-access")
  814.                 public void run()
  815.                 {
  816.                     synchronized (AbstractSpaceSamplerFdSource.this)
  817.                     {
  818.                         // an active plot draw will now request data on invalid items
  819.                         AbstractSpaceSamplerFdSource.this.invalid = true;
  820.                         AbstractSpaceSamplerFdSource.this.periodNumber = -1;
  821.                         AbstractSpaceSamplerFdSource.this.updateInterval = getUpdateInterval();
  822.                         AbstractSpaceSamplerFdSource.this.firstMeasurement =
  823.                                 new double[AbstractSpaceSamplerFdSource.this.nSeries][10];
  824.                         AbstractSpaceSamplerFdSource.this.secondMeasurement =
  825.                                 new double[AbstractSpaceSamplerFdSource.this.nSeries][10];
  826.                         AbstractSpaceSamplerFdSource.this.lastConsecutivelyAssignedTrajectories.clear();
  827.                         AbstractSpaceSamplerFdSource.this.assignedTrajectories.clear();
  828.                         for (KpiLaneDirection lane : AbstractSpaceSamplerFdSource.this.space)
  829.                         {
  830.                             AbstractSpaceSamplerFdSource.this.lastConsecutivelyAssignedTrajectories.put(lane, -1);
  831.                             AbstractSpaceSamplerFdSource.this.assignedTrajectories.put(lane, new TreeSet<>());
  832.                         }
  833.                         AbstractSpaceSamplerFdSource.this.lastUpdateTime = null; // so the increaseTime call is not skipped
  834.                         while ((AbstractSpaceSamplerFdSource.this.periodNumber + 1) * getUpdateInterval().si
  835.                             + AbstractSpaceSamplerFdSource.this.aggregationPeriod.si <= time.si)
  836.                         {
  837.                             increaseTime(Time.instantiateSI((AbstractSpaceSamplerFdSource.this.periodNumber + 1)
  838.                                 * getUpdateInterval().si + AbstractSpaceSamplerFdSource.this.aggregationPeriod.si));
  839.                             // TODO: if multiple plots are coupled to the same source, other plots are not invalidated
  840.                             // TODO: change of aggregation period / update freq, is not updated in the GUI on other plots
  841.                             // for (FundamentalDiagram diagram : getDiagrams())
  842.                             // {
  843.                             // }
  844.                         }
  845.                         AbstractSpaceSamplerFdSource.this.invalid = false;
  846.                     }
  847.                 }
  848.             }, "Fundamental diagram recalculation").start();
  849.         }

  850.         /** {@inheritDoc} */
  851.         @Override
  852.         public Duration getDelay()
  853.         {
  854.             return Duration.instantiateSI(1.0);
  855.         }

  856.         /** {@inheritDoc} */
  857.         @Override
  858.         public synchronized void increaseTime(final Time time)
  859.         {
  860.             if (time.si < this.aggregationPeriod.si)
  861.             {
  862.                 // skip periods that fall below 0.0 time
  863.                 return;
  864.             }

  865.             if (this.lastUpdateTime != null && time.le(this.lastUpdateTime))
  866.             {
  867.                 // skip updates from different graphs at the same time
  868.                 return;
  869.             }
  870.             this.lastUpdateTime = time;

  871.             // ensure capacity
  872.             int nextPeriod = this.periodNumber + 1;
  873.             if (nextPeriod >= this.firstMeasurement[0].length - 1)
  874.             {
  875.                 for (int i = 0; i < this.nSeries; i++)
  876.                 {
  877.                     this.firstMeasurement[i] = GraphUtil.ensureCapacity(this.firstMeasurement[i], nextPeriod + 1);
  878.                     this.secondMeasurement[i] = GraphUtil.ensureCapacity(this.secondMeasurement[i], nextPeriod + 1);
  879.                 }
  880.             }

  881.             // loop positions and trajectories
  882.             Time startTime = time.minus(this.aggregationPeriod);
  883.             double first = 0;
  884.             double second = 0.0;
  885.             for (int series = 0; series < this.space.getNumberOfSeries(); series++)
  886.             {
  887.                 Iterator<? extends KpiLaneDirection> it = this.space.iterator(series);
  888.                 while (it.hasNext())
  889.                 {
  890.                     KpiLaneDirection lane = it.next();
  891.                     if (!this.sampler.getSamplerData().contains(lane))
  892.                     {
  893.                         // sampler has not yet started to record on this lane
  894.                         continue;
  895.                     }
  896.                     TrajectoryGroup<?> trajectoryGroup = this.sampler.getSamplerData().getTrajectoryGroup(lane);
  897.                     int last = this.lastConsecutivelyAssignedTrajectories.get(lane);
  898.                     SortedSet<Integer> assigned = this.assignedTrajectories.get(lane);
  899.                     if (!this.aggregateLanes)
  900.                     {
  901.                         first = 0.0;
  902.                         second = 0.0;
  903.                     }

  904.                     // Length x = this.crossSection.position(series);
  905.                     int i = 0;
  906.                     for (Trajectory<?> trajectory : trajectoryGroup.getTrajectories())
  907.                     {
  908.                         // we can skip all assigned trajectories, which are all up to and including 'last' and all in 'assigned'
  909.                         try
  910.                         {
  911.                             if (i > last && !assigned.contains(i))
  912.                             {
  913.                                 // quickly filter
  914.                                 if (GraphUtil.considerTrajectory(trajectory, startTime, time))
  915.                                 {
  916.                                     double[] measurements = new double[2];
  917.                                     getMeasurements(trajectory, startTime, time, lane.getLaneData().getLength(), series,
  918.                                         measurements);
  919.                                     first += measurements[0];
  920.                                     second += measurements[1];
  921.                                 }
  922.                                 if (trajectory.getT(trajectory.size() - 1) < startTime.si - getDelay().si)
  923.                                 {
  924.                                     assigned.add(i);
  925.                                 }
  926.                             }
  927.                             i++;
  928.                         }
  929.                         catch (SamplingException exception)
  930.                         {
  931.                             throw new RuntimeException("Unexpected exception while counting trajectories.", exception);
  932.                         }
  933.                     }
  934.                     if (!this.aggregateLanes)
  935.                     {
  936.                         this.firstMeasurement[series][nextPeriod] = first;
  937.                         this.secondMeasurement[series][nextPeriod] = second;
  938.                     }

  939.                     // consolidate list of assigned trajectories in 'all up to n' and 'these specific ones beyond n'
  940.                     if (!assigned.isEmpty())
  941.                     {
  942.                         int possibleNextLastAssigned = assigned.first();
  943.                         while (possibleNextLastAssigned == last + 1) // consecutive or very first
  944.                         {
  945.                             last = possibleNextLastAssigned;
  946.                             assigned.remove(possibleNextLastAssigned);
  947.                             possibleNextLastAssigned = assigned.isEmpty() ? -1 : assigned.first();
  948.                         }
  949.                         this.lastConsecutivelyAssignedTrajectories.put(lane, last);
  950.                     }
  951.                 }
  952.             }
  953.             if (this.aggregateLanes)
  954.             {
  955.                 // whatever we measured, it was summed and can be normalized per line like this
  956.                 this.firstMeasurement[0][nextPeriod] = first / this.space.getNumberOfSeries();
  957.                 this.secondMeasurement[0][nextPeriod] = second / this.space.getNumberOfSeries();
  958.             }
  959.             this.periodNumber = nextPeriod;
  960.         }

  961.         /** {@inheritDoc} */
  962.         @Override
  963.         public int getNumberOfSeries()
  964.         {
  965.             // if there is an active plot draw as the data is being recalculated, data on invalid items is requested
  966.             // a call to getSeriesCount() indicates a new draw, and during a recalculation the data is limited but valid
  967.             this.invalid = false;
  968.             return this.nSeries;
  969.         }

  970.         /** {@inheritDoc} */
  971.         @Override
  972.         public void setAggregateName(final String aggregateName)
  973.         {
  974.             this.aggregateName = aggregateName;
  975.         }

  976.         /** {@inheritDoc} */
  977.         @Override
  978.         public String getName(final int series)
  979.         {
  980.             if (this.aggregateLanes)
  981.             {
  982.                 return this.aggregateName;
  983.             }
  984.             return this.space.getName(series);
  985.         }

  986.         /** {@inheritDoc} */
  987.         @Override
  988.         public int getItemCount(final int series)
  989.         {
  990.             return this.periodNumber + 1;
  991.         }

  992.         /** {@inheritDoc} */
  993.         @Override
  994.         public final double getFlow(final int series, final int item)
  995.         {
  996.             if (this.invalid)
  997.             {
  998.                 return Double.NaN;
  999.             }
  1000.             return getVehicleCount(this.firstMeasurement[series][item], this.secondMeasurement[series][item])
  1001.                 / this.aggregationPeriod.si;
  1002.         }

  1003.         /** {@inheritDoc} */
  1004.         @Override
  1005.         public final double getDensity(final int series, final int item)
  1006.         {
  1007.             return getFlow(series, item) / getSpeed(series, item);
  1008.         }

  1009.         /** {@inheritDoc} */
  1010.         @Override
  1011.         public final double getSpeed(final int series, final int item)
  1012.         {
  1013.             if (this.invalid)
  1014.             {
  1015.                 return Double.NaN;
  1016.             }
  1017.             return getSpeed(this.firstMeasurement[series][item], this.secondMeasurement[series][item]);
  1018.         }

  1019.         /** {@inheritDoc} */
  1020.         @Override
  1021.         public final boolean isAggregate()
  1022.         {
  1023.             return this.aggregateLanes;
  1024.         }

  1025.         /**
  1026.          * Returns the first and the second measurement of a trajectory. For a cross-section this is 1 and the vehicle speed if
  1027.          * the trajectory crosses the location, and for a path it is the traveled distance and the traveled time. If the
  1028.          * trajectory didn't cross the cross section or space-time range, both should be 0.
  1029.          * @param trajectory Trajectory&lt;?&gt;; trajectory
  1030.          * @param startTime Time; start time of aggregation period
  1031.          * @param endTime Time; end time of aggregation period
  1032.          * @param length Length; length of the section (to cut off possible lane overshoot of trajectories)
  1033.          * @param series int; series number in the section
  1034.          * @param measurements double[]; array with length 2 to place the first and second measurement in
  1035.          */
  1036.         protected abstract void getMeasurements(Trajectory<?> trajectory, Time startTime, Time endTime, Length length,
  1037.                 int series, double[] measurements);

  1038.         /**
  1039.          * Returns the vehicle count of two related measurement values. For a cross section: vehicle count & sum of speeds (or
  1040.          * sum of inverted speeds for the harmonic mean). For a path: total traveled distance & total traveled time.
  1041.          * <p>
  1042.          * The value will be divided by the aggregation time to calculate flow. Hence, for a cross section the first measurement
  1043.          * should be returned, while for a path the first measurement divided by the section length should be returned. That
  1044.          * will end up to equate to {@code q = sum(x)/XT}.
  1045.          * @param first double; first measurement value
  1046.          * @param second double; second measurement value
  1047.          * @return double; flow
  1048.          */
  1049.         protected abstract double getVehicleCount(double first, double second);

  1050.         /**
  1051.          * Returns the speed of two related measurement values. For a cross section: vehicle count & sum of speeds (or sum of
  1052.          * inverted speeds for the harmonic mean). For a path: total traveled distance & total traveled time.
  1053.          * @param first double; first measurement value
  1054.          * @param second double; second measurement value
  1055.          * @return double; speed
  1056.          */
  1057.         protected abstract double getSpeed(double first, double second);

  1058.     }

  1059.     /**
  1060.      * Class to group multiple sources in plot.
  1061.      */
  1062.     // TODO: when sub-sources recalculate responding to a click in the graph, they notify only their coupled plots, which are
  1063.     // none
  1064.     private static class MultiFdSource extends AbstractFdSource
  1065.     {

  1066.         /** Sources. */
  1067.         private FdSource[] sources;

  1068.         /** Source names. */
  1069.         private String[] sourceNames;

  1070.         /**
  1071.          * Constructor.
  1072.          * @param sources Map&lt;String, FdSource&gt;; sources
  1073.          */
  1074.         MultiFdSource(final Map<String, FdSource> sources)
  1075.         {
  1076.             Throw.when(sources == null || sources.size() == 0, IllegalArgumentException.class,
  1077.                 "At least 1 source is required.");
  1078.             this.sources = new FdSource[sources.size()];
  1079.             this.sourceNames = new String[sources.size()];
  1080.             int index = 0;
  1081.             for (Entry<String, FdSource> entry : sources.entrySet())
  1082.             {
  1083.                 this.sources[index] = entry.getValue();
  1084.                 this.sourceNames[index] = entry.getKey();
  1085.                 index++;
  1086.             }
  1087.         }

  1088.         /**
  1089.          * Returns from a series number overall, the index of the sub-source and the series index in that source.
  1090.          * @param series int; overall series number
  1091.          * @return index of the sub-source and the series index in that source
  1092.          */
  1093.         private int[] getSourceAndSeries(final int series)
  1094.         {
  1095.             int source = 0;
  1096.             int sourceSeries = series;
  1097.             while (sourceSeries >= this.sources[source].getNumberOfSeries())
  1098.             {
  1099.                 sourceSeries -= this.sources[source].getNumberOfSeries();
  1100.                 source++;
  1101.             }
  1102.             return new int[] {source, sourceSeries};
  1103.         }

  1104.         /** {@inheritDoc} */
  1105.         @Override
  1106.         public Duration getUpdateInterval()
  1107.         {
  1108.             return this.sources[0].getUpdateInterval();
  1109.         }

  1110.         /** {@inheritDoc} */
  1111.         @Override
  1112.         public void setUpdateInterval(final Duration interval, final Time time)
  1113.         {
  1114.             for (FdSource source : this.sources)
  1115.             {
  1116.                 source.setUpdateInterval(interval, time);
  1117.             }
  1118.         }

  1119.         /** {@inheritDoc} */
  1120.         @Override
  1121.         public Duration getAggregationPeriod()
  1122.         {
  1123.             return this.sources[0].getAggregationPeriod();
  1124.         }

  1125.         /** {@inheritDoc} */
  1126.         @Override
  1127.         public void setAggregationPeriod(final Duration period)
  1128.         {
  1129.             for (FdSource source : this.sources)
  1130.             {
  1131.                 source.setAggregationPeriod(period);
  1132.             }
  1133.         }

  1134.         /** {@inheritDoc} */
  1135.         @Override
  1136.         public void recalculate(final Time time)
  1137.         {
  1138.             for (FdSource source : this.sources)
  1139.             {
  1140.                 source.recalculate(time);
  1141.             }
  1142.         }

  1143.         /** {@inheritDoc} */
  1144.         @Override
  1145.         public Duration getDelay()
  1146.         {
  1147.             return this.sources[0].getDelay();
  1148.         }

  1149.         /** {@inheritDoc} */
  1150.         @Override
  1151.         public void increaseTime(final Time time)
  1152.         {
  1153.             for (FdSource source : this.sources)
  1154.             {
  1155.                 source.increaseTime(time);
  1156.             }
  1157.         }

  1158.         /** {@inheritDoc} */
  1159.         @Override
  1160.         public int getNumberOfSeries()
  1161.         {
  1162.             int numberOfSeries = 0;
  1163.             for (FdSource source : this.sources)
  1164.             {
  1165.                 numberOfSeries += source.getNumberOfSeries();
  1166.             }
  1167.             return numberOfSeries;
  1168.         }

  1169.         /** {@inheritDoc} */
  1170.         @Override
  1171.         public String getName(final int series)
  1172.         {
  1173.             int[] ss = getSourceAndSeries(series);
  1174.             return this.sourceNames[ss[0]] + (this.sources[ss[0]].isAggregate() ? "" : ": " + this.sources[ss[0]].getName(
  1175.                 ss[1]));
  1176.         }

  1177.         /** {@inheritDoc} */
  1178.         @Override
  1179.         public int getItemCount(final int series)
  1180.         {
  1181.             int[] ss = getSourceAndSeries(series);
  1182.             return this.sources[ss[0]].getItemCount(ss[1]);
  1183.         }

  1184.         /** {@inheritDoc} */
  1185.         @Override
  1186.         public double getFlow(final int series, final int item)
  1187.         {
  1188.             int[] ss = getSourceAndSeries(series);
  1189.             return this.sources[ss[0]].getFlow(ss[1], item);
  1190.         }

  1191.         /** {@inheritDoc} */
  1192.         @Override
  1193.         public double getDensity(final int series, final int item)
  1194.         {
  1195.             int[] ss = getSourceAndSeries(series);
  1196.             return this.sources[ss[0]].getDensity(ss[1], item);
  1197.         }

  1198.         /** {@inheritDoc} */
  1199.         @Override
  1200.         public double getSpeed(final int series, final int item)
  1201.         {
  1202.             int[] ss = getSourceAndSeries(series);
  1203.             return this.sources[ss[0]].getSpeed(ss[1], item);
  1204.         }

  1205.         /** {@inheritDoc} */
  1206.         @Override
  1207.         public boolean isAggregate()
  1208.         {
  1209.             return false;
  1210.         }

  1211.         /** {@inheritDoc} */
  1212.         @Override
  1213.         public void setAggregateName(final String aggregateName)
  1214.         {
  1215.             // invalid for this source type
  1216.         }

  1217.     }

  1218.     /**
  1219.      * Defines a line plot for a fundamental diagram.
  1220.      */
  1221.     public interface FdLine
  1222.     {
  1223.         /**
  1224.          * Return the values for the given quantity. For two quantities, this should result in a 2D fundamental diagram line.
  1225.          * @param quantity Quantity; quantity to return value for.
  1226.          * @return double[]; values for quantity
  1227.          */
  1228.         double[] getValues(Quantity quantity);

  1229.         /**
  1230.          * Returns the name of the line, as shown in the legend.
  1231.          * @return String; name of the line, as shown in the legend
  1232.          */
  1233.         String getName();
  1234.     }

  1235.     /** {@inheritDoc} */
  1236.     @Override
  1237.     public String toString()
  1238.     {
  1239.         return "FundamentalDiagram [source=" + this.getSource() + ", domainQuantity=" + this.getDomainQuantity()
  1240.             + ", rangeQuantity=" + this.getRangeQuantity() + ", otherQuantity=" + this.getOtherQuantity() + ", seriesLabels="
  1241.             + this.seriesLabels + ", graphUpdater=" + this.graphUpdater + ", timeInfo=" + this.getTimeInfo() + ", legend="
  1242.             + this.legend + ", laneVisible=" + this.laneVisible + "]";
  1243.     }

  1244.     /**
  1245.      * Get the data source.
  1246.      * @return FdSource; the data source
  1247.      */
  1248.     public FdSource getSource()
  1249.     {
  1250.         return this.source;
  1251.     }

  1252.     /**
  1253.      * Retrievee the legend of this FundamentalDiagram.
  1254.      * @return LegendItemCollection; the legend
  1255.      */
  1256.     public LegendItemCollection getLegend()
  1257.     {
  1258.         return this.legend;
  1259.     }

  1260.     /**
  1261.      * Return the list of lane visibility flags.
  1262.      * @return List&lt;Boolean&gt;; the list of lane visibility flags
  1263.      */
  1264.     public List<Boolean> getLaneVisible()
  1265.     {
  1266.         return this.laneVisible;
  1267.     }

  1268.     /**
  1269.      * Return the domain quantity.
  1270.      * @return Quantity; the domain quantity
  1271.      */
  1272.     public Quantity getDomainQuantity()
  1273.     {
  1274.         return this.domainQuantity;
  1275.     }

  1276.     /**
  1277.      * Set the domain quantity.
  1278.      * @param domainQuantity Quantity; the new domain quantity
  1279.      */
  1280.     public void setDomainQuantity(final Quantity domainQuantity)
  1281.     {
  1282.         this.domainQuantity = domainQuantity;
  1283.     }

  1284.     /**
  1285.      * Get the other (non domain; vertical axis) quantity.
  1286.      * @return Quantity; the quantity for the vertical axis
  1287.      */
  1288.     public Quantity getOtherQuantity()
  1289.     {
  1290.         return this.otherQuantity;
  1291.     }

  1292.     /**
  1293.      * Set the other (non domain; vertical axis) quantity.
  1294.      * @param otherQuantity Quantity; the quantity for the vertical axis
  1295.      */
  1296.     public void setOtherQuantity(final Quantity otherQuantity)
  1297.     {
  1298.         this.otherQuantity = otherQuantity;
  1299.     }

  1300.     /**
  1301.      * Get the range quantity.
  1302.      * @return Quantity; the range quantity
  1303.      */
  1304.     public Quantity getRangeQuantity()
  1305.     {
  1306.         return this.rangeQuantity;
  1307.     }

  1308.     /**
  1309.      * Set the range quantity.
  1310.      * @param rangeQuantity Quantity; the new range quantity
  1311.      */
  1312.     public void setRangeQuantity(final Quantity rangeQuantity)
  1313.     {
  1314.         this.rangeQuantity = rangeQuantity;
  1315.     }

  1316.     /**
  1317.      * Retrieve the time info.
  1318.      * @return String; the time info
  1319.      */
  1320.     public String getTimeInfo()
  1321.     {
  1322.         return this.timeInfo;
  1323.     }

  1324.     /**
  1325.      * Set the time info.
  1326.      * @param timeInfo String; the new time info
  1327.      */
  1328.     public void setTimeInfo(final String timeInfo)
  1329.     {
  1330.         this.timeInfo = timeInfo;
  1331.     }

  1332.     /**
  1333.      * Return whether the plot has a fundamental diagram line.
  1334.      * @return boolean; whether the plot has a fundamental diagram line
  1335.      */
  1336.     public boolean hasLineFD()
  1337.     {
  1338.         return this.fdLine != null;
  1339.     }

  1340. }