TrajectoryPlot.java
package org.opentrafficsim.graphs;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.geom.Line2D;
import java.io.Serializable;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;
import org.djunits.unit.LengthUnit;
import org.djunits.unit.TimeUnit;
import org.djunits.value.vdouble.scalar.DoubleScalar;
import org.djunits.value.vdouble.scalar.Duration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Time;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.StandardChartTheme;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.DomainOrder;
import org.jfree.data.general.DatasetChangeEvent;
import org.jfree.data.general.DatasetChangeListener;
import org.jfree.data.general.DatasetGroup;
import org.jfree.data.xy.XYDataset;
import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
import org.opentrafficsim.core.gtu.GTUException;
import org.opentrafficsim.core.network.NetworkException;
import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
import org.opentrafficsim.road.network.lane.Lane;
import nl.tudelft.simulation.dsol.SimRuntimeException;
import nl.tudelft.simulation.event.EventInterface;
import nl.tudelft.simulation.event.EventListenerInterface;
import nl.tudelft.simulation.event.TimedEvent;
/**
* Trajectory plot.
* <p>
* Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
* BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
* <p>
* $LastChangedDate: 2015-09-14 01:33:02 +0200 (Mon, 14 Sep 2015) $, @version $Revision: 1401 $, by $Author: averbraeck $,
* initial version Jul 24, 2014 <br>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
*/
public class TrajectoryPlot extends AbstractOTSPlot implements XYDataset, LaneBasedGTUSampler, EventListenerInterface
{
/** */
private static final long serialVersionUID = 20140724L;
/** Sample interval of this TrajectoryPlot. */
private final Duration sampleInterval;
/** The simulator. */
private final OTSDEVSSimulatorInterface simulator;
/**
* @return sampleInterval if this TrajectoryPlot samples at a fixed rate, or null if this TrajectoryPlot samples on the GTU
* move events
*/
public final Duration getSampleInterval()
{
return this.sampleInterval;
}
/** The cumulative lengths of the elements of path. */
private final double[] cumulativeLengths;
/**
* Retrieve the cumulative length of the sampled path at the end of a path element.
* @param index int; the index of the path element; if -1, the total length of the path is returned
* @return double; the cumulative length at the end of the specified path element in meters (si)
*/
public final double getCumulativeLength(final int index)
{
return index == -1 ? this.cumulativeLengths[this.cumulativeLengths.length - 1] : this.cumulativeLengths[index];
}
/** Maximum of the time axis. */
private Time maximumTime = new Time(300, TimeUnit.SECOND);
/**
* @return maximumTime
*/
public final Time getMaximumTime()
{
return this.maximumTime;
}
/**
* @param maximumTime set maximumTime
*/
public final void setMaximumTime(final Time maximumTime)
{
this.maximumTime = maximumTime;
}
/** Not used internally. */
private DatasetGroup datasetGroup = null;
/**
* Create a new TrajectoryPlot.
* @param caption String; the text to show above the TrajectoryPlot
* @param sampleInterval DoubleScalarRel<TimeUnit>; the time between samples of this TrajectoryPlot, or null in which
* case the GTUs are sampled whenever they fire a MOVE_EVENT
* @param path ArrayList<Lane>; the series of Lanes that will provide the data for this TrajectoryPlot
* @param simulator OTSDEVSSimulatorInterface; the simulator
*/
public TrajectoryPlot(final String caption, final Duration sampleInterval, final List<Lane> path,
final OTSDEVSSimulatorInterface simulator)
{
super(caption, path);
this.sampleInterval = sampleInterval;
this.simulator = simulator;
double[] endLengths = new double[path.size()];
double cumulativeLength = 0;
for (int i = 0; i < path.size(); i++)
{
Lane lane = path.get(i);
lane.addListener(this, Lane.GTU_ADD_EVENT, true);
lane.addListener(this, Lane.GTU_REMOVE_EVENT, true);
try
{
// Register the GTUs currently (i.e. already) on the lane (if any) for statistics sampling.
for (LaneBasedGTU gtu : lane.getGtuList())
{
notify(new TimedEvent<OTSSimTimeDouble>(Lane.GTU_ADD_EVENT, lane, new Object[] { gtu.getId(), gtu },
gtu.getSimulator().getSimulatorTime()));
}
}
catch (RemoteException exception)
{
exception.printStackTrace();
}
cumulativeLength += lane.getLength().getSI();
endLengths[i] = cumulativeLength;
}
this.cumulativeLengths = endLengths;
setChart(createChart(this));
this.reGraph(); // fixes the domain axis
if (null != this.sampleInterval)
{
try
{
this.simulator.scheduleEventRel(Duration.ZERO, this, this, "sample", null);
}
catch (SimRuntimeException exception)
{
exception.printStackTrace();
}
}
}
/** {@inheritDoc} */
@Override
public final GraphType getGraphType()
{
return GraphType.TRAJECTORY;
}
/**
* Sample all the GTUs on the observed lanes.
*/
public void sample()
{
Time now = this.simulator.getSimulatorTime().getTime();
for (LaneBasedGTU gtu : this.gtusOfInterest)
{
try
{
Map<Lane, Length> positions = gtu.positions(gtu.getReference(), now);
int hits = 0;
for (Lane lane : positions.keySet())
{
if (getPath().contains(lane))
{
Length position = positions.get(lane);
if (position.si >= 0 && position.si <= lane.getLength().si)
{
addData(gtu, lane, positions.get(lane).si);
hits++;
}
}
}
if (1 != hits)
{
System.err.println("GTU " + gtu + " scored " + hits + " (expected 1 hit)");
}
}
catch (GTUException exception)
{
exception.printStackTrace();
}
}
// Schedule the next sample
try
{
this.simulator.scheduleEventRel(this.sampleInterval, this, this, "sample", null);
}
catch (SimRuntimeException exception)
{
exception.printStackTrace();
}
}
/** The GTUs that might be of interest to gather statistics about. */
private Set<LaneBasedGTU> gtusOfInterest = new HashSet<>();
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public void notify(final EventInterface event) throws RemoteException
{
LaneBasedGTU gtu;
if (event.getType().equals(Lane.GTU_ADD_EVENT))
{
Object[] content = (Object[]) event.getContent();
gtu = (LaneBasedGTU) content[1];
if (!this.gtusOfInterest.contains(gtu))
{
this.gtusOfInterest.add(gtu);
if (null == this.sampleInterval)
{
gtu.addListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT);
}
}
}
else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
{
Object[] content = (Object[]) event.getContent();
gtu = (LaneBasedGTU) content[1];
Lane lane = null;
try
{
lane = gtu.getReferencePosition().getLane();
}
catch (GTUException exception)
{
// ignore - lane will be null
}
if (lane == null || !getPath().contains(lane))
{
this.gtusOfInterest.remove(gtu);
if (null != this.sampleInterval)
{
gtu.removeListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT);
}
else
{
String key = gtu.getId();
VariableSampleRateTrajectory carTrajectory = (VariableSampleRateTrajectory) this.trajectories.get(key);
if (null != carTrajectory)
{
carTrajectory.recordGTULeftTrajectoryEvent();
}
}
}
}
else if (event.getType().equals(LaneBasedGTU.LANEBASED_MOVE_EVENT))
{
Object[] content = (Object[]) event.getContent();
Lane lane = (Lane) content[6];
Length posOnLane = (Length) content[7];
gtu = (LaneBasedGTU) event.getSource();
if (getPath().contains(lane))
{
addData(gtu, lane, posOnLane.si);
}
}
}
/** {@inheritDoc} */
@Override
protected JFreeChart createChart(final JFrame container)
{
final JLabel statusLabel = new JLabel(" ", SwingConstants.CENTER);
container.add(statusLabel, BorderLayout.SOUTH);
ChartFactory.setChartTheme(new StandardChartTheme("JFree/Shadow", false));
final JFreeChart result =
ChartFactory.createXYLineChart(getCaption(), "", "", this, PlotOrientation.VERTICAL, false, false, false);
// Overrule the default background paint because some of the lines are invisible on top of this default.
result.getPlot().setBackgroundPaint(new Color(0.9f, 0.9f, 0.9f));
FixCaption.fixCaption(result);
NumberAxis xAxis = new NumberAxis("\u2192 " + "time [s]");
xAxis.setLowerMargin(0.0);
xAxis.setUpperMargin(0.0);
NumberAxis yAxis = new NumberAxis("\u2192 " + "Distance [m]");
yAxis.setAutoRangeIncludesZero(false);
yAxis.setLowerMargin(0.0);
yAxis.setUpperMargin(0.0);
yAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
result.getXYPlot().setDomainAxis(xAxis);
result.getXYPlot().setRangeAxis(yAxis);
Length minimumPosition = Length.ZERO;
Length maximumPosition = new Length(getCumulativeLength(-1), LengthUnit.SI);
configureAxis(result.getXYPlot().getRangeAxis(), DoubleScalar.minus(maximumPosition, minimumPosition).getSI());
final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) result.getXYPlot().getRenderer();
renderer.setBaseLinesVisible(true);
renderer.setBaseShapesVisible(false);
renderer.setBaseShape(new Line2D.Float(0, 0, 0, 0));
final ChartPanel cp = new ChartPanel(result);
cp.setMouseWheelEnabled(true);
final PointerHandler ph = new PointerHandler()
{
/** {@inheritDoc} */
@Override
void updateHint(final double domainValue, final double rangeValue)
{
if (Double.isNaN(domainValue))
{
statusLabel.setText(" ");
return;
}
String value = "";
/*-
XYDataset dataset = plot.getDataset();
double bestDistance = Double.MAX_VALUE;
Trajectory bestTrajectory = null;
final int mousePrecision = 5;
java.awt.geom.Point2D.Double mousePoint = new java.awt.geom.Point2D.Double(t, distance);
double lowTime =
plot.getDomainAxis().java2DToValue(p.getX() - mousePrecision, pi.getDataArea(),
plot.getDomainAxisEdge()) - 1;
double highTime =
plot.getDomainAxis().java2DToValue(p.getX() + mousePrecision, pi.getDataArea(),
plot.getDomainAxisEdge()) + 1;
double lowDistance =
plot.getRangeAxis().java2DToValue(p.getY() + mousePrecision, pi.getDataArea(),
plot.getRangeAxisEdge()) - 20;
double highDistance =
plot.getRangeAxis().java2DToValue(p.getY() - mousePrecision, pi.getDataArea(),
plot.getRangeAxisEdge()) + 20;
// System.out.println(String.format("Searching area t[%.1f-%.1f], x[%.1f,%.1f]", lowTime, highTime,
// lowDistance, highDistance));
for (Trajectory trajectory : this.trajectories)
{
java.awt.geom.Point2D.Double[] clippedTrajectory =
trajectory.clipTrajectory(lowTime, highTime, lowDistance, highDistance);
if (null == clippedTrajectory)
continue;
java.awt.geom.Point2D.Double prevPoint = null;
for (java.awt.geom.Point2D.Double trajectoryPoint : clippedTrajectory)
{
if (null != prevPoint)
{
double thisDistance = Planar.distancePolygonToPoint(clippedTrajectory, mousePoint);
if (thisDistance < bestDistance)
{
bestDistance = thisDistance;
bestTrajectory = trajectory;
}
}
prevPoint = trajectoryPoint;
}
}
if (null != bestTrajectory)
{
for (SimulatedObject so : indices.keySet())
if (this.trajectories.get(indices.get(so)) == bestTrajectory)
{
Point2D.Double bestPosition = bestTrajectory.getEstimatedPosition(t);
if (null == bestPosition)
continue;
value =
String.format(
Main.locale,
": vehicle %s; location on measurement path at t=%.1fs: "
+ "longitudinal %.1fm, lateral %.1fm",
so.toString(), t, bestPosition.x, bestPosition.y);
}
}
else
value = "";
*/
statusLabel.setText(String.format("t=%.0fs, distance=%.0fm%s", domainValue, rangeValue, value));
}
};
cp.addMouseMotionListener(ph);
cp.addMouseListener(ph);
container.add(cp, BorderLayout.CENTER);
// TODO ensure that shapes for all the data points don't get allocated.
// Currently JFreeChart allocates many megabytes of memory for Ellipses that are never drawn.
JPopupMenu popupMenu = cp.getPopupMenu();
popupMenu.add(new JPopupMenu.Separator());
popupMenu.add(StandAloneChartWindow.createMenuItem(this));
return result;
}
/** {@inheritDoc} */
@Override
public final void reGraph()
{
for (DatasetChangeListener dcl : getListenerList().getListeners(DatasetChangeListener.class))
{
if (dcl instanceof XYPlot)
{
configureAxis(((XYPlot) dcl).getDomainAxis(), this.maximumTime.getSI());
}
}
notifyListeners(new DatasetChangeEvent(this, null)); // This guess work actually works!
}
/**
* Configure the range of an axis.
* @param valueAxis ValueAxis
* @param range double; the upper bound of the axis
*/
private static void configureAxis(final ValueAxis valueAxis, final double range)
{
valueAxis.setUpperBound(range);
valueAxis.setLowerMargin(0);
valueAxis.setUpperMargin(0);
valueAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
valueAxis.setAutoRange(true);
valueAxis.setAutoRangeMinimumSize(range);
valueAxis.centerRange(range / 2);
}
/** {@inheritDoc} */
@Override
public void actionPerformed(final ActionEvent e)
{
// not yet
}
/** All stored trajectories. */
private HashMap<String, Trajectory> trajectories = new HashMap<String, Trajectory>();
/** Quick access to the Nth trajectory. */
private ArrayList<Trajectory> trajectoryIndices = new ArrayList<Trajectory>();
/**
* Add data for a GTU on a lane to this graph.
* @param gtu the gtu to add the data for
* @param lane the lane on which the GTU is registered
* @param posOnLane the position on the lane as a double si Length
*/
protected final void addData(final LaneBasedGTU gtu, final Lane lane, final double posOnLane)
{
int index = getPath().indexOf(lane);
if (index < 0)
{
// error -- silently ignore for now. Graphs should not cause errors.
System.err.println("TrajectoryPlot: GTU " + gtu.getId() + " is not registered on lane " + lane.toString());
return;
}
double lengthOffset = index == 0 ? 0 : this.cumulativeLengths[index - 1];
String key = gtu.getId();
Trajectory carTrajectory = this.trajectories.get(key);
if (null == carTrajectory)
{
// Create a new Trajectory for this GTU
carTrajectory =
null == this.sampleInterval ? new VariableSampleRateTrajectory(key) : new FixedSampleRateTrajectory(key);
this.trajectoryIndices.add(carTrajectory);
this.trajectories.put(key, carTrajectory);
}
try
{
carTrajectory.addSample(gtu, lane, lengthOffset + posOnLane);
}
catch (NetworkException | GTUException exception)
{
// error -- silently ignore for now. Graphs should not cause errors.
System.err.println("TrajectoryPlot: GTU " + gtu.getId() + " on lane " + lane.toString() + " caused exception "
+ exception.getMessage());
}
}
/**
* Common interface for both (all?) types of trajectories.
*/
interface Trajectory
{
/**
* Retrieve the time of the last stored event.
* @return Time; the time of the last stored event
*/
Time getCurrentEndTime();
/**
* Retrieve the last recorded non-null position, or null if no non-null positions have been recorded yet.
* @return Double; the last recorded position of this Trajectory in meters
*/
Double getLastPosition();
/**
* Retrieve the id of this Trajectory.
* @return Object; the id of this Trajectory
*/
String getId();
/**
* Add a trajectory segment sample and update the currentEndTime and currentEndPosition.
* @param gtu AbstractLaneBasedGTU; the GTU whose currently committed trajectory segment must be added
* @param lane Lane; the Lane that the positionOffset is valid for
* @param position Double; distance in meters from the start of the trajectory
* @throws NetworkException when car is not on lane anymore
* @throws GTUException on problems obtaining data from the GTU
*/
void addSample(LaneBasedGTU gtu, Lane lane, double position) throws NetworkException, GTUException;
/**
* Retrieve the number of stored samples in this Trajectory.
* @return int; number of stored samples
*/
int size();
/**
* Return the time of the Nth stored sample.
* @param item int; the index of the sample
* @return double; the time of the sample
*/
double getTime(int item);
/**
* Return the distance of the Nth stored sample.
* @param item int; the index of the sample
* @return double; the distance of the sample
*/
double getDistance(int item);
}
/**
* Store trajectory data for use with a variable sample rate.
* <p>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
*/
class VariableSampleRateTrajectory implements Trajectory, Serializable
{
/** */
private static final long serialVersionUID = 20140000L;
/** Time of (current) end of trajectory. */
private Time currentEndTime;
/** ID of the GTU. */
private final String id;
/** Storage for the samples of the GTU. */
private ArrayList<DistanceAndTime> samples = new ArrayList<DistanceAndTime>();
/**
* Construct a new VariableSamplerateTrajectory.
* @param id String; id of the new Trajectory (id of the GTU)
*/
public VariableSampleRateTrajectory(final String id)
{
this.id = id;
}
/** {@inheritDoc} */
@Override
public Time getCurrentEndTime()
{
return this.currentEndTime;
}
/** {@inheritDoc} */
@Override
public Double getLastPosition()
{
return null;
}
/** {@inheritDoc} */
@Override
public String getId()
{
return this.id;
}
/** {@inheritDoc} */
@Override
public void addSample(LaneBasedGTU gtu, Lane lane, double position) throws NetworkException, GTUException
{
if (this.samples.size() > 0)
{
DistanceAndTime lastSample = this.samples.get(this.samples.size() - 1);
if (null != lastSample)
{
Double lastPosition = lastSample.getDistance();
if (null != lastPosition && Math.abs(lastPosition - position) > 0.9 * getCumulativeLength(-1))
{
// wrap around... probably circular lane, insert a GTU left trajectory event.
recordGTULeftTrajectoryEvent();
}
}
}
this.currentEndTime = gtu.getSimulator().getSimulatorTime().getTime();
this.samples.add(new DistanceAndTime(position, this.currentEndTime.si));
}
/**
* Store that the GTU went off of the trajectory.
*/
public void recordGTULeftTrajectoryEvent()
{
this.samples.add(null);
}
/** {@inheritDoc} */
@Override
public int size()
{
return this.samples.size();
}
/**
* Retrieve the Nth sample.
* @param item int; the number of the sample
* @return DistanceAndTime; the Nth sample (samples can be null to indicate that GTU went off the trajectory).
*/
private DistanceAndTime getSample(int item)
{
return this.samples.get(item);
}
/** {@inheritDoc} */
@Override
public double getTime(int item)
{
DistanceAndTime sample = getSample(item);
if (null == sample)
{
return Double.NaN;
}
return this.samples.get(item).getTime();
}
/** {@inheritDoc} */
@Override
public double getDistance(int item)
{
DistanceAndTime sample = getSample(item);
if (null == sample)
{
return Double.NaN;
}
return sample.getDistance();
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "VariableSampleRateTrajectory [id=" + this.id + ", currentEndTime=" + this.currentEndTime + "]";
}
/**
* Store a position and a time.
*/
class DistanceAndTime
{
/** The position [m]. */
final double distance;
/** The time [s]. */
final double time;
/**
* Construct a new DistanceAndTime object.
* @param distance double; the position
* @param time double; the time
*/
public DistanceAndTime(final double distance, final double time)
{
this.distance = distance;
this.time = time;
}
/**
* Retrieve the position.
* @return double; the position
*/
public double getDistance()
{
return this.distance;
}
/**
* Retrieve the time.
* @return double; the time
*/
public double getTime()
{
return this.time;
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "DistanceAndTime [distance=" + this.distance + ", time=" + this.time + "]";
}
}
}
/**
* Store trajectory data for use with a fixed sample rate.
* <p>
* @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
*/
class FixedSampleRateTrajectory implements Trajectory, Serializable
{
/** */
private static final long serialVersionUID = 20140000L;
/** Time of (current) end of trajectory. */
private Time currentEndTime;
/** ID of the GTU. */
private final String id;
/** Storage for the position of the GTU. */
private ArrayList<Double> positions = new ArrayList<Double>();
/** Sample number of sample with index 0 in positions (following entries will each be one sampleTime later). */
private int firstSample;
/**
* Construct a FixedSampleRateTrajectory.
* @param id String; id of the new Trajectory (id of the GTU)
*/
FixedSampleRateTrajectory(final String id)
{
this.id = id;
}
/** {@inheritDoc} */
public final Time getCurrentEndTime()
{
return this.currentEndTime;
}
/** {@inheritDoc} */
public final Double getLastPosition()
{
for (int i = this.positions.size(); --i >= 0;)
{
Double result = this.positions.get(i);
if (null != result)
{
return result;
}
}
return null;
}
/** {@inheritDoc} */
public final String getId()
{
return this.id;
}
/** {@inheritDoc} */
public final void addSample(final LaneBasedGTU gtu, final Lane lane, final double position)
throws NetworkException, GTUException
{
final int sample = (int) Math.ceil(gtu.getOperationalPlan().getStartTime().si / getSampleInterval().si);
if (0 == this.positions.size())
{
this.firstSample = sample;
}
while (sample - this.firstSample > this.positions.size())
{
// insert nulls as place holders for unsampled data (usually because vehicle was in a parallel Lane)
this.positions.add(null);
}
Double adjustedPosition = position;
Double lastPosition = this.positions.size() > 0 ? this.positions.get(this.positions.size() - 1) : null;
if (null != lastPosition && Math.abs(lastPosition - position) > 0.9 * getCumulativeLength(-1))
{
// wrap around... probably circular lane.
adjustedPosition = null;
}
this.positions.add(adjustedPosition);
this.currentEndTime = gtu.getSimulator().getSimulatorTime().getTime();
/*-
try
{
final int startSample =
(int) Math.ceil(car.getOperationalPlan().getStartTime().getSI() / getSampleInterval());
final int endSample =
(int) (Math.ceil(car.getOperationalPlan().getEndTime().getSI() / getSampleInterval()));
for (int sample = startSample; sample < endSample; sample++)
{
Time sampleTime = new Time(sample * getSampleInterval(), TimeUnit.SI);
Double position = car.position(lane, car.getReference(), sampleTime).getSI() + positionOffset;
if (this.positions.size() > 0 && null != this.currentEndPosition
&& position < this.currentEndPosition.getSI() - 0.001)
{
if (0 != positionOffset)
{
// System.out.println("Already added " + car);
break;
}
// System.out.println("inserting null for " + car);
position = null; // Wrapping on circular path?
}
if (this.positions.size() == 0)
{
this.firstSample = sample;
}
while (sample - this.firstSample > this.positions.size())
{
// System.out.println("Inserting nulls");
this.positions.add(null); // insert nulls as place holders for unsampled data (usually because
// vehicle was temporarily in a parallel Lane)
}
if (null != position && this.positions.size() > sample - this.firstSample)
{
// System.out.println("Skipping sample " + car);
continue;
}
this.positions.add(position);
}
this.currentEndTime = car.getOperationalPlan().getEndTime();
this.currentEndPosition = new Length(
car.position(lane, car.getReference(), this.currentEndTime).getSI() + positionOffset, LengthUnit.SI);
}
catch (Exception e)
{
// TODO lane change causes error...
System.err.println("Trajectoryplot caught unexpected Exception: " + e.getMessage());
e.printStackTrace();
}
*/
if (gtu.getSimulator().getSimulatorTime().getTime().gt(getMaximumTime()))
{
setMaximumTime(gtu.getSimulator().getSimulatorTime().getTime());
}
}
/** {@inheritDoc} */
public int size()
{
return this.positions.size();
}
/** {@inheritDoc} */
public double getTime(final int item)
{
return (item + this.firstSample) * getSampleInterval().si;
}
/**
* @param item Integer; the sample number
* @return Double; the position indexed by item
*/
public double getDistance(final int item)
{
Double distance = this.positions.get(item);
if (null == distance)
{
return Double.NaN;
}
return this.positions.get(item);
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "FixedSampleRateTrajectory [currentEndTime=" + this.currentEndTime + ", id=" + this.id + ", positions.size="
+ this.positions.size() + ", firstSample=" + this.firstSample + "]";
}
}
/** {@inheritDoc} */
@Override
public final int getSeriesCount()
{
return this.trajectories.size();
}
/** {@inheritDoc} */
@Override
public final Comparable<Integer> getSeriesKey(final int series)
{
return series;
}
/** {@inheritDoc} */
@SuppressWarnings("rawtypes")
@Override
public final int indexOf(final Comparable seriesKey)
{
if (seriesKey instanceof Integer)
{
return (Integer) seriesKey;
}
return -1;
}
/** {@inheritDoc} */
@Override
public final DatasetGroup getGroup()
{
return this.datasetGroup;
}
/** {@inheritDoc} */
@Override
public final void setGroup(final DatasetGroup group)
{
this.datasetGroup = group;
}
/** {@inheritDoc} */
@Override
public final DomainOrder getDomainOrder()
{
return DomainOrder.ASCENDING;
}
/** {@inheritDoc} */
@Override
public final int getItemCount(final int series)
{
return this.trajectoryIndices.get(series).size();
}
/** {@inheritDoc} */
@Override
public final Number getX(final int series, final int item)
{
double v = getXValue(series, item);
if (Double.isNaN(v))
{
return null;
}
return v;
}
/** {@inheritDoc} */
@Override
public final double getXValue(final int series, final int item)
{
return this.trajectoryIndices.get(series).getTime(item);
}
/** {@inheritDoc} */
@Override
public final Number getY(final int series, final int item)
{
double v = getYValue(series, item);
if (Double.isNaN(v))
{
return null;
}
return v;
}
/** {@inheritDoc} */
@Override
public final double getYValue(final int series, final int item)
{
return this.trajectoryIndices.get(series).getDistance(item);
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "TrajectoryPlot [sampleInterval=" + this.sampleInterval + ", path=" + getPath() + ", cumulativeLengths.length="
+ this.cumulativeLengths.length + ", maximumTime=" + this.maximumTime + ", caption=" + getCaption()
+ ", trajectories.size=" + this.trajectories.size() + "]";
}
}