package org.opentrafficsim.road.network.sampling; import java.rmi.RemoteException; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import org.djunits.unit.DurationUnit; import org.djunits.value.vdouble.scalar.Acceleration; import org.djunits.value.vdouble.scalar.Duration; import org.djunits.value.vdouble.scalar.Frequency; import org.djunits.value.vdouble.scalar.Length; import org.djunits.value.vdouble.scalar.Speed; import org.djunits.value.vdouble.scalar.Time; import org.djutils.event.EventInterface; import org.djutils.event.EventListenerInterface; import org.djutils.event.TimedEvent; import org.djutils.event.ref.ReferenceType; import org.djutils.exceptions.Throw; import org.opentrafficsim.core.gtu.GTUDirectionality; import org.opentrafficsim.core.gtu.GTUException; import org.opentrafficsim.core.gtu.RelativePosition; import org.opentrafficsim.kpi.sampling.KpiGtuDirectionality; import org.opentrafficsim.kpi.sampling.KpiLaneDirection; import org.opentrafficsim.kpi.sampling.Sampler; import org.opentrafficsim.kpi.sampling.data.ExtendedDataType; import org.opentrafficsim.kpi.sampling.meta.FilterDataType; import org.opentrafficsim.road.gtu.lane.LaneBasedGTU; import org.opentrafficsim.road.network.OTSRoadNetwork; import org.opentrafficsim.road.network.lane.CrossSectionLink; import org.opentrafficsim.road.network.lane.Lane; import org.opentrafficsim.road.network.lane.LaneDirection; import nl.tudelft.simulation.dsol.SimRuntimeException; import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface; import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit; import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface; /** * Implementation of kpi sampler for OTS. *

* Copyright (c) 2013-2022 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 12 okt. 2016
* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ public class RoadSampler extends Sampler implements EventListenerInterface { /** */ private static final long serialVersionUID = 20200228L; /** Simulator. */ private final DEVSSimulatorInterface.TimeDoubleUnit simulator; /** Network. */ private final OTSRoadNetwork network; /** Sampling interval. */ private final Duration samplingInterval; /** Registration of sampling events of each GTU per lane, if interval based. */ private final Map>> eventPerGtu = new LinkedHashMap<>(); /** List of lane the sampler is listening to for each GTU. Usually 1, could be 2 during a trajectory transition. */ private final Map> listenersPerGtu = new LinkedHashMap<>(); /** * Constructor which uses the operational plan updates of GTU's as sampling interval. * @param network OTSRoadNetwork; the network * @throws NullPointerException if the simulator is {@code null} */ public RoadSampler(final OTSRoadNetwork network) { this(new LinkedHashSet<>(), new LinkedHashSet<>(), network); } /** * Constructor which uses the operational plan updates of GTU's as sampling interval. * @param extendedDataTypes Set<ExtendedDataType<?, ?, ?, GtuData>>; extended data types * @param filterDataTypes Set<FilterDataType<?>>; filter data types * @param network OTSRoadNetwork; the network * @throws NullPointerException if the simulator is {@code null} */ public RoadSampler(final Set> extendedDataTypes, final Set> filterDataTypes, final OTSRoadNetwork network) { super(extendedDataTypes, filterDataTypes); Throw.whenNull(network, "Network may not be null."); this.network = network; this.simulator = network.getSimulator(); this.samplingInterval = null; } /** * Constructor which uses the given frequency to determine the sampling interval. * @param network OTSRoadNetwork; the network * @param frequency Frequency; sampling frequency * @throws NullPointerException if an input is {@code null} * @throws IllegalArgumentException if frequency is negative or zero */ public RoadSampler(final OTSRoadNetwork network, final Frequency frequency) { this(new LinkedHashSet<>(), new LinkedHashSet<>(), network, frequency); } /** * Constructor which uses the given frequency to determine the sampling interval. * @param extendedDataTypes Set<ExtendedDataType<?, ?, ?, GGtuData>>; extended data types * @param filterDataTypes Set<FilterDataType<?>>; filter data types * @param network OTSRoadNetwork; the network * @param frequency Frequency; sampling frequency * @throws NullPointerException if an input is {@code null} * @throws IllegalArgumentException if frequency is negative or zero */ public RoadSampler(final Set> extendedDataTypes, final Set> filterDataTypes, final OTSRoadNetwork network, final Frequency frequency) { super(extendedDataTypes, filterDataTypes); Throw.whenNull(network, "Network may not be null."); Throw.whenNull(frequency, "Frequency may not be null."); Throw.when(frequency.le(Frequency.ZERO), IllegalArgumentException.class, "Negative or zero sampling frequency is not permitted."); this.network = network; this.simulator = network.getSimulator(); this.samplingInterval = new Duration(1.0 / frequency.si, DurationUnit.SI); } /** {@inheritDoc} */ @Override public final Time now() { return this.simulator.getSimulatorTime(); } /** {@inheritDoc} */ @Override public final void scheduleStartRecording(final Time time, final KpiLaneDirection kpiLaneDirection) { try { this.simulator.scheduleEventAbs(time, this, this, "startRecording", new Object[] { kpiLaneDirection }); } catch (SimRuntimeException exception) { throw new RuntimeException("Cannot start recording.", exception); } } /** {@inheritDoc} */ @Override public final void scheduleStopRecording(final Time time, final KpiLaneDirection kpiLaneDirection) { try { this.simulator.scheduleEventAbs(time, this, this, "stopRecording", new Object[] { kpiLaneDirection }); } catch (SimRuntimeException exception) { throw new RuntimeException("Cannot stop recording.", exception); } } /** {@inheritDoc} */ @Override public final void initRecording(final KpiLaneDirection kpiLaneDirection) { Lane lane = ((LaneData) kpiLaneDirection.getLaneData()).getLane(); lane.addListener(this, Lane.GTU_ADD_EVENT, ReferenceType.WEAK); lane.addListener(this, Lane.GTU_REMOVE_EVENT, ReferenceType.WEAK); int count = 1; for (LaneBasedGTU gtu : lane.getGtuList()) { try { if (sameDirection(kpiLaneDirection.getKpiDirection(), gtu.getDirection(lane))) { // Payload: Object[] {String gtuId, gtu, int count_after_addition} //notify(new TimedEvent<>(Lane.GTU_ADD_EVENT, lane, new Object[] { gtu.getId(), gtu, count }, // gtu.getSimulator().getSimulatorTime())); // Payload: Object[] {String gtuId, int count_after_addition} notify(new TimedEvent<>(Lane.GTU_ADD_EVENT, lane, new Object[] { gtu.getId(), count }, gtu.getSimulator().getSimulatorTime())); } count++; } catch (RemoteException | GTUException exception) { throw new RuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception); } } } /** {@inheritDoc} */ @Override public final void finalizeRecording(final KpiLaneDirection kpiLaneDirection) { Lane lane = ((LaneData) kpiLaneDirection.getLaneData()).getLane(); lane.removeListener(this, Lane.GTU_ADD_EVENT); lane.removeListener(this, Lane.GTU_REMOVE_EVENT); // Lane lane = ((LaneData) kpiLaneDirection.getLaneData()).getLane(); // int count = 0; // List currentGtus = new ArrayList<>(); // try // { // for (LaneBasedGTU gtu : lane.getGtuList()) // { // DirectedLanePosition dlp = gtu.getReferencePosition(); // if (dlp.getLane().equals(lane) && sameDirection(kpiLaneDirection.getKpiDirection(), dlp.getGtuDirection())) // { // currentGtus.add(gtu); // count++; // } // } // for (LaneBasedGTU gtu : currentGtus) // { // // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_removal, Length position} // notify(new TimedEvent<>(Lane.GTU_REMOVE_EVENT, lane, new Object[] { gtu.getId(), gtu, count }, // gtu.getSimulator().getSimulatorTime())); // count--; // } // } // catch (RemoteException | GTUException exception) // { // throw new RuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception); // } } /** * Compares a {@link KpiGtuDirectionality} and a {@link GTUDirectionality}. * @param kpiGtuDirectionality KpiGtuDirectionality; kpi gtu direction * @param gtuDirectionality GTUDirectionality; gtu direction * @return whether both are in the same direction */ private boolean sameDirection(final KpiGtuDirectionality kpiGtuDirectionality, final GTUDirectionality gtuDirectionality) { if (kpiGtuDirectionality.equals(KpiGtuDirectionality.DIR_PLUS)) { return gtuDirectionality.equals(GTUDirectionality.DIR_PLUS); } return gtuDirectionality.equals(GTUDirectionality.DIR_MINUS); } /** {@inheritDoc} */ @Override public final void notify(final EventInterface event) throws RemoteException { if (event.getType().equals(LaneBasedGTU.LANEBASED_MOVE_EVENT)) { // Payload: [String gtuId, PositionVector currentPosition, Direction currentDirection, Speed speed, Acceleration // acceleration, TurnIndicatorStatus turnIndicatorStatus, Length odometer, Link id of referenceLane, Lane id of // referenceLane, Length positionOnReferenceLane, GTUDirectionality direction] Object[] payload = (Object[]) event.getContent(); CrossSectionLink link = (CrossSectionLink) this.network.getLink(payload[7].toString()); Lane lane = (Lane) link.getCrossSectionElement(payload[8].toString()); LaneBasedGTU gtu = (LaneBasedGTU) this.network.getGTU(payload[0].toString()); KpiLaneDirection laneDirection = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS); processGtuMoveEvent(laneDirection, (Length) payload[9], (Speed) payload[3], (Acceleration) payload[4], now(), new GtuData(gtu)); } else if (event.getType().equals(Lane.GTU_ADD_EVENT)) { // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_addition} // Assumes that the lane itself is the sourceId Lane lane = (Lane) event.getSourceId(); // TODO GTUDirectionality from Lane.GTU_ADD_EVENT KpiLaneDirection laneDirection = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS); if (!getSamplerData().contains(laneDirection)) { // we are not sampling this LaneDirection return; } Object[] payload = (Object[]) event.getContent(); // LaneBasedGTU gtu = (LaneBasedGTU) payload[1]; LaneBasedGTU gtu = (LaneBasedGTU) this.network.getGTU((String) payload[0]); Length position; try { // TODO Length from Lane.GTU_ADD_EVENT position = gtu.position(lane, RelativePosition.REFERENCE_POSITION); } catch (GTUException exception) { throw new RuntimeException(exception); } Speed speed = gtu.getSpeed(); Acceleration acceleration = gtu.getAcceleration(); processGtuAddEvent(laneDirection, position, speed, acceleration, now(), new GtuData(gtu)); LaneDirection lDirection = new LaneDirection(lane, GTUDirectionality.DIR_PLUS); if (isIntervalBased()) { scheduleSamplingEvent(gtu, lDirection); } else { if (!this.listenersPerGtu.containsKey(gtu.getId())) { this.listenersPerGtu.put(gtu.getId(), new LinkedHashSet<>()); } this.listenersPerGtu.get(gtu.getId()).add(lDirection); gtu.addListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT, ReferenceType.WEAK); } } else if (event.getType().equals(Lane.GTU_REMOVE_EVENT)) { // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_removal, Length position} // Assumes that the lane itself is the sourceId Lane lane = (Lane) event.getSourceId(); // TODO GTUDirectionality from Lane.GTU_REMOVE_EVENT KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS); Object[] payload = (Object[]) event.getContent(); LaneBasedGTU gtu = (LaneBasedGTU) payload[1]; Length position = (Length) payload[3]; Speed speed = gtu.getSpeed(); Acceleration acceleration = gtu.getAcceleration(); processGtuRemoveEvent(kpiLaneDirection, position, speed, acceleration, now(), new GtuData(gtu)); LaneDirection lDirection = new LaneDirection(lane, GTUDirectionality.DIR_PLUS); if (isIntervalBased()) { String gtuId = (String) payload[0]; if (this.eventPerGtu.get(gtuId) != null) { if (this.eventPerGtu.get(gtuId).containsKey(lDirection)) { this.simulator.cancelEvent(this.eventPerGtu.get(gtuId).get(lDirection)); } this.eventPerGtu.get(gtuId).remove(lDirection); if (this.eventPerGtu.get(gtuId).isEmpty()) { this.eventPerGtu.remove(gtuId); } } } else { // Should not remove if just added on other lane this.listenersPerGtu.get(gtu.getId()).remove(lDirection); if (this.listenersPerGtu.get(gtu.getId()).isEmpty()) { this.listenersPerGtu.remove(gtu.getId()); gtu.removeListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT); } } } } /** * @return whether sampling is interval based */ private boolean isIntervalBased() { return this.samplingInterval != null; } /** * Schedules a sampling event for the given gtu on the given lane for the sampling interval from the current time. * @param gtu LaneBasedGTU; gtu to sample * @param laneDirection LaneDirection; lane direction where the gtu is at */ private void scheduleSamplingEvent(final LaneBasedGTU gtu, final LaneDirection laneDirection) { SimEventInterface simEvent; try { // this.simulator.scheduleEvent(simEvent); simEvent = this.simulator.scheduleEventRel(this.samplingInterval, this, this, "notifySample", new Object[] { gtu, laneDirection }); } catch (SimRuntimeException exception) { // should not happen with getSimulatorTime.add() throw new RuntimeException("Scheduling sampling in the past.", exception); } String gtuId = gtu.getId(); if (!this.eventPerGtu.containsKey(gtuId)) { Map> map = new LinkedHashMap<>(); this.eventPerGtu.put(gtuId, map); } this.eventPerGtu.get(gtuId).put(laneDirection, simEvent); } /** * Samples a gtu and schedules the next sampling event. * @param gtu LaneBasedGTU; gtu to sample * @param laneDirection LaneDirection; lane direction where the gtu is at */ public final void notifySample(final LaneBasedGTU gtu, final LaneDirection laneDirection) { KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(laneDirection.getLane()), laneDirection.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS); try { this.processGtuMoveEvent(kpiLaneDirection, gtu.position(laneDirection.getLane(), RelativePosition.REFERENCE_POSITION), gtu.getSpeed(), gtu.getAcceleration(), now(), new GtuData(gtu)); } catch (GTUException exception) { throw new RuntimeException("Requesting position on lane, but the GTU is not on the lane.", exception); } scheduleSamplingEvent(gtu, laneDirection); } /** {@inheritDoc} */ @Override public final int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((this.eventPerGtu == null) ? 0 : this.eventPerGtu.hashCode()); result = prime * result + ((this.samplingInterval == null) ? 0 : this.samplingInterval.hashCode()); result = prime * result + ((this.simulator == null) ? 0 : this.simulator.hashCode()); return result; } /** {@inheritDoc} */ @Override public final boolean equals(final Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (getClass() != obj.getClass()) { return false; } RoadSampler other = (RoadSampler) obj; if (this.eventPerGtu == null) { if (other.eventPerGtu != null) { return false; } } else if (!this.eventPerGtu.equals(other.eventPerGtu)) { return false; } if (this.samplingInterval == null) { if (other.samplingInterval != null) { return false; } } else if (!this.samplingInterval.equals(other.samplingInterval)) { return false; } if (this.simulator == null) { if (other.simulator != null) { return false; } } else if (!this.simulator.equals(other.simulator)) { return false; } return true; } /** {@inheritDoc} */ @Override public final String toString() { return "RoadSampler [samplingInterval=" + this.samplingInterval + "]"; // do not use "this.eventPerGtu", it creates circular toString and hence stack overflow } /** * Returns a factory to create a sampler. * @param network OTSRoadNetwork; network * @return Factory; factory to create a sampler */ public static Factory build(final OTSRoadNetwork network) { return new Factory(network); } /** Factory for {@code RoadSampler}. */ public static final class Factory { /** Simulator. */ private final OTSRoadNetwork network; /** Registration of included extended data types. */ private final Set> extendedDataTypes = new LinkedHashSet<>(); /** Set of registered filter data types. */ private final Set> filterDataTypes = new LinkedHashSet<>(); /** Frequency. */ private Frequency freq; /** * Constructor. * @param network OTSRoadNetwork; network */ Factory(final OTSRoadNetwork network) { this.network = network; } /** * Register extended data type. * @param extendedDataType ExtendedDataType<?, ?, ?, GtuData>; extended data type * @return Factory; this factory */ public Factory registerExtendedDataType(final ExtendedDataType extendedDataType) { Throw.whenNull(extendedDataType, "Extended data type may not be null."); this.extendedDataTypes.add(extendedDataType); return this; } /** * Register filter data type. * @param filterDataType FilterDataType<?>; filter data type * @return Factory; this factory */ public Factory registerFilterDataType(final FilterDataType filterDataType) { Throw.whenNull(filterDataType, "Filter data type may not be null."); this.filterDataTypes.add(filterDataType); return this; } /** * Sets the frequency. If no frequency is set, a sampler is created that records on move events of GTU's. * @param frequency Frequency; frequency * @return Factory; this factory */ public Factory setFrequency(final Frequency frequency) { this.freq = frequency; return this; } /** * Create sampler. * @return RoadSampler; sampler */ public RoadSampler create() { return this.freq == null ? new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network) : new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network, this.freq); } } }