getSeriesKey(final int series)
{
return series;
}
/** {@inheritDoc} */
@SuppressWarnings("rawtypes")
@Override
public int indexOf(final Comparable seriesKey)
{
return 0;
}
/** {@inheritDoc} */
@Override
public DomainOrder getDomainOrder()
{
return DomainOrder.ASCENDING;
}
/** {@inheritDoc} */
@Override
public int getItemCount(final int series)
{
OffsetTrajectory trajectory = getTrajectory(series);
return trajectory == null ? 0 : trajectory.size();
}
/** {@inheritDoc} */
@Override
public Number getX(final int series, final int item)
{
return getXValue(series, item);
}
/** {@inheritDoc} */
@Override
public double getXValue(final int series, final int item)
{
return getTrajectory(series).getT(item);
}
/** {@inheritDoc} */
@Override
public Number getY(final int series, final int item)
{
return getYValue(series, item);
}
/** {@inheritDoc} */
@Override
public double getYValue(final int series, final int item)
{
return getTrajectory(series).getX(item);
}
/**
* Get the trajectory of the series number.
* @param series int; series
* @return OffsetTrajectory; trajectory of the series number
*/
private OffsetTrajectory getTrajectory(final int series)
{
int[] n = getLaneAndSeriesNumber(series);
return this.curves.get(n[0]).get(n[1]);
}
/**
* Returns the lane number, and series number within the lane data.
* @param series int; overall series number
* @return int[]; lane number, and series number within the lane data
*/
private int[] getLaneAndSeriesNumber(final int series)
{
int n = series;
for (int i = 0; i < this.curves.size(); i++)
{
int m = this.curvesPerLane.get(i);
if (n < m)
{
return new int[] {i, n};
}
n -= m;
}
throw new RuntimeException("Discrepancy between series number and available data.");
}
/**
* Extension of a line renderer to select a color based on GTU ID, and to overrule an unused shape to save memory.
*
* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
*
* BSD-style license. See OpenTrafficSim License.
*
* @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 okt. 2018
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
private final class XYLineAndShapeRendererID extends XYLineAndShapeRenderer
{
/** */
private static final long serialVersionUID = 20181014L;
/**
* Constructor.
*/
XYLineAndShapeRendererID()
{
super(false, true);
setDefaultLinesVisible(true);
setDefaultShapesVisible(false);
setDrawSeriesLineAsPath(true);
}
/** {@inheritDoc} */
@SuppressWarnings("synthetic-access")
@Override
public boolean isSeriesVisible(final int series)
{
int[] n = getLaneAndSeriesNumber(series);
return TrajectoryPlot.this.laneVisible.get(n[0]);
}
/** {@inheritDoc} */
@SuppressWarnings("synthetic-access")
@Override
public Stroke getSeriesStroke(final int series)
{
if (TrajectoryPlot.this.curves.size() == 1)
{
return STROKES[0];
}
int[] n = getLaneAndSeriesNumber(series);
return TrajectoryPlot.this.strokes.get(n[0]).get(n[1]);
}
/** {@inheritDoc} */
@SuppressWarnings("synthetic-access")
@Override
public Paint getSeriesPaint(final int series)
{
if (TrajectoryPlot.this.curves.size() == 1)
{
String gtuId = getTrajectory(series).getGtuId();
for (int pos = gtuId.length(); --pos >= 0;)
{
Character c = gtuId.charAt(pos);
if (Character.isDigit(c))
{
return IdGtuColorer.LEGEND.get(c - '0').getColor();
}
}
}
int[] n = getLaneAndSeriesNumber(series);
return COLORMAP[n[0] % COLORMAP.length];
}
/**
* {@inheritDoc} Largely based on the super implementation, but returns a dummy shape for markers to save memory and as
* markers are not used.
*/
@SuppressWarnings("synthetic-access")
@Override
protected void addEntity(final EntityCollection entities, final Shape hotspot, final XYDataset dataset,
final int series, final int item, final double entityX, final double entityY)
{
if (!getItemCreateEntity(series, item))
{
return;
}
// if not hotspot is provided, we create a default based on the
// provided data coordinates (which are already in Java2D space)
Shape hotspot2 = hotspot == null ? NO_SHAPE : hotspot;
String tip = null;
XYToolTipGenerator generator = getToolTipGenerator(series, item);
if (generator != null)
{
tip = generator.generateToolTip(dataset, series, item);
}
String url = null;
if (getURLGenerator() != null)
{
url = getURLGenerator().generateURL(dataset, series, item);
}
XYItemEntity entity = new XYItemEntity(hotspot2, dataset, series, item, tip, url);
entities.add(entity);
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "XYLineAndShapeRendererID []";
}
}
/**
* Class containing a trajectory with an offset. Takes care of bits that are before and beyond the lane without affecting
* the trajectory itself.
*
* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
*
* BSD-style license. See OpenTrafficSim License.
*
* @version $Revision$, $LastChangedDate$, by $Author$, initial version 14 okt. 2018
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
*/
private class OffsetTrajectory
{
/** The trajectory. */
private final Trajectory> trajectory;
/** The offset. */
private final double offset;
/** Scale factor for space dimension. */
private final double scaleFactor;
/** First index of the trajectory to include, possibly cutting some measurements before the lane. */
private int first;
/** Size of the trajectory to consider starting at first, possibly cutting some measurements beyond the lane. */
private int size;
/** Length of the lane to determine {@code size}. */
private final Length laneLength;
/**
* Construct a new TrajectoryAndLengthOffset object.
* @param trajectory Trajectory<?>; the trajectory
* @param offset Length; the length from the beginning of the sampled path to the start of the lane to which the
* trajectory belongs
* @param scaleFactor double; scale factor for space dimension
* @param laneLength Length; length of the lane
*/
OffsetTrajectory(final Trajectory> trajectory, final Length offset, final double scaleFactor, final Length laneLength)
{
this.trajectory = trajectory;
this.offset = offset.si;
this.scaleFactor = scaleFactor;
this.laneLength = laneLength;
}
/**
* Returns the number of measurements in the trajectory.
* @return int; number of measurements in the trajectory
*/
public final int size()
{
// as trajectories grow, this calculation needs to be done on each request
try
{
/*
* Note on overlap:
*
* Suppose a GTU crosses a lane boundary producing the following events, where distance e->| is the front, and
* |->l is the tail, relative to the reference point of the GTU.
* @formatter:off
* ------------------------------------------- o) regular move event
* o e o o | l o o e) lane enter event on next lane
* ------------------------------------------- l) lane leave event on previous lane
* o o o (l) measurements on previous lane
* (e) (o) (o) o o measurements on next lane
* @formatter:on
* Trajectories of a particular GTU are not explicitly tied together. Not only would this involve quite some
* work, it is also impossible to distinguish a lane change near the start or end of a lane, from moving
* longitudinally on to the next lane. The basic idea to minimize overlap is to remove all positions on the
* previous lane beyond the lane length, and all negative positions on the next lane, i.e. all between ( ). This
* would however create a gap at the lane boundary '|'. Allowing one event beyond the lane length may still
* result in a gap, l->o in this case. Allowing one event before the lane would work in this case, but 'e' could
* also fall between an 'o' and '|'. At one trajectory it is thus not known whether the other trajectory
* continues from, or is continued from, the extra point. Hence we require an extra point before the lane and
* one beyond the lane to assure there is no gap. The resulting overlap can be as large as a move, but this is
* better than occasional gaps.
*/
int f = 0;
while (f < this.trajectory.size() - 1 && this.trajectory.getX(f + 1) < 0.0)
{
f++;
}
this.first = f;
int s = this.trajectory.size() - 1;
while (s > 1 && this.trajectory.getX(s - 1) > this.laneLength.si)
{
s--;
}
this.size = s - f + 1;
}
catch (SamplingException exception)
{
throw new RuntimeException("Unexpected exception while obtaining location value from trajectory for plotting.",
exception);
}
return this.size;
}
/**
* Returns the location, including offset, of an item.
* @param item int; item (sample) number
* @return double; location, including offset, of an item
*/
public final double getX(final int item)
{
return Try.assign(() -> this.offset + this.trajectory.getX(this.first + item) * this.scaleFactor,
"Unexpected exception while obtaining location value from trajectory for plotting.");
}
/**
* Returns the time of an item.
* @param item int; item (sample) number
* @return double; time of an item
*/
public final double getT(final int item)
{
return Try.assign(() -> (double) this.trajectory.getT(this.first + item),
"Unexpected exception while obtaining time value from trajectory for plotting.");
}
/**
* Returns the ID of the GTU of this trajectory.
* @return String; the ID of the GTU of this trajectory
*/
public final String getGtuId()
{
return this.trajectory.getGtuId();
}
/** {@inheritDoc} */
@Override
public final String toString()
{
return "OffsetTrajectory [trajectory=" + this.trajectory + ", offset=" + this.offset + "]";
}
}
/** {@inheritDoc} */
@Override
public String toString()
{
return "TrajectoryPlot [graphUpdater=" + this.graphUpdater + ", knownTrajectories=" + this.knownTrajectories
+ ", curves=" + this.curves + ", strokes=" + this.strokes + ", curvesPerLane=" + this.curvesPerLane
+ ", legend=" + this.legend + ", laneVisible=" + this.laneVisible + "]";
}
/**
* Retrieve the legend.
* @return LegendItemCollection; the legend
*/
public LegendItemCollection getLegend()
{
return this.legend;
}
/**
* Retrieve the lane visibility flags.
* @return List<Boolean>; the lane visibility flags
*/
public List getLaneVisible()
{
return this.laneVisible;
}
}