package org.opentrafficsim.kpi.sampling;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.djunits.value.vdouble.scalar.Acceleration;
import org.djunits.value.vdouble.scalar.Length;
import org.djunits.value.vdouble.scalar.Speed;
import org.djunits.value.vdouble.scalar.Time;
import org.djutils.exceptions.Throw;
import org.opentrafficsim.kpi.interfaces.GtuDataInterface;
import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
import org.opentrafficsim.kpi.sampling.meta.FilterDataType;
import org.opentrafficsim.kpi.sampling.meta.MetaData;
/**
* Sampler is the highest level organizer for sampling.
*
* Copyright (c) 2013-2021 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 Sep 22, 2016
* @author Alexander Verbraeck
* @author Peter Knoppers
* @author Wouter Schakel
* @param gtu data type
*/
public abstract class Sampler
{
/** Sampler data. */
private final SamplerData samplerData;
/** Registration of included extended data types. */
private final Set> extendedDataTypes;
/** Set of registered filter data types. */
private final Set> filterDataTypes;
/** Registration of current trajectories of each GTU per lane. */
private final Map>> trajectoryPerGtu = new LinkedHashMap<>();
/** End times of active samplings. */
private final Map endTimes = new LinkedHashMap<>();
/** Space time regions. */
private Set spaceTimeRegions = new LinkedHashSet<>();
/**
* Constructor.
* @param extendedDataTypes Set<ExtendedDataType<?, ?, ?, G>>; extended data types
* @param filterDataTypes Set<FilterDataType<?>>; filter data types
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public Sampler(final Set> extendedDataTypes, final Set> filterDataTypes)
{
this.extendedDataTypes = new LinkedHashSet<>(extendedDataTypes);
this.filterDataTypes = new LinkedHashSet<>(filterDataTypes);
Set> columns = new LinkedHashSet<>();
// TODO: fixed columns!
for (ExtendedDataType, ?, ?, G> extendedDataType : this.extendedDataTypes)
{
columns.add(new SimpleColumn(extendedDataType.getId(), extendedDataType.getId(), extendedDataType.getType()));
}
for (FilterDataType> filterDataType : this.filterDataTypes)
{
columns.add(new SimpleColumn(filterDataType.getId(), filterDataType.getId(), String.class));
}
this.samplerData = new SamplerData<>(columns);
}
/**
* Underlying sampler data.
* @return SamplerData<G>; underlying sampler data
*/
public SamplerData getSamplerData()
{
return this.samplerData;
}
/**
* Whether this sampler has the given extended data type registered to it.
* @param extendedDataType ExtendedDataType<?,?,?,?>; extended data type
* @return whether this sampler has the given extended data type registered to it
*/
public boolean contains(final ExtendedDataType, ?, ?, ?> extendedDataType)
{
return this.extendedDataTypes.contains(extendedDataType);
}
/**
* Registers a space-time region. Data will be recorded across the entire length of a lane, but only during specified time
* periods.
* @param spaceTimeRegion SpaceTimeRegion; space-time region
* @throws IllegalStateException if data is not available from the requested start time
*/
public final void registerSpaceTimeRegion(final SpaceTimeRegion spaceTimeRegion)
{
Throw.whenNull(spaceTimeRegion, "SpaceTimeRegion may not be null.");
Time firstPossibleDataTime;
if (this.samplerData.contains(spaceTimeRegion.getLaneDirection()))
{
firstPossibleDataTime = this.samplerData.getTrajectoryGroup(spaceTimeRegion.getLaneDirection()).getStartTime();
}
else
{
firstPossibleDataTime = now();
}
Throw.when(spaceTimeRegion.getStartTime().lt(firstPossibleDataTime), IllegalStateException.class,
"Space time region with start time %s is defined while data is available from %s onwards.",
spaceTimeRegion.getStartTime(), firstPossibleDataTime);
if (this.samplerData.contains(spaceTimeRegion.getLaneDirection()))
{
this.endTimes.put(spaceTimeRegion.getLaneDirection(),
Time.max(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getEndTime()));
}
else
{
this.endTimes.put(spaceTimeRegion.getLaneDirection(), spaceTimeRegion.getEndTime());
scheduleStartRecording(spaceTimeRegion.getStartTime(), spaceTimeRegion.getLaneDirection());
}
scheduleStopRecording(this.endTimes.get(spaceTimeRegion.getLaneDirection()), spaceTimeRegion.getLaneDirection());
this.spaceTimeRegions.add(spaceTimeRegion);
}
/**
* Returns the current simulation time.
* @return current simulation time
*/
public abstract Time now();
/**
* Schedules the start of recording for a given lane-direction.
* @param time Time; time to start recording
* @param kpiLaneDirection KpiLaneDirection; lane-direction to start recording
*/
public abstract void scheduleStartRecording(Time time, KpiLane kpiLaneDirection);
/**
* Schedules the stop of recording for a given lane-direction.
* @param time Time; time to stop recording
* @param kpiLaneDirection KpiLaneDirection; lane-direction to stop recording
*/
public abstract void scheduleStopRecording(Time time, KpiLane kpiLaneDirection);
/**
* Start recording at the given time (which should be the current time) on the given lane direction.
* @param kpiLaneDirection KpiLaneDirection; lane direction
*/
public final void startRecording(final KpiLane kpiLaneDirection)
{
Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
if (this.samplerData.contains(kpiLaneDirection))
{
return;
}
this.samplerData.putTrajectoryGroup(kpiLaneDirection, new TrajectoryGroup<>(now(), kpiLaneDirection));
initRecording(kpiLaneDirection);
}
/**
* Adds listeners to start recording.
* @param kpiLaneDirection KpiLaneDirection; lane direction to initialize recording for
*/
public abstract void initRecording(KpiLane kpiLaneDirection);
/**
* Stop recording at given lane direction.
* @param kpiLaneDirection KpiLaneDirection; lane direction
*/
public final void stopRecording(final KpiLane kpiLaneDirection)
{
Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
if (!this.samplerData.contains(kpiLaneDirection) || this.endTimes.get(kpiLaneDirection).gt(now()))
{
return;
}
finalizeRecording(kpiLaneDirection);
}
/**
* Remove listeners to stop recording.
* @param kpiLaneDirection KpiLaneDirection; lane direction to finalize recording for
*/
public abstract void finalizeRecording(KpiLane kpiLaneDirection);
/**
* Creates a trajectory with the current snapshot of a GTU.
* @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
* @param position Length; position of the gtu on the lane
* @param speed Speed; speed of the gtu
* @param acceleration Acceleration; acceleration of the gtu
* @param time Time; current time
* @param gtu G; gtu
*/
public final void processGtuAddEvent(final KpiLane kpiLaneDirection, final Length position, final Speed speed,
final Acceleration acceleration, final Time time, final G gtu)
{
Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
Throw.whenNull(position, "Position may not be null.");
Throw.whenNull(speed, "Speed may not be null.");
Throw.whenNull(acceleration, "Acceleration may not be null.");
Throw.whenNull(time, "Time may not be null.");
Throw.whenNull(gtu, "GtuDataInterface may not be null.");
if (kpiLaneDirection.getLaneData().getLength().lt(position))
{
// ignore event if beyond lane length (may happen during lane change)
return;
}
String gtuId = gtu.getId();
Trajectory trajectory = new Trajectory<>(gtu, makeMetaData(gtu), this.extendedDataTypes, kpiLaneDirection);
if (!this.trajectoryPerGtu.containsKey(gtuId))
{
Map> map = new LinkedHashMap<>();
this.trajectoryPerGtu.put(gtuId, map);
}
this.trajectoryPerGtu.get(gtuId).put(kpiLaneDirection, trajectory);
this.samplerData.getTrajectoryGroup(kpiLaneDirection).addTrajectory(trajectory);
processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
}
/**
* Adds a new snapshot of a GTU to its recording trajectory, if recorded. This method may be invoked on GTU that are not
* being recorded; the event will then be ignored.
* @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
* @param position Length; position of the gtu on the lane
* @param speed Speed; speed of the gtu
* @param acceleration Acceleration; acceleration of the gtu
* @param time Time; current time
* @param gtu G; gtu
*/
public final void processGtuMoveEvent(final KpiLane kpiLaneDirection, final Length position, final Speed speed,
final Acceleration acceleration, final Time time, final G gtu)
{
Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
Throw.whenNull(position, "Position may not be null.");
Throw.whenNull(speed, "Speed may not be null.");
Throw.whenNull(acceleration, "Acceleration may not be null.");
Throw.whenNull(time, "Time may not be null.");
Throw.whenNull(gtu, "GtuDataInterface may not be null.");
String gtuId = gtu.getId();
if (this.trajectoryPerGtu.containsKey(gtuId) && this.trajectoryPerGtu.get(gtuId).containsKey(kpiLaneDirection))
{
this.trajectoryPerGtu.get(gtuId).get(kpiLaneDirection).add(position, speed, acceleration, time, gtu);
}
}
/**
* Finalizes a trajectory with the current snapshot of a GTU.
* @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
* @param position Length; position of the gtu on the lane
* @param speed Speed; speed of the gtu
* @param acceleration Acceleration; acceleration of the gtu
* @param time Time; current time
* @param gtu G; gtu
*/
public final void processGtuRemoveEvent(final KpiLane kpiLaneDirection, final Length position, final Speed speed,
final Acceleration acceleration, final Time time, final G gtu)
{
processGtuMoveEvent(kpiLaneDirection, position, speed, acceleration, time, gtu);
processGtuRemoveEvent(kpiLaneDirection, gtu);
}
/**
* Finalizes a trajectory.
* @param kpiLaneDirection KpiLaneDirection; lane direction the gtu is at
* @param gtu G; gtu
*/
public final void processGtuRemoveEvent(final KpiLane kpiLaneDirection, final G gtu)
{
Throw.whenNull(kpiLaneDirection, "KpiLaneDirection may not be null.");
Throw.whenNull(gtu, "GtuDataInterface may not be null.");
String gtuId = gtu.getId();
if (this.trajectoryPerGtu.containsKey(gtuId))
{
this.trajectoryPerGtu.get(gtuId).remove(kpiLaneDirection);
if (this.trajectoryPerGtu.get(gtuId).isEmpty())
{
this.trajectoryPerGtu.remove(gtuId);
}
}
}
/**
* @param gtu G; gtu to return meta data for
* @param underlying type of a meta data type
* @return meta data for the given gtu
*/
@SuppressWarnings("unchecked")
private MetaData makeMetaData(final G gtu)
{
MetaData metaData = new MetaData();
for (FilterDataType> metaDataType : this.filterDataTypes)
{
T value = (T) metaDataType.getValue(gtu);
if (value != null)
{
metaData.put((FilterDataType) metaDataType, value);
}
}
return metaData;
}
// TODO: hashCode / equals
}