LoopDetector.java
- package org.opentrafficsim.road.network.lane.object.detector;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.LinkedHashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.NoSuchElementException;
- import java.util.Set;
- import java.util.TreeSet;
- import java.util.stream.Collectors;
- import java.util.stream.IntStream;
- import org.djunits.unit.SpeedUnit;
- 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.data.Column;
- import org.djutils.data.Row;
- import org.djutils.data.Table;
- import org.djutils.event.EventType;
- import org.djutils.exceptions.Throw;
- import org.djutils.exceptions.Try;
- import org.djutils.metadata.MetaData;
- import org.djutils.metadata.ObjectDescriptor;
- import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
- import org.opentrafficsim.core.gtu.RelativePosition;
- import org.opentrafficsim.core.network.NetworkException;
- import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
- import org.opentrafficsim.road.network.RoadNetwork;
- import org.opentrafficsim.road.network.lane.Lane;
- /**
- * Detector, measuring a dynamic set of measurements typical for single- or dual-loop detectors. A subsidiary detector is placed
- * at a position downstream based on the detector length, that triggers when the rear leaves the detector.
- * <p>
- * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
- * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
- * </p>
- * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
- * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
- * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
- */
- public class LoopDetector extends LaneDetector
- {
- /** */
- private static final long serialVersionUID = 20180312L;
- /** Trigger event. Payload: [Id of LaneBasedGtu]. */
- public static final EventType LOOP_DETECTOR_TRIGGERED =
- new EventType("LOOPDETECTOR.TRIGGER", new MetaData("Dual loop detector triggered", "Dual loop detector triggered",
- new ObjectDescriptor[] {new ObjectDescriptor("Id of GTU", "Id of GTU", String.class)}));
- /** Aggregation event. Payload: [Frequency, measurement...]/ */
- public static final EventType LOOP_DETECTOR_AGGREGATE = new EventType("LOOPDETECTOR.AGGREGATE", MetaData.NO_META_DATA);
- /** Mean speed measurement. */
- public static final LoopDetectorMeasurement<Double, Speed> MEAN_SPEED = new LoopDetectorMeasurement<Double, Speed>()
- {
- @Override
- public Double identity()
- {
- return 0.0;
- }
- @Override
- public Double accumulateEntry(final Double cumulative, final LaneBasedGtu gtu, final LoopDetector loopDetector)
- {
- return cumulative + gtu.getSpeed().si;
- }
- @Override
- public Double accumulateExit(final Double cumulative, final LaneBasedGtu gtu, final LoopDetector loopDetector)
- {
- return cumulative;
- }
- @Override
- public boolean isPeriodic()
- {
- return true;
- }
- @Override
- public Speed aggregate(final Double cumulative, final int gtuCount, final Duration aggregation)
- {
- return new Speed(3.6 * cumulative / gtuCount, SpeedUnit.KM_PER_HOUR);
- }
- @Override
- public String getName()
- {
- return "v";
- }
- @Override
- public String toString()
- {
- return getName();
- }
- @Override
- public String getDescription()
- {
- return "mean speed";
- }
- @Override
- public String getUnit()
- {
- return "km/h";
- }
- @Override
- public Class<Speed> getValueType()
- {
- return Speed.class;
- }
- };
- /** Harmonic mean speed measurement. */
- public static final LoopDetectorMeasurement<Double, Speed> HARMONIC_MEAN_SPEED =
- new LoopDetectorMeasurement<Double, Speed>()
- {
- @Override
- public Double identity()
- {
- return 0.0;
- }
- @Override
- public Double accumulateEntry(final Double cumulative, final LaneBasedGtu gtu, final LoopDetector loopDetector)
- {
- return cumulative + (1.0 / gtu.getSpeed().si);
- }
- @Override
- public Double accumulateExit(final Double cumulative, final LaneBasedGtu gtu, final LoopDetector loopDetector)
- {
- return cumulative;
- }
- @Override
- public boolean isPeriodic()
- {
- return true;
- }
- @Override
- public Speed aggregate(final Double cumulative, final int gtuCount, final Duration aggregation)
- {
- return new Speed(3.6 * gtuCount / cumulative, SpeedUnit.KM_PER_HOUR);
- }
- @Override
- public String getName()
- {
- return "vHarm";
- }
- @Override
- public String toString()
- {
- return getName();
- }
- @Override
- public String getDescription()
- {
- return "harmonic mean speed";
- }
- @Override
- public String getUnit()
- {
- return "km/h";
- }
- @Override
- public Class<Speed> getValueType()
- {
- return Speed.class;
- }
- };
- /** Occupancy measurement. */
- public static final LoopDetectorMeasurement<Double, Double> OCCUPANCY = new LoopDetectorMeasurement<Double, Double>()
- {
- /** Time the last GTU triggered the upstream detector. */
- private double lastEntry = Double.NaN;
- /** Id of last GTU that triggered the upstream detector. */
- private String lastId;
- @Override
- public Double identity()
- {
- return 0.0;
- }
- @Override
- public Double accumulateEntry(final Double cumulative, final LaneBasedGtu gtu, final LoopDetector loopDetector)
- {
- this.lastEntry = gtu.getSimulator().getSimulatorTime().si;
- this.lastId = gtu.getId();
- return cumulative;
- }
- @Override
- public Double accumulateExit(final Double cumulative, final LaneBasedGtu gtu, final LoopDetector loopDetector)
- {
- if (!gtu.getId().equals(this.lastId))
- {
- // vehicle entered the lane along the length of the detector; we can skip as occupancy detector are not long
- return cumulative;
- }
- this.lastId = null;
- return cumulative + (gtu.getSimulator().getSimulatorTime().si - this.lastEntry);
- }
- @Override
- public boolean isPeriodic()
- {
- return true;
- }
- @Override
- public Double aggregate(final Double cumulative, final int gtuCount, final Duration aggregation)
- {
- return cumulative / aggregation.si;
- }
- @Override
- public String getName()
- {
- return "occupancy";
- }
- @Override
- public String toString()
- {
- return getName();
- }
- @Override
- public String getDescription()
- {
- return "occupancy as fraction of time";
- }
- @Override
- public Class<Double> getValueType()
- {
- return Double.class;
- }
- };
- /** Passages measurement. */
- public static final LoopDetectorMeasurement<List<Duration>, List<Duration>> PASSAGES =
- new LoopDetectorMeasurement<List<Duration>, List<Duration>>()
- {
- @Override
- public List<Duration> identity()
- {
- return new ArrayList<>();
- }
- @Override
- public List<Duration> accumulateEntry(final List<Duration> cumulative, final LaneBasedGtu gtu,
- final LoopDetector loopDetector)
- {
- cumulative.add(gtu.getSimulator().getSimulatorTime());
- return cumulative;
- }
- @Override
- public List<Duration> accumulateExit(final List<Duration> cumulative, final LaneBasedGtu gtu,
- final LoopDetector loopDetector)
- {
- return cumulative;
- }
- @Override
- public boolean isPeriodic()
- {
- return false;
- }
- @Override
- public List<Duration> aggregate(final List<Duration> cumulative, final int gtuCount, final Duration aggregation)
- {
- return cumulative;
- }
- @Override
- public String getName()
- {
- return "passage times";
- }
- @Override
- public String toString()
- {
- return getName();
- }
- @Override
- public String getDescription()
- {
- return "list of vehicle passage time";
- }
- @Override
- public String getUnit()
- {
- return "s";
- }
- @Override
- public Class<Duration> getValueType()
- {
- return Duration.class;
- }
- };
- /** Aggregation time. */
- private final Duration aggregation;
- /** Aggregation time of current period, may differ due to offset at start. */
- private Duration currentAggregation;
- /** First aggregation. */
- private final Time firstAggregation;
- /** Flow per aggregation period. */
- private final List<Frequency> flow = new ArrayList<>();
- /** Measurements per aggregation period. */
- private final Map<LoopDetectorMeasurement<?, ?>, List<?>> periodicDataMap = new LinkedHashMap<>();
- /** Detector length. */
- private final Length length;
- /** Period number. */
- private int period = 1;
- /** Count in current period. */
- private int gtuCountCurrentPeriod = 0;
- /** Count overall. */
- private int overallGtuCount = 0;
- /** Current cumulative measurements. */
- private final Map<LoopDetectorMeasurement<?, ?>, Object> currentCumulativeDataMap = new LinkedHashMap<>();
- /**
- * Constructor for regular Dutch dual-loop detectors measuring flow and mean speed aggregated over 60s.
- * @param id String; detector id
- * @param lane Lane; lane
- * @param longitudinalPosition Length; position
- * @param simulator OtsSimulatorInterface; simulator
- * @param detectorType DetectorType; detector type.
- * @throws NetworkException on network exception
- */
- public LoopDetector(final String id, final Lane lane, final Length longitudinalPosition, final DetectorType detectorType,
- final OtsSimulatorInterface simulator) throws NetworkException
- {
- // Note: length not important for flow and mean speed
- this(id, lane, longitudinalPosition, Length.ZERO, detectorType, simulator, Time.instantiateSI(60.0),
- Duration.instantiateSI(60.0), MEAN_SPEED);
- }
- /**
- * Constructor.
- * @param id String; detector id
- * @param lane Lane; lane
- * @param longitudinalPosition Length; position
- * @param length Length; length
- * @param simulator OtsSimulatorInterface; simulator
- * @param firstAggregation Time; time of first aggregation
- * @param aggregation Duration; aggregation period
- * @param measurements DetectorMeasurement<?, ?>...; measurements to obtain
- * @param detectorType DetectorType; detector type.
- * @throws NetworkException on network exception
- */
- public LoopDetector(final String id, final Lane lane, final Length longitudinalPosition, final Length length,
- final DetectorType detectorType, final OtsSimulatorInterface simulator, final Time firstAggregation,
- final Duration aggregation, final LoopDetectorMeasurement<?, ?>... measurements) throws NetworkException
- {
- super(id, lane, longitudinalPosition, RelativePosition.FRONT, simulator, detectorType);
- Throw.when(aggregation.si <= 0.0, IllegalArgumentException.class, "Aggregation time should be positive.");
- this.length = length;
- this.currentAggregation = Duration.instantiateSI(firstAggregation.si);
- this.aggregation = aggregation;
- this.firstAggregation = firstAggregation;
- Try.execute(() -> simulator.scheduleEventAbsTime(firstAggregation, this, "aggregate", null), "");
- for (LoopDetectorMeasurement<?, ?> measurement : measurements)
- {
- this.currentCumulativeDataMap.put(measurement, measurement.identity());
- if (measurement.isPeriodic())
- {
- this.periodicDataMap.put(measurement, new ArrayList<>());
- }
- }
- // rear detector
- /** Abstract detector. */
- class RearDetector extends LaneDetector
- {
- /** */
- private static final long serialVersionUID = 20180315L;
- /**
- * Constructor.
- * @param idRear String; id
- * @param laneRear Lane; lane
- * @param longitudinalPositionRear Length; position
- * @param simulatorRear OtsSimulatorInterface; simulator
- * @param detectorType DetectorType; detector type.
- * @throws NetworkException on network exception
- */
- @SuppressWarnings("synthetic-access")
- RearDetector(final String idRear, final Lane laneRear, final Length longitudinalPositionRear,
- final OtsSimulatorInterface simulatorRear, final DetectorType detectorType) throws NetworkException
- {
- super(idRear, laneRear, longitudinalPositionRear, RelativePosition.REAR, simulatorRear, detectorType);
- }
- /** {@inheritDoc} */
- @SuppressWarnings("synthetic-access")
- @Override
- protected void triggerResponse(final LaneBasedGtu gtu)
- {
- for (LoopDetectorMeasurement<?, ?> measurement : LoopDetector.this.currentCumulativeDataMap.keySet())
- {
- accumulate(measurement, gtu, false);
- }
- }
- }
- Length position = longitudinalPosition.plus(length);
- Throw.when(position.gt(lane.getLength()), IllegalStateException.class,
- "A Detector can not be placed at a lane boundary");
- new RearDetector(id + "_rear", lane, position, simulator, detectorType);
- }
- /**
- * Returns the detector length.
- * @return Length; the detector length
- */
- public Length getLength()
- {
- return this.length;
- }
- /** {@inheritDoc} */
- @Override
- protected void triggerResponse(final LaneBasedGtu gtu)
- {
- this.gtuCountCurrentPeriod++;
- this.overallGtuCount++;
- for (LoopDetectorMeasurement<?, ?> measurement : this.currentCumulativeDataMap.keySet())
- {
- accumulate(measurement, gtu, true);
- }
- this.fireTimedEvent(LOOP_DETECTOR_TRIGGERED, new Object[] {gtu.getId()}, getSimulator().getSimulatorTime());
- }
- /**
- * Accumulates a measurement.
- * @param measurement DetectorMeasurement<C, ?>; measurement to accumulate
- * @param gtu LaneBasedGtu; gtu
- * @param front boolean; triggered by front entering (or rear leaving when false)
- * @param <C> accumulated type
- */
- @SuppressWarnings("unchecked")
- <C> void accumulate(final LoopDetectorMeasurement<C, ?> measurement, final LaneBasedGtu gtu, final boolean front)
- {
- if (front)
- {
- this.currentCumulativeDataMap.put(measurement,
- measurement.accumulateEntry((C) this.currentCumulativeDataMap.get(measurement), gtu, this));
- }
- else
- {
- this.currentCumulativeDataMap.put(measurement,
- measurement.accumulateExit((C) this.currentCumulativeDataMap.get(measurement), gtu, this));
- }
- }
- /**
- * Aggregation.
- */
- private void aggregate()
- {
- Frequency frequency = Frequency.instantiateSI(this.gtuCountCurrentPeriod / this.currentAggregation.si);
- this.flow.add(frequency);
- for (LoopDetectorMeasurement<?, ?> measurement : this.periodicDataMap.keySet())
- {
- aggregate(measurement, this.gtuCountCurrentPeriod, this.currentAggregation);
- this.currentCumulativeDataMap.put(measurement, measurement.identity());
- }
- this.gtuCountCurrentPeriod = 0;
- this.currentAggregation = this.aggregation; // after first possibly irregular period, all periods regular
- if (!getListenerReferences(LOOP_DETECTOR_AGGREGATE).isEmpty())
- {
- Object[] data = new Object[this.periodicDataMap.size() + 1];
- data[0] = frequency;
- int i = 1;
- for (LoopDetectorMeasurement<?, ?> measurement : this.periodicDataMap.keySet())
- {
- List<?> list = this.periodicDataMap.get(measurement);
- data[i] = list.get(list.size() - 1);
- i++;
- }
- this.fireTimedEvent(LOOP_DETECTOR_AGGREGATE, data, getSimulator().getSimulatorTime());
- }
- Time time = Time.instantiateSI(this.firstAggregation.si + this.aggregation.si * this.period++);
- Try.execute(() -> getSimulator().scheduleEventAbsTime(time, this, "aggregate", null), "");
- }
- /**
- * Returns whether the detector has aggregated data available.
- * @return boolean; whether the detector has aggregated data available
- */
- public boolean hasLastValue()
- {
- return !this.flow.isEmpty();
- }
- /**
- * Returns the last flow.
- * @return last flow
- */
- public Frequency getLastFlow()
- {
- return this.flow.get(this.flow.size() - 1);
- }
- /**
- * Returns the last value of the detector measurement.
- * @param detectorMeasurement DetectorMeasurement<?,A>; detector measurement
- * @return last value of the detector measurement
- * @param <A> aggregate value type of the detector measurement
- */
- public <A> A getLastValue(final LoopDetectorMeasurement<?, A> detectorMeasurement)
- {
- @SuppressWarnings("unchecked")
- List<A> list = (List<A>) this.periodicDataMap.get(detectorMeasurement);
- return list.get(list.size() - 1);
- }
- /**
- * Aggregates a periodic measurement.
- * @param measurement DetectorMeasurement<C, A>; measurement to aggregate
- * @param gtuCount int; number of GTUs
- * @param agg Duration; aggregation period
- * @param <C> accumulated type
- * @param <A> aggregated type
- */
- @SuppressWarnings("unchecked")
- private <C, A> void aggregate(final LoopDetectorMeasurement<C, A> measurement, final int gtuCount, final Duration agg)
- {
- ((List<A>) this.periodicDataMap.get(measurement)).add(getAggregateValue(measurement, gtuCount, agg));
- }
- /**
- * Returns the aggregated value of the measurement.
- * @param measurement DetectorMeasurement<C, A>; measurement to aggregate
- * @param gtuCount int; number of GTUs
- * @param agg Duration; aggregation period
- * @return A; aggregated value of the measurement
- * @param <C> accumulated type
- * @param <A> aggregated type
- */
- @SuppressWarnings("unchecked")
- private <C, A> A getAggregateValue(final LoopDetectorMeasurement<C, A> measurement, final int gtuCount, final Duration agg)
- {
- return measurement.aggregate((C) this.currentCumulativeDataMap.get(measurement), gtuCount, agg);
- }
- /**
- * Returns a map of non-periodic measurements, mapping measurement type and the data.
- * @return Map<DetectorMeasurement, Object>; map of non-periodic measurements
- */
- private Map<LoopDetectorMeasurement<?, ?>, Object> getNonPeriodicMeasurements()
- {
- Map<LoopDetectorMeasurement<?, ?>, Object> map = new LinkedHashMap<>();
- for (LoopDetectorMeasurement<?, ?> measurement : this.currentCumulativeDataMap.keySet())
- {
- if (!measurement.isPeriodic())
- {
- map.put(measurement, getAggregateValue(measurement, this.overallGtuCount,
- this.getSimulator().getSimulatorAbsTime().minus(Time.ZERO)));
- }
- }
- return map;
- }
- /**
- * Returns a Table with loop detector positions.
- * @param network RoadNetwork; network from which all detectors are found.
- * @return Table; with loop detector positions.
- */
- public static Table asTablePositions(final RoadNetwork network)
- {
- Set<LoopDetector> detectors = getLoopDetectors(network);
- Collection<Column<?>> columns = new LinkedHashSet<>();
- columns.add(new Column<>("id", "detector id", String.class, null));
- columns.add(new Column<>("laneId", "lane id", String.class, null));
- columns.add(new Column<>("linkId", "link id", String.class, null));
- columns.add(new Column<>("position", "detector position on the lane", Length.class, "m"));
- return new Table("detectors", "list of all loop-detectors", columns)
- {
- /** {@inheritDoc} */
- @Override
- public Iterator<Row> iterator()
- {
- Iterator<LoopDetector> iterator = detectors.iterator();
- return new Iterator<>()
- {
- /** {@inheritDoc} */
- @Override
- public boolean hasNext()
- {
- return iterator.hasNext();
- }
- /** {@inheritDoc} */
- @Override
- public Row next()
- {
- LoopDetector detector = iterator.next();
- return new Row(table(), new Object[] {detector.getId(), detector.getLane().getId(),
- detector.getLane().getLink().getId(), detector.getLongitudinalPosition()});
- }
- };
- }
- /**
- * Returns this table instance for inner classes as {@code Table.this} is not possible in an anonymous Table class.
- * @return Table; this table instance for inner classes.
- */
- private Table table()
- {
- return this;
- }
- /** {@inheritDoc} */
- @Override
- public boolean isEmpty()
- {
- return detectors.isEmpty();
- }
- };
- }
- /**
- * Returns a Table with all periodic data, such as flow and speed per minute.
- * @param network RoadNetwork; network from which all detectors are found.
- * @return Table; with all periodic data, such as flow and speed per minute.
- */
- public static Table asTablePeriodicData(final RoadNetwork network)
- {
- Set<LoopDetector> detectors = getLoopDetectors(network);
- Set<LoopDetectorMeasurement<?, ?>> measurements = getMeasurements(detectors, true);
- Collection<Column<?>> columns = new LinkedHashSet<>();
- columns.add(new Column<>("id", "detector id", String.class, null));
- columns.add(new Column<>("t", "time (start of aggregation period)", Duration.class, "s"));
- columns.add(new Column<>("q", "flow", Frequency.class, "/h"));
- for (LoopDetectorMeasurement<?, ?> measurement : measurements)
- {
- columns.add(new Column<>(measurement.getName(), measurement.getDescription(), measurement.getValueType(),
- measurement.getUnit()));
- }
- return new Table("periodic", "periodic measurements", columns)
- {
- /** {@inheritDoc} */
- @Override
- public Iterator<Row> iterator()
- {
- Iterator<LoopDetector> iterator = detectors.iterator();
- return new Iterator<>()
- {
- /** Index iterator. */
- private Iterator<Integer> indexIterator = Collections.emptyIterator();
- /** Current loop detector. */
- private LoopDetector loopDetector;
- /** Map of measurement data per measurement, updated for each detector. */
- private Map<LoopDetectorMeasurement<?, ?>, List<?>> map;
- /** {@inheritDoc} */
- @Override
- public boolean hasNext()
- {
- while (!this.indexIterator.hasNext())
- {
- if (!iterator.hasNext())
- {
- return false;
- }
- this.loopDetector = iterator.next();
- this.loopDetector.aggregate();
- this.indexIterator = IntStream.range(0, this.loopDetector.flow.size()).iterator();
- this.map = this.loopDetector.periodicDataMap;
- }
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public Row next()
- {
- Throw.when(!hasNext(), NoSuchElementException.class, "Periodic data unavailable.");
- Object[] data = new Object[columns.size()];
- int index = this.indexIterator.next();
- double t = this.loopDetector.firstAggregation.si + (index - 1) * this.loopDetector.aggregation.si;
- data[0] = this.loopDetector.getId();
- data[1] = Duration.instantiateSI(t < 0.0 ? 0.0 : t);
- data[2] = this.loopDetector.flow.get(index);
- int dataIndex = 3;
- for (LoopDetectorMeasurement<?, ?> measurement : measurements)
- {
- if (this.map.containsKey(measurement))
- {
- data[dataIndex++] = this.map.get(measurement).get(index);
- }
- else
- {
- // this data is not available for this detector
- data[dataIndex++] = null;
- }
- }
- return new Row(table(), data);
- }
- };
- }
- /**
- * Returns this table instance for inner classes as {@code Table.this} is not possible in an anonymous Table class.
- * @return Table; this table instance for inner classes.
- */
- private Table table()
- {
- return this;
- }
- /** {@inheritDoc} */
- @Override
- public boolean isEmpty()
- {
- return detectors.isEmpty() || !detectors.iterator().next().hasLastValue();
- }
- };
- }
- /**
- * Returns a Table with all non-periodic data, such as vehicle passage times or platoon counts.
- * @param network RoadNetwork; network from which all detectors are found.
- * @return Table; with all non-periodic data, such as vehicle passage times or platoon counts.
- */
- public static Table asTableNonPeriodicData(final RoadNetwork network)
- {
- Set<LoopDetector> detectors = getLoopDetectors(network);
- Set<LoopDetectorMeasurement<?, ?>> measurements = getMeasurements(detectors, false);
- Collection<Column<?>> columns = new LinkedHashSet<>();
- columns.add(new Column<>("id", "detector id", String.class, null));
- columns.add(new Column<>("measurement", "measurement type", String.class, null));
- columns.add(new Column<>("data", "data in any form", String.class, null));
- return new Table("non-periodic", "non-periodic measurements", columns)
- {
- /** {@inheritDoc} */
- @Override
- public Iterator<Row> iterator()
- {
- Iterator<LoopDetector> iterator = detectors.iterator();
- return new Iterator<>()
- {
- /** Index iterator. */
- private Iterator<LoopDetectorMeasurement<?, ?>> measurementIterator = Collections.emptyIterator();
- /** Current loop detector. */
- private LoopDetector loopDetector;
- /** Map of measurement data per measurement, updated for each detector. */
- private Map<LoopDetectorMeasurement<?, ?>, Object> map;
- /** Current measurement. */
- private LoopDetectorMeasurement<?, ?> measurement;
- /** {@inheritDoc} */
- @Override
- public boolean hasNext()
- {
- if (this.measurement != null)
- {
- return true; // catch consecutive 'hasNext' calls before next 'next' call
- }
- while (!this.measurementIterator.hasNext())
- {
- if (!iterator.hasNext())
- {
- return false;
- }
- this.loopDetector = iterator.next();
- this.measurementIterator = measurements.iterator();
- this.map = this.loopDetector.getNonPeriodicMeasurements();
- }
- // skip if data is not available for this detector
- this.measurement = this.measurementIterator.next();
- if (!this.map.containsKey(this.measurement))
- {
- this.measurement = null;
- return hasNext();
- }
- return true;
- }
- /** {@inheritDoc} */
- @Override
- public Row next()
- {
- Throw.when(!hasNext(), NoSuchElementException.class, "Non-periodic data unavailable.");
- Object[] data = new Object[columns.size()];
- data[0] = this.loopDetector.getId();
- data[1] = this.measurement.getName();
- data[2] = this.map.get(this.measurement).toString();
- this.measurement = null;
- return new Row(table(), data);
- }
- };
- }
- /**
- * Returns this table instance for inner classes as {@code Table.this} is not possible in an anonymous Table class.
- * @return Table; this table instance for inner classes.
- */
- private Table table()
- {
- return this;
- }
- /** {@inheritDoc} */
- @Override
- public boolean isEmpty()
- {
- return detectors.isEmpty() || measurements.isEmpty();
- }
- };
- }
- /**
- * Gathers all loop detectors from the network and puts them in a set sorted by loop detector id.
- * @param network RoadNetwork; network.
- * @return Set<LoopDetector>; set of loop detector sorted by loop detector id.
- */
- private static Set<LoopDetector> getLoopDetectors(final RoadNetwork network)
- {
- Set<LoopDetector> detectors = new TreeSet<>(new Comparator<LoopDetector>()
- {
- @Override
- public int compare(final LoopDetector o1, final LoopDetector o2)
- {
- return o1.getId().compareTo(o2.getId());
- }
- });
- detectors.addAll(network.getObjectMap(LoopDetector.class).values().toCollection());
- return detectors;
- }
- /**
- * Returns all measurement type that are found accross a set of loop detectors.
- * @param detectors Set<LoopDetector>; set of loop detectors.
- * @param periodic boolean; gather the periodic measurements {@code true}, or the non-periodic measurements {@code false}.
- * @return Set<LoopDetectorMeasurement<?, ?>>; set of periodic or non-periodic measurements from the detectors.
- */
- private static Set<LoopDetectorMeasurement<?, ?>> getMeasurements(final Set<LoopDetector> detectors, final boolean periodic)
- {
- return detectors.stream().flatMap((det) -> det.currentCumulativeDataMap.keySet().stream())
- .filter((measurement) -> measurement.isPeriodic() == periodic)
- .collect(Collectors.toCollection(LinkedHashSet::new));
- }
- /**
- * Interface for what detectors measure.
- * <p>
- * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
- * <br>
- * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
- * </p>
- * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
- * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
- * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
- * @param <C> accumulated type
- * @param <A> aggregated type
- */
- public interface LoopDetectorMeasurement<C, A>
- {
- /**
- * Returns the initial value before accumulation.
- * @return C; initial value before accumulation
- */
- C identity();
- /**
- * Returns an accumulated value for when the front reaches the detector. GTU's may trigger an exit without having
- * triggered an entry due to a lane change. Reversely, GTU's may not trigger an exit while they did trigger an entry.
- * @param cumulative C; accumulated value
- * @param gtu LaneBasedGtu; gtu
- * @param loopDetector Detector; loop detector
- * @return C; accumulated value
- */
- C accumulateEntry(C cumulative, LaneBasedGtu gtu, LoopDetector loopDetector);
- /**
- * Returns an accumulated value for when the rear leaves the detector. GTU's may trigger an exit without having
- * triggered an entry due to a lane change. Reversely, GTU's may not trigger an exit while they did trigger an entry.
- * @param cumulative C; accumulated value
- * @param gtu LaneBasedGtu; gtu
- * @param loopDetector Detector; loop detector
- * @return C; accumulated value
- */
- C accumulateExit(C cumulative, LaneBasedGtu gtu, LoopDetector loopDetector);
- /**
- * Returns whether the measurement aggregates every aggregation period (or only over the entire simulation).
- * @return boolean; whether the measurement aggregates every aggregation period (or only over the entire simulation)
- */
- boolean isPeriodic();
- /**
- * Returns an aggregated value.
- * @param cumulative C; accumulated value
- * @param gtuCount int; GTU gtuCount
- * @param aggregation Duration; aggregation period
- * @return A; aggregated value
- */
- A aggregate(C cumulative, int gtuCount, Duration aggregation);
- /**
- * Returns the value name.
- * @return String; value name
- */
- String getName();
- /**
- * Measurement description.
- * @return String; measurement description.
- */
- String getDescription();
- /**
- * Returns the unit string, default is {@code null}.
- * @return String; unit string.
- */
- default String getUnit()
- {
- return null;
- }
- /**
- * Returns the data type.
- * @return Class<?>; data type.
- */
- Class<?> getValueType();
- }
- /**
- * Measurement of platoon sizes based on time between previous GTU exit and GTU entry.
- * <p>
- * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
- * <br>
- * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
- * </p>
- * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
- * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
- * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
- */
- public static class PlatoonSizes implements LoopDetectorMeasurement<PlatoonMeasurement, List<Integer>>
- {
- /** Maximum time between two vehicles that are considered to be in the same platoon. */
- private final Duration threshold;
- /**
- * Constructor.
- * @param threshold Duration; maximum time between two vehicles that are considered to be in the same platoon
- */
- public PlatoonSizes(final Duration threshold)
- {
- this.threshold = threshold;
- }
- /** {@inheritDoc} */
- @Override
- public PlatoonMeasurement identity()
- {
- return new PlatoonMeasurement();
- }
- /** {@inheritDoc} */
- @SuppressWarnings("synthetic-access")
- @Override
- public PlatoonMeasurement accumulateEntry(final PlatoonMeasurement cumulative, final LaneBasedGtu gtu,
- final LoopDetector loopDetector)
- {
- Time now = gtu.getSimulator().getSimulatorAbsTime();
- if (now.si - cumulative.lastExitTime.si < this.threshold.si)
- {
- cumulative.gtuCount++;
- }
- else
- {
- if (cumulative.gtuCount > 0) // 0 means this is the first vehicle of the first platoon
- {
- cumulative.platoons.add(cumulative.gtuCount);
- }
- cumulative.gtuCount = 1;
- }
- cumulative.enteredGTUs.add(gtu);
- cumulative.lastExitTime = now; // should we change lane before triggering the exit
- return cumulative;
- }
- /** {@inheritDoc} */
- @SuppressWarnings("synthetic-access")
- @Override
- public PlatoonMeasurement accumulateExit(final PlatoonMeasurement cumulative, final LaneBasedGtu gtu,
- final LoopDetector loopDetector)
- {
- int index = cumulative.enteredGTUs.indexOf(gtu);
- if (index >= 0)
- {
- cumulative.lastExitTime = gtu.getSimulator().getSimulatorAbsTime();
- // gtu is likely the oldest gtu in the list at index 0, but sometimes an older gtu may have left the detector by
- // changing lane, by clearing up to this gtu, older gtu's are automatically removed
- cumulative.enteredGTUs.subList(0, index).clear();
- }
- return cumulative;
- }
- /** {@inheritDoc} */
- @Override
- public boolean isPeriodic()
- {
- return false;
- }
- /** {@inheritDoc} */
- @SuppressWarnings("synthetic-access")
- @Override
- public List<Integer> aggregate(final PlatoonMeasurement cumulative, final int count, final Duration aggregation)
- {
- if (cumulative.gtuCount > 0)
- {
- cumulative.platoons.add(cumulative.gtuCount);
- cumulative.gtuCount = 0; // prevent that the last platoon is added again if the same output is saved again
- }
- return cumulative.platoons;
- }
- /** {@inheritDoc} */
- @Override
- public String getName()
- {
- return "platoon sizes";
- }
- /** {@inheritDoc} */
- @Override
- public String toString()
- {
- return getName();
- }
- /** {@inheritDoc} */
- @Override
- public String getDescription()
- {
- return "list of platoon sizes (threshold: " + this.threshold + ")";
- }
- /** {@inheritDoc} */
- @Override
- public Class<Integer> getValueType()
- {
- return Integer.class;
- }
- }
- /**
- * Cumulative information for platoon size measurement.
- * <p>
- * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
- * <br>
- * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
- * </p>
- * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
- * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
- * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
- */
- static class PlatoonMeasurement
- {
- /** GTU's counted so far in the current platoon. */
- private int gtuCount = 0;
- /** Time the last GTU exited the detector. */
- private Time lastExitTime = Time.instantiateSI(Double.NEGATIVE_INFINITY);
- /** Stored sizes of earlier platoons. */
- private List<Integer> platoons = new ArrayList<>();
- /** GTU's currently on the detector, some may have left by a lane change. */
- private List<LaneBasedGtu> enteredGTUs = new ArrayList<>();
- }
- }