TrajectoryPlot.java

  1. package org.opentrafficsim.graphs;

  2. import java.awt.BorderLayout;
  3. import java.awt.Color;
  4. import java.awt.event.ActionEvent;
  5. import java.awt.event.ActionListener;
  6. import java.awt.geom.Line2D;
  7. import java.util.ArrayList;
  8. import java.util.HashMap;
  9. import java.util.List;

  10. import javax.swing.JFrame;
  11. import javax.swing.JLabel;
  12. import javax.swing.JPopupMenu;
  13. import javax.swing.SwingConstants;
  14. import javax.swing.event.EventListenerList;

  15. import org.djunits.unit.LengthUnit;
  16. import org.djunits.unit.TimeUnit;
  17. import org.djunits.value.StorageType;
  18. import org.djunits.value.ValueException;
  19. import org.djunits.value.vdouble.scalar.DoubleScalar;
  20. import org.djunits.value.vdouble.scalar.Length;
  21. import org.djunits.value.vdouble.scalar.Time;
  22. import org.djunits.value.vdouble.vector.LengthVector;
  23. import org.jfree.chart.ChartFactory;
  24. import org.jfree.chart.ChartPanel;
  25. import org.jfree.chart.JFreeChart;
  26. import org.jfree.chart.StandardChartTheme;
  27. import org.jfree.chart.axis.NumberAxis;
  28. import org.jfree.chart.axis.ValueAxis;
  29. import org.jfree.chart.plot.PlotOrientation;
  30. import org.jfree.chart.plot.XYPlot;
  31. import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
  32. import org.jfree.data.DomainOrder;
  33. import org.jfree.data.general.DatasetChangeEvent;
  34. import org.jfree.data.general.DatasetChangeListener;
  35. import org.jfree.data.general.DatasetGroup;
  36. import org.jfree.data.xy.XYDataset;
  37. import org.opentrafficsim.core.gtu.GTUException;
  38. import org.opentrafficsim.core.network.NetworkException;
  39. import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
  40. import org.opentrafficsim.road.network.lane.Lane;

  41. /**
  42.  * Trajectory plot.
  43.  * <p>
  44.  * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  45.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  46.  * <p>
  47.  * $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
  48.  * initial version Jul 24, 2014 <br>
  49.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  50.  */
  51. public class TrajectoryPlot extends JFrame implements ActionListener, XYDataset, MultipleViewerChart, LaneBasedGTUSampler

  52. {
  53.     /** */
  54.     private static final long serialVersionUID = 20140724L;

  55.     /** Sample interval of this TrajectoryPlot. */
  56.     private final Time.Rel sampleInterval;

  57.     /**
  58.      * @return sampleInterval
  59.      */
  60.     public final Time.Rel getSampleInterval()
  61.     {
  62.         return this.sampleInterval;
  63.     }

  64.     /** The series of Lanes that provide the data for this TrajectoryPlot. */
  65.     private final ArrayList<Lane> path;

  66.     /** The cumulative lengths of the elements of path. */
  67.     private final LengthVector.Rel cumulativeLengths;

  68.     /**
  69.      * Retrieve the cumulative length of the sampled path at the end of a path element.
  70.      * @param index int; the index of the path element; if -1, the total length of the path is returned
  71.      * @return Length.Rel; the cumulative length at the end of the specified path element
  72.      */
  73.     public final Length.Rel getCumulativeLength(final int index)
  74.     {
  75.         int useIndex = -1 == index ? this.cumulativeLengths.size() - 1 : index;
  76.         try
  77.         {
  78.             return new Length.Rel(this.cumulativeLengths.get(useIndex));
  79.         }
  80.         catch (ValueException exception)
  81.         {
  82.             exception.printStackTrace();
  83.         }
  84.         return null; // NOTREACHED
  85.     }

  86.     /** Maximum of the time axis. */
  87.     private Time.Abs maximumTime = new Time.Abs(300, TimeUnit.SECOND);

  88.     /**
  89.      * @return maximumTime
  90.      */
  91.     public final Time.Abs getMaximumTime()
  92.     {
  93.         return this.maximumTime;
  94.     }

  95.     /**
  96.      * @param maximumTime set maximumTime
  97.      */
  98.     public final void setMaximumTime(final Time.Abs maximumTime)
  99.     {
  100.         this.maximumTime = maximumTime;
  101.     }

  102.     /** List of parties interested in changes of this ContourPlot. */
  103.     private transient EventListenerList listenerList = new EventListenerList();

  104.     /** Not used internally. */
  105.     private DatasetGroup datasetGroup = null;

  106.     /** Name of the chart. */
  107.     private final String caption;

  108.     /**
  109.      * Create a new TrajectoryPlot.
  110.      * @param caption String; the text to show above the TrajectoryPlot
  111.      * @param sampleInterval DoubleScalarRel&lt;TimeUnit&gt;; the time between samples of this TrajectoryPlot
  112.      * @param path ArrayList&lt;Lane&gt;; the series of Lanes that will provide the data for this TrajectoryPlot
  113.      */
  114.     public TrajectoryPlot(final String caption, final Time.Rel sampleInterval, final List<Lane> path)
  115.     {
  116.         this.sampleInterval = sampleInterval;
  117.         this.path = new ArrayList<Lane>(path); // make a copy
  118.         double[] endLengths = new double[path.size()];
  119.         double cumulativeLength = 0;
  120.         LengthVector.Rel lengths = null;
  121.         for (int i = 0; i < path.size(); i++)
  122.         {
  123.             Lane lane = path.get(i);
  124.             lane.addSampler(this);
  125.             cumulativeLength += lane.getLength().getSI();
  126.             endLengths[i] = cumulativeLength;
  127.         }
  128.         try
  129.         {
  130.             lengths = new LengthVector.Rel(endLengths, LengthUnit.SI, StorageType.DENSE);
  131.         }
  132.         catch (ValueException exception)
  133.         {
  134.             exception.printStackTrace();
  135.         }
  136.         this.cumulativeLengths = lengths;
  137.         this.caption = caption;
  138.         createChart(this);
  139.         this.reGraph(); // fixes the domain axis
  140.     }

  141.     /**
  142.      * Create the visualization.
  143.      * @param container JFrame; the JFrame that will be filled with chart and the status label
  144.      * @return JFreeChart; the visualization
  145.      */
  146.     private JFreeChart createChart(final JFrame container)
  147.     {
  148.         final JLabel statusLabel = new JLabel(" ", SwingConstants.CENTER);
  149.         container.add(statusLabel, BorderLayout.SOUTH);
  150.         ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
  151.         final JFreeChart result =
  152.                 ChartFactory.createXYLineChart(this.caption, "", "", this, PlotOrientation.VERTICAL, false, false, false);
  153.         // Overrule the default background paint because some of the lines are invisible on top of this default.
  154.         result.getPlot().setBackgroundPaint(new Color(0.9f, 0.9f, 0.9f));
  155.         FixCaption.fixCaption(result);
  156.         NumberAxis xAxis = new NumberAxis("\u2192 " + "time [s]");
  157.         xAxis.setLowerMargin(0.0);
  158.         xAxis.setUpperMargin(0.0);
  159.         NumberAxis yAxis = new NumberAxis("\u2192 " + "Distance [m]");
  160.         yAxis.setAutoRangeIncludesZero(false);
  161.         yAxis.setLowerMargin(0.0);
  162.         yAxis.setUpperMargin(0.0);
  163.         yAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
  164.         result.getXYPlot().setDomainAxis(xAxis);
  165.         result.getXYPlot().setRangeAxis(yAxis);
  166.         Length.Rel minimumPosition = Length.Rel.ZERO;
  167.         Length.Rel maximumPosition = getCumulativeLength(-1);
  168.         configureAxis(result.getXYPlot().getRangeAxis(), DoubleScalar.minus(maximumPosition, minimumPosition).getSI());
  169.         final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) result.getXYPlot().getRenderer();
  170.         renderer.setBaseLinesVisible(true);
  171.         renderer.setBaseShapesVisible(false);
  172.         renderer.setBaseShape(new Line2D.Float(0, 0, 0, 0));
  173.         final ChartPanel cp = new ChartPanel(result);
  174.         cp.setMouseWheelEnabled(true);
  175.         final PointerHandler ph = new PointerHandler()
  176.         {
  177.             /** {@inheritDoc} */
  178.             @Override
  179.             void updateHint(final double domainValue, final double rangeValue)
  180.             {
  181.                 if (Double.isNaN(domainValue))
  182.                 {
  183.                     statusLabel.setText(" ");
  184.                     return;
  185.                 }
  186.                 String value = "";
  187.                 /*-
  188.                 XYDataset dataset = plot.getDataset();
  189.                 double bestDistance = Double.MAX_VALUE;
  190.                 Trajectory bestTrajectory = null;
  191.                 final int mousePrecision = 5;
  192.                 java.awt.geom.Point2D.Double mousePoint = new java.awt.geom.Point2D.Double(t, distance);
  193.                 double lowTime =
  194.                         plot.getDomainAxis().java2DToValue(p.getX() - mousePrecision, pi.getDataArea(),
  195.                                 plot.getDomainAxisEdge()) - 1;
  196.                 double highTime =
  197.                         plot.getDomainAxis().java2DToValue(p.getX() + mousePrecision, pi.getDataArea(),
  198.                                 plot.getDomainAxisEdge()) + 1;
  199.                 double lowDistance =
  200.                         plot.getRangeAxis().java2DToValue(p.getY() + mousePrecision, pi.getDataArea(),
  201.                                 plot.getRangeAxisEdge()) - 20;
  202.                 double highDistance =
  203.                         plot.getRangeAxis().java2DToValue(p.getY() - mousePrecision, pi.getDataArea(),
  204.                                 plot.getRangeAxisEdge()) + 20;
  205.                 // System.out.println(String.format("Searching area t[%.1f-%.1f], x[%.1f,%.1f]", lowTime, highTime,
  206.                 // lowDistance, highDistance));
  207.                 for (Trajectory trajectory : this.trajectories)
  208.                 {
  209.                     java.awt.geom.Point2D.Double[] clippedTrajectory =
  210.                             trajectory.clipTrajectory(lowTime, highTime, lowDistance, highDistance);
  211.                     if (null == clippedTrajectory)
  212.                         continue;
  213.                     java.awt.geom.Point2D.Double prevPoint = null;
  214.                     for (java.awt.geom.Point2D.Double trajectoryPoint : clippedTrajectory)
  215.                     {
  216.                         if (null != prevPoint)
  217.                         {
  218.                             double thisDistance = Planar.distancePolygonToPoint(clippedTrajectory, mousePoint);
  219.                             if (thisDistance < bestDistance)
  220.                             {
  221.                                 bestDistance = thisDistance;
  222.                                 bestTrajectory = trajectory;
  223.                             }
  224.                         }
  225.                         prevPoint = trajectoryPoint;
  226.                     }
  227.                 }
  228.                 if (null != bestTrajectory)
  229.                 {
  230.                     for (SimulatedObject so : indices.keySet())
  231.                         if (this.trajectories.get(indices.get(so)) == bestTrajectory)
  232.                         {
  233.                             Point2D.Double bestPosition = bestTrajectory.getEstimatedPosition(t);
  234.                             if (null == bestPosition)
  235.                                 continue;
  236.                             value =
  237.                                     String.format(
  238.                                             Main.locale,
  239.                                             ": vehicle %s; location on measurement path at t=%.1fs: "
  240.                                             + "longitudinal %.1fm, lateral %.1fm",
  241.                                             so.toString(), t, bestPosition.x, bestPosition.y);
  242.                         }
  243.                 }
  244.                 else
  245.                     value = "";
  246.                  */
  247.                 statusLabel.setText(String.format("t=%.0fs, distance=%.0fm%s", domainValue, rangeValue, value));
  248.             }
  249.         };
  250.         cp.addMouseMotionListener(ph);
  251.         cp.addMouseListener(ph);
  252.         container.add(cp, BorderLayout.CENTER);
  253.         // TODO ensure that shapes for all the data points don't get allocated.
  254.         // Currently JFreeChart allocates many megabytes of memory for Ellipses that are never drawn.
  255.         JPopupMenu popupMenu = cp.getPopupMenu();
  256.         popupMenu.add(new JPopupMenu.Separator());
  257.         popupMenu.add(StandAloneChartWindow.createMenuItem(this));
  258.         return result;
  259.     }

  260.     /**
  261.      * Redraw this TrajectoryGraph (after the underlying data has been changed).
  262.      */
  263.     public final void reGraph()
  264.     {
  265.         for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
  266.         {
  267.             if (dcl instanceof XYPlot)
  268.             {
  269.                 configureAxis(((XYPlot) dcl).getDomainAxis(), this.maximumTime.getSI());
  270.             }
  271.         }
  272.         notifyListeners(new DatasetChangeEvent(this, null)); // This guess work actually works!
  273.     }

  274.     /**
  275.      * Notify interested parties of an event affecting this TrajectoryPlot.
  276.      * @param event DatasetChangedEvent
  277.      */
  278.     private void notifyListeners(final DatasetChangeEvent event)
  279.     {
  280.         for (DatasetChangeListener dcl : this.listenerList.getListeners(DatasetChangeListener.class))
  281.         {
  282.             dcl.datasetChanged(event);
  283.         }
  284.     }

  285.     /**
  286.      * Configure the range of an axis.
  287.      * @param valueAxis ValueAxis
  288.      * @param range double; the upper bound of the axis
  289.      */
  290.     private static void configureAxis(final ValueAxis valueAxis, final double range)
  291.     {
  292.         valueAxis.setUpperBound(range);
  293.         valueAxis.setLowerMargin(0);
  294.         valueAxis.setUpperMargin(0);
  295.         valueAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
  296.         valueAxis.setAutoRange(true);
  297.         valueAxis.setAutoRangeMinimumSize(range);
  298.         valueAxis.centerRange(range / 2);
  299.     }

  300.     /** {@inheritDoc} */
  301.     @Override
  302.     public void actionPerformed(final ActionEvent e)
  303.     {
  304.         // not yet
  305.     }

  306.     /** All stored trajectories. */
  307.     private HashMap<String, Trajectory> trajectories = new HashMap<String, Trajectory>();

  308.     /** Quick access to the Nth trajectory. */
  309.     private ArrayList<Trajectory> trajectoryIndices = new ArrayList<Trajectory>();

  310.     /** {@inheritDoc} */
  311.     public final void addData(final LaneBasedGTU car, final Lane lane) throws NetworkException, GTUException
  312.     {
  313.         // final Time.Abs startTime = car.getLastEvaluationTime();
  314.         // System.out.println("addData car: " + car + ", lastEval: " + startTime);
  315.         // Convert the position of the car to a position on path.
  316.         // Find a (the first) lane that car is on that is in our path.
  317.         double lengthOffset = 0;
  318.         int index = this.path.indexOf(lane);
  319.         if (index >= 0)
  320.         {
  321.             if (index > 0)
  322.             {
  323.                 try
  324.                 {
  325.                     lengthOffset = this.cumulativeLengths.getSI(index - 1);
  326.                 }
  327.                 catch (ValueException exception)
  328.                 {
  329.                     exception.printStackTrace();
  330.                 }
  331.             }
  332.         }
  333.         else
  334.         {
  335.             throw new Error("Car is not on any lane in the path");
  336.         }
  337.         // System.out.println("lane index is " + index + " car is " + car);
  338.         // final Length.Rel startPosition =
  339.         // DoubleScalar.plus(new Length.Rel(lengthOffset, LengthUnit.SI),
  340.         // car.position(lane, car.getReference(), startTime));
  341.         String key = car.getId().toString();
  342.         Trajectory carTrajectory = this.trajectories.get(key);
  343.         if (null == carTrajectory)
  344.         {
  345.             // Create a new Trajectory for this GTU
  346.             carTrajectory = new Trajectory(key);
  347.             this.trajectoryIndices.add(carTrajectory);
  348.             this.trajectories.put(key, carTrajectory);
  349.             // System.out.println("Creating new trajectory for GTU " + key);
  350.         }
  351.         carTrajectory.addSegment(car, lane, lengthOffset);
  352.     }

  353.     /**
  354.      * Store trajectory data.
  355.      * <p>
  356.      * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  357.      * <p>
  358.      * See for project information <a href="http://www.simulation.tudelft.nl/"> www.simulation.tudelft.nl</a>.
  359.      * <p>
  360.      * The OpenTrafficSim project is distributed under the following BSD-style license:<br>
  361.      * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
  362.      * following conditions are met:
  363.      * <ul>
  364.      * <li>Redistributions of source code must retain the above copyright notice, this list of conditions and the following
  365.      * disclaimer.</li>
  366.      * <li>Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  367.      * disclaimer in the documentation and/or other materials provided with the distribution.</li>
  368.      * <li>Neither the name of Delft University of Technology, nor the names of its contributors may be used to endorse or
  369.      * promote products derived from this software without specific prior written permission.</li>
  370.      * </ul>
  371.      * This software is provided by the copyright holders and contributors "as is" and any express or implied warranties,
  372.      * including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are
  373.      * disclaimed. In no event shall the copyright holder or contributors be liable for any direct, indirect, incidental,
  374.      * special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services;
  375.      * loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in
  376.      * contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this
  377.      * software, even if advised of the possibility of such damage. $LastChangedDate: 2015-07-15 11:18:39 +0200 (Wed, 15 Jul
  378.      * 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $, initial versionJul 24, 2014 <br>
  379.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  380.      */
  381.     class Trajectory
  382.     {
  383.         /** Time of (current) end of trajectory. */
  384.         private Time.Abs currentEndTime;

  385.         /**
  386.          * Retrieve the current end time of this Trajectory.
  387.          * @return currentEndTime
  388.          */
  389.         public final Time.Abs getCurrentEndTime()
  390.         {
  391.             return this.currentEndTime;
  392.         }

  393.         /** Position of (current) end of trajectory. */
  394.         private Length.Rel currentEndPosition;

  395.         /**
  396.          * Retrieve the current end position of this Trajectory.
  397.          * @return currentEndPosition
  398.          */
  399.         public final Length.Rel getCurrentEndPosition()
  400.         {
  401.             return this.currentEndPosition;
  402.         }

  403.         /** ID of the GTU. */
  404.         private final Object id;

  405.         /**
  406.          * Retrieve the id of this Trajectory.
  407.          * @return Object; the id of this Trajectory
  408.          */
  409.         public final Object getId()
  410.         {
  411.             return this.id;
  412.         }

  413.         /** Storage for the position of the car. */
  414.         private ArrayList<Double> positions = new ArrayList<Double>();

  415.         /** Time sample of first sample in positions (successive entries will each be one sampleTime later). */
  416.         private int firstSample;

  417.         /**
  418.          * Construct a Trajectory.
  419.          * @param id Object; Id of the new Trajectory
  420.          */
  421.         public Trajectory(final Object id)
  422.         {
  423.             this.id = id;
  424.         }

  425.         /**
  426.          * Add a trajectory segment and update the currentEndTime and currentEndPosition.
  427.          * @param car AbstractLaneBasedGTU; the GTU whose currently committed trajectory segment must be added
  428.          * @param lane Lane; the Lane that the positionOffset is valid for
  429.          * @param positionOffset double; offset needed to convert the position in the current Lane to a position on the
  430.          *            trajectory
  431.          * @throws NetworkException when car is not on lane anymore
  432.          * @throws GTUException on problems obtaining data from the GTU
  433.          */
  434.         public final void addSegment(final LaneBasedGTU car, final Lane lane, final double positionOffset)
  435.                 throws NetworkException, GTUException
  436.         {
  437. //            if ("4".equals(car.getId()) && "Lane lane.0 of FirstVia to SecondVia".equals(lane.toString()))
  438. //            {
  439. //                System.out.println("Enter. positions.size is " + this.positions.size() + ", currentEndPosition is "
  440. //                        + this.currentEndPosition);
  441. //            }
  442.             try
  443.             {
  444.                 final int startSample =
  445.                         (int) Math.ceil(car.getOperationalPlan().getStartTime().getSI() / getSampleInterval().getSI());
  446.                 final int endSample =
  447.                         (int) (Math.ceil(car.getOperationalPlan().getEndTime().getSI() / getSampleInterval().getSI()));
  448.                 for (int sample = startSample; sample < endSample; sample++)
  449.                 {
  450.                     Time.Abs sampleTime = new Time.Abs(sample * getSampleInterval().getSI(), TimeUnit.SI);
  451.                     Double position = car.position(lane, car.getReference(), sampleTime).getSI() + positionOffset;
  452.                     if (this.positions.size() > 0 && null != this.currentEndPosition && position < this.currentEndPosition.getSI() - 0.001)
  453.                     {
  454.                         if (0 != positionOffset)
  455.                         {
  456.                             // System.out.println("Already added " + car);
  457.                             break;
  458.                         }
  459.                         // System.out.println("inserting null for " + car);
  460.                         position = null; // Wrapping on circular path?
  461.                     }
  462.                     if (this.positions.size() == 0)
  463.                     {
  464.                         this.firstSample = sample;
  465.                     }
  466.                     /*-
  467.                     if (sample - this.firstSample > this.positions.size())
  468.                     {
  469.                         System.out.println("Inserting " + (sample - this.positions.size())
  470.                                 + " nulls; this is trajectory number " + trajectoryIndices.indexOf(this));
  471.                     }
  472.                      */
  473.                     while (sample - this.firstSample > this.positions.size())
  474.                     {
  475.                         // System.out.println("Inserting nulls");
  476.                         this.positions.add(null); // insert nulls as place holders for unsampled data (usually because
  477.                                                   // vehicle was temporarily in a parallel Lane)
  478.                     }
  479.                     if (null != position && this.positions.size() > sample - this.firstSample)
  480.                     {
  481.                         // System.out.println("Skipping sample " + car);
  482.                         continue;
  483.                     }
  484.                     this.positions.add(position);
  485.                 }
  486.                 this.currentEndTime = car.getOperationalPlan().getEndTime();
  487.                 this.currentEndPosition =
  488.                         new Length.Rel(car.position(lane, car.getReference(), this.currentEndTime).getSI() + positionOffset,
  489.                                 LengthUnit.SI);
  490.                 if (car.getOperationalPlan().getEndTime().gt(getMaximumTime()))
  491.                 {
  492.                     setMaximumTime(car.getOperationalPlan().getEndTime());
  493.                 }
  494.             }
  495.             catch (Exception e)
  496.             {
  497.                 // TODO lane change causes error...
  498.                 System.err.println("Trajectoryplot caught unexpected Exception: " + e.getMessage());
  499.                 e.printStackTrace();
  500.             }
  501.         }

  502.         /**
  503.          * Retrieve the number of samples in this Trajectory.
  504.          * @return Integer; number of positions in this Trajectory
  505.          */
  506.         public int size()
  507.         {
  508.             return this.positions.size();
  509.         }

  510.         /**
  511.          * @param item Integer; the sample number
  512.          * @return Double; the time of the sample indexed by item
  513.          */
  514.         public double getTime(final int item)
  515.         {
  516.             return (item + this.firstSample) * getSampleInterval().getSI();
  517.         }

  518.         /**
  519.          * @param item Integer; the sample number
  520.          * @return Double; the position indexed by item
  521.          */
  522.         public double getDistance(final int item)
  523.         {
  524.             Double distance = this.positions.get(item);
  525.             if (null == distance)
  526.             {
  527.                 return Double.NaN;
  528.             }
  529.             return this.positions.get(item);
  530.         }
  531.     }

  532.     /** {@inheritDoc} */
  533.     @Override
  534.     public final int getSeriesCount()
  535.     {
  536.         return this.trajectories.size();
  537.     }

  538.     /** {@inheritDoc} */
  539.     @Override
  540.     public final Comparable<Integer> getSeriesKey(final int series)
  541.     {
  542.         return series;
  543.     }

  544.     /** {@inheritDoc} */
  545.     @SuppressWarnings("rawtypes")
  546.     @Override
  547.     public final int indexOf(final Comparable seriesKey)
  548.     {
  549.         if (seriesKey instanceof Integer)
  550.         {
  551.             return (Integer) seriesKey;
  552.         }
  553.         return -1;
  554.     }

  555.     /** {@inheritDoc} */
  556.     @Override
  557.     public final void addChangeListener(final DatasetChangeListener listener)
  558.     {
  559.         this.listenerList.add(DatasetChangeListener.class, listener);
  560.     }

  561.     /** {@inheritDoc} */
  562.     @Override
  563.     public final void removeChangeListener(final DatasetChangeListener listener)
  564.     {
  565.         this.listenerList.remove(DatasetChangeListener.class, listener);
  566.     }

  567.     /** {@inheritDoc} */
  568.     @Override
  569.     public final DatasetGroup getGroup()
  570.     {
  571.         return this.datasetGroup;
  572.     }

  573.     /** {@inheritDoc} */
  574.     @Override
  575.     public final void setGroup(final DatasetGroup group)
  576.     {
  577.         this.datasetGroup = group;
  578.     }

  579.     /** {@inheritDoc} */
  580.     @Override
  581.     public final DomainOrder getDomainOrder()
  582.     {
  583.         return DomainOrder.ASCENDING;
  584.     }

  585.     /** {@inheritDoc} */
  586.     @Override
  587.     public final int getItemCount(final int series)
  588.     {
  589.         return this.trajectoryIndices.get(series).size();
  590.     }

  591.     /** {@inheritDoc} */
  592.     @Override
  593.     public final Number getX(final int series, final int item)
  594.     {
  595.         double v = getXValue(series, item);
  596.         if (Double.isNaN(v))
  597.         {
  598.             return null;
  599.         }
  600.         return v;
  601.     }

  602.     /** {@inheritDoc} */
  603.     @Override
  604.     public final double getXValue(final int series, final int item)
  605.     {
  606.         return this.trajectoryIndices.get(series).getTime(item);
  607.     }

  608.     /** {@inheritDoc} */
  609.     @Override
  610.     public final Number getY(final int series, final int item)
  611.     {
  612.         double v = getYValue(series, item);
  613.         if (Double.isNaN(v))
  614.         {
  615.             return null;
  616.         }
  617.         return v;
  618.     }

  619.     /** {@inheritDoc} */
  620.     @Override
  621.     public final double getYValue(final int series, final int item)
  622.     {
  623.         return this.trajectoryIndices.get(series).getDistance(item);
  624.     }

  625.     /** {@inheritDoc} */
  626.     @Override
  627.     public final JFrame addViewer()
  628.     {
  629.         JFrame result = new JFrame(this.caption);
  630.         result.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
  631.         JFreeChart newChart = createChart(result);
  632.         newChart.setTitle((String) null);
  633.         addChangeListener(newChart.getPlot());
  634.         return result;
  635.     }

  636. }