View Javadoc
1   package org.opentrafficsim.road.network.sampling;
2   
3   import java.rmi.RemoteException;
4   import java.util.LinkedHashMap;
5   import java.util.LinkedHashSet;
6   import java.util.Map;
7   import java.util.Set;
8   
9   import org.djunits.unit.DurationUnit;
10  import org.djunits.value.vdouble.scalar.Acceleration;
11  import org.djunits.value.vdouble.scalar.Duration;
12  import org.djunits.value.vdouble.scalar.Frequency;
13  import org.djunits.value.vdouble.scalar.Length;
14  import org.djunits.value.vdouble.scalar.Speed;
15  import org.djunits.value.vdouble.scalar.Time;
16  import org.djutils.event.Event;
17  import org.djutils.event.EventListener;
18  import org.djutils.event.TimedEvent;
19  import org.djutils.event.reference.ReferenceType;
20  import org.djutils.exceptions.Throw;
21  import org.djutils.exceptions.Try;
22  import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
23  import org.opentrafficsim.core.gtu.GtuException;
24  import org.opentrafficsim.core.gtu.RelativePosition;
25  import org.opentrafficsim.kpi.sampling.Sampler;
26  import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
27  import org.opentrafficsim.kpi.sampling.meta.FilterDataType;
28  import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
29  import org.opentrafficsim.road.network.RoadNetwork;
30  import org.opentrafficsim.road.network.lane.CrossSectionLink;
31  import org.opentrafficsim.road.network.lane.Lane;
32  
33  import nl.tudelft.simulation.dsol.SimRuntimeException;
34  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
35  
36  /**
37   * Implementation of kpi sampler for OTS.
38   * <p>
39   * Copyright (c) 2013-2023 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
40   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
41   * </p>
42   * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
43   * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
44   * @author <a href="https://dittlab.tudelft.nl">Wouter Schakel</a>
45   */
46  public class RoadSampler extends Sampler<GtuDataRoad, LaneDataRoad> implements EventListener
47  {
48  
49      /** */
50      private static final long serialVersionUID = 20200228L;
51  
52      /** Simulator. */
53      private final OtsSimulatorInterface simulator;
54  
55      /** Network. */
56      private final RoadNetwork network;
57  
58      /** Sampling interval. */
59      private final Duration samplingInterval;
60  
61      /** Registration of sampling events of each GTU per lane, if interval based. */
62      private final Map<String, Map<Lane, SimEventInterface<Duration>>> eventsPerGtu = new LinkedHashMap<>();
63  
64      /** Set of lanes the sampler knows each GTU to be at. Usually 1, could be 2 during a trajectory transition. */
65      private final Map<String, Set<Lane>> activeLanesPerGtu = new LinkedHashMap<>();
66  
67      /** Set of actively sampled GTUs. */
68      private final Set<String> activeGtus = new LinkedHashSet<>();
69  
70      /**
71       * Constructor which uses the operational plan updates of GTU's as sampling interval.
72       * @param network RoadNetwork; the network
73       * @throws NullPointerException if the simulator is {@code null}
74       */
75      public RoadSampler(final RoadNetwork network)
76      {
77          this(new LinkedHashSet<>(), new LinkedHashSet<>(), network);
78      }
79  
80      /**
81       * Constructor which uses the operational plan updates of GTU's as sampling interval.
82       * @param extendedDataTypes Set&lt;ExtendedDataType&lt;?, ?, ?, GtuData&gt;&gt;; extended data types
83       * @param filterDataTypes Set&lt;FilterDataType&lt;?&gt;&gt;; filter data types
84       * @param network RoadNetwork; the network
85       * @throws NullPointerException if the simulator is {@code null}
86       */
87      public RoadSampler(final Set<ExtendedDataType<?, ?, ?, GtuDataRoad>> extendedDataTypes,
88              final Set<FilterDataType<?>> filterDataTypes, final RoadNetwork network)
89      {
90          super(extendedDataTypes, filterDataTypes);
91          Throw.whenNull(network, "Network may not be null.");
92          this.network = network;
93          this.simulator = network.getSimulator();
94          this.samplingInterval = null;
95      }
96  
97      /**
98       * Constructor which uses the given frequency to determine the sampling interval.
99       * @param network RoadNetwork; the network
100      * @param frequency Frequency; sampling frequency
101      * @throws NullPointerException if an input is {@code null}
102      * @throws IllegalArgumentException if frequency is negative or zero
103      */
104     public RoadSampler(final RoadNetwork network, final Frequency frequency)
105     {
106         this(new LinkedHashSet<>(), new LinkedHashSet<>(), network, frequency);
107     }
108 
109     /**
110      * Constructor which uses the given frequency to determine the sampling interval.
111      * @param extendedDataTypes Set&lt;ExtendedDataType&lt;?, ?, ?, GtuData&gt;&gt;; extended data types
112      * @param filterDataTypes Set&lt;FilterDataType&lt;?&gt;&gt;; filter data types
113      * @param network RoadNetwork; the network
114      * @param frequency Frequency; sampling frequency
115      * @throws NullPointerException if an input is {@code null}
116      * @throws IllegalArgumentException if frequency is negative or zero
117      */
118     public RoadSampler(final Set<ExtendedDataType<?, ?, ?, GtuDataRoad>> extendedDataTypes,
119             final Set<FilterDataType<?>> filterDataTypes, final RoadNetwork network, final Frequency frequency)
120     {
121         super(extendedDataTypes, filterDataTypes);
122         Throw.whenNull(network, "Network may not be null.");
123         Throw.whenNull(frequency, "Frequency may not be null.");
124         Throw.when(frequency.le(Frequency.ZERO), IllegalArgumentException.class,
125                 "Negative or zero sampling frequency is not permitted.");
126         this.network = network;
127         this.simulator = network.getSimulator();
128         this.samplingInterval = new Duration(1.0 / frequency.si, DurationUnit.SI);
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public final Time now()
134     {
135         return this.simulator.getSimulatorAbsTime();
136     }
137 
138     /** {@inheritDoc} */
139     @Override
140     public final void scheduleStartRecording(final Time time, final LaneDataRoad lane)
141     {
142         try
143         {
144             this.simulator.scheduleEventAbsTime(time, this, "startRecording", new Object[] {lane});
145         }
146         catch (SimRuntimeException exception)
147         {
148             throw new RuntimeException("Cannot start recording.", exception);
149         }
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public final void scheduleStopRecording(final Time time, final LaneDataRoad lane)
155     {
156         try
157         {
158             this.simulator.scheduleEventAbsTime(time, this, "stopRecording", new Object[] {lane});
159         }
160         catch (SimRuntimeException exception)
161         {
162             throw new RuntimeException("Cannot stop recording.", exception);
163         }
164     }
165 
166     /** {@inheritDoc} */
167     @Override
168     public final void initRecording(final LaneDataRoad lane)
169     {
170         Lane roadLane = lane.getLane();
171         // @docs/02-model-structure/djutils.md#event-producers-and-listeners
172         roadLane.addListener(this, Lane.GTU_ADD_EVENT, ReferenceType.WEAK);
173         roadLane.addListener(this, Lane.GTU_REMOVE_EVENT, ReferenceType.WEAK);
174         // @end
175         int count = 1;
176         for (LaneBasedGtu gtu : roadLane.getGtuList())
177         {
178             try
179             {
180                 // Payload: Object[] {String gtuId, Lane source}
181                 notify(new TimedEvent<>(Lane.GTU_ADD_EVENT,
182                         new Object[] {gtu.getId(), count, roadLane.getId(), roadLane.getParentLink().getId()},
183                         gtu.getSimulator().getSimulatorTime()));
184                 count++;
185             }
186             catch (Exception exception)
187             {
188                 throw new RuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception);
189             }
190         }
191     }
192 
193     /** {@inheritDoc} */
194     @Override
195     public final void finalizeRecording(final LaneDataRoad lane)
196     {
197         Lane roadLane = lane.getLane();
198         roadLane.removeListener(this, Lane.GTU_ADD_EVENT);
199         roadLane.removeListener(this, Lane.GTU_REMOVE_EVENT);
200     }
201 
202     /** {@inheritDoc} */
203     @Override
204     // @docs/02-model-structure/djutils.md#event-producers-and-listeners (if-structure + add/removeListener(...))
205     public final void notify(final Event event) throws RemoteException
206     {
207         if (event.getType().equals(LaneBasedGtu.LANEBASED_MOVE_EVENT))
208         {
209             // Payload: [String gtuId, PositionVector currentPosition, Direction currentDirection, Speed speed, Acceleration
210             // acceleration, TurnIndicatorStatus turnIndicatorStatus, Length odometer, Link id of referenceLane, Lane id of
211             // referenceLane, Length positionOnReferenceLane]
212             Object[] payload = (Object[]) event.getContent();
213             CrossSectionLink link = (CrossSectionLink) this.network.getLink(payload[7].toString());
214             Lane lane = (Lane) link.getCrossSectionElement(payload[8].toString());
215             LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU(payload[0].toString());
216             LaneDataRoad laneData = new LaneDataRoad(lane);
217 
218             if (!this.activeGtus.contains(gtu.getId()))
219             {
220                 // GTU add was skipped during add event due to an improper phase of initialization, do here instead
221                 processGtuAddEvent(laneData, new GtuDataRoad(gtu));
222                 this.activeGtus.add(gtu.getId());
223             }
224             processGtuMoveEvent(laneData, (Length) payload[9], (Speed) payload[3], (Acceleration) payload[4], now(),
225                     new GtuDataRoad(gtu));
226         }
227         else if (event.getType().equals(Lane.GTU_ADD_EVENT))
228         {
229             // Payload: Object[] {String gtuId, int count_after_addition, String laneId, String linkId}
230             Object[] payload = (Object[]) event.getContent();
231             Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[3]))
232                     .getCrossSectionElement((String) payload[2]);
233             LaneDataRoad laneData = new LaneDataRoad(lane);
234             if (!getSamplerData().contains(laneData))
235             {
236                 return; // we are not sampling this Lane
237             }
238             LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU((String) payload[0]);
239 
240             // Skip add when first encountering this GTU, it is in an improper phase of initialization.
241             // If interval-based, a GTU is also not in the active list in the moment of an instantaneous lane-change.
242             boolean active = this.activeGtus.contains(gtu.getId());
243             if (active)
244             {
245                 Length position = Try.assign(() -> gtu.position(lane, RelativePosition.REFERENCE_POSITION),
246                         "Could not determine position.");
247                 Speed speed = gtu.getSpeed();
248                 Acceleration acceleration = gtu.getAcceleration();
249                 processGtuAddEventWithMove(laneData, position, speed, acceleration, now(), new GtuDataRoad(gtu));
250             }
251 
252             if (isIntervalBased())
253             {
254                 Duration nowOnFirstEncounterOtherwiseAtInterval = active ? this.samplingInterval : Duration.ZERO;
255                 scheduleSamplingInterval(gtu, lane, nowOnFirstEncounterOtherwiseAtInterval);
256             }
257             else
258             {
259                 this.activeLanesPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashSet<>()).add(lane);
260                 gtu.addListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT, ReferenceType.WEAK);
261             }
262         }
263         else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
264         {
265             // Payload: Object[] {String gtuId, LaneBasedGtu gtu, int count_after_removal, Length position, String laneId,
266             // String linkId}
267             Object[] payload = (Object[]) event.getContent();
268             Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[5]))
269                     .getCrossSectionElement((String) payload[4]);
270             LaneDataRoad laneData = new LaneDataRoad(lane);
271             LaneBasedGtu gtu = (LaneBasedGtu) payload[1];
272             Length position = (Length) payload[3];
273             Speed speed = gtu.getSpeed();
274             Acceleration acceleration = gtu.getAcceleration();
275 
276             processGtuRemoveEventWithMove(laneData, position, speed, acceleration, now(), new GtuDataRoad(gtu));
277 
278             if (isIntervalBased())
279             {
280                 Map<Lane, SimEventInterface<Duration>> events = this.eventsPerGtu.get(gtu.getId());
281                 SimEventInterface<Duration> e = events.remove(lane);
282                 if (e != null)
283                 {
284                     this.simulator.cancelEvent(e);
285                 }
286                 if (events.isEmpty())
287                 {
288                     this.eventsPerGtu.remove(gtu.getId());
289                     this.activeGtus.remove(gtu.getId());
290                 }
291             }
292             else
293             {
294                 this.activeLanesPerGtu.get(gtu.getId()).remove(lane);
295                 if (this.activeLanesPerGtu.get(gtu.getId()).isEmpty())
296                 {
297                     this.activeLanesPerGtu.remove(gtu.getId());
298                     gtu.removeListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT);
299                     this.activeGtus.remove(gtu.getId());
300                 }
301             }
302         }
303     }
304 
305     /**
306      * @return whether sampling is interval based
307      */
308     private boolean isIntervalBased()
309     {
310         return this.samplingInterval != null;
311     }
312 
313     /**
314      * Schedules a sampling event for the given gtu on the given lane for the sampling interval from the current time.
315      * @param gtu LaneBasedGtu; gtu to sample
316      * @param lane Lane; lane where the gtu is at
317      * @param inTime Duration; relative time to schedule
318      */
319     private void scheduleSamplingInterval(final LaneBasedGtu gtu, final Lane lane, final Duration inTime)
320     {
321         SimEventInterface<Duration> simEvent;
322         try
323         {
324             simEvent = this.simulator.scheduleEventRel(inTime, this, "notifySample", new Object[] {gtu, lane});
325         }
326         catch (SimRuntimeException exception)
327         {
328             // should not happen with getSimulatorTime.add()
329             throw new RuntimeException("Scheduling sampling in the past.", exception);
330         }
331         this.eventsPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashMap<>()).put(lane, simEvent);
332     }
333 
334     /**
335      * Samples a gtu and schedules the next sampling event. This is used for interval-based sampling.
336      * @param gtu LaneBasedGtu; gtu to sample
337      * @param lane Lane; lane direction where the gtu is at
338      */
339     public final void notifySample(final LaneBasedGtu gtu, final Lane lane)
340     {
341         LaneDataRoad laneData = new LaneDataRoad(lane);
342         try
343         {
344             Length position = gtu.position(lane, RelativePosition.REFERENCE_POSITION);
345             if (this.activeGtus.contains(gtu.getId()))
346             {
347                 // already recording this GTU, just trigger a record through a move
348                 processGtuMoveEvent(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(), new GtuDataRoad(gtu));
349             }
350             else
351             {
352                 // first time encountering this GTU so add, which also triggers a record through a move
353                 processGtuAddEventWithMove(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(),
354                         new GtuDataRoad(gtu));
355                 this.activeGtus.add(gtu.getId());
356             }
357         }
358         catch (GtuException exception)
359         {
360             throw new RuntimeException("Requesting position on lane, but the GTU is not on the lane.", exception);
361         }
362         scheduleSamplingInterval(gtu, lane, this.samplingInterval);
363     }
364 
365     /** {@inheritDoc} */
366     @Override
367     public final int hashCode()
368     {
369         final int prime = 31;
370         int result = super.hashCode();
371         result = prime * result + ((this.eventsPerGtu == null) ? 0 : this.eventsPerGtu.hashCode());
372         result = prime * result + ((this.samplingInterval == null) ? 0 : this.samplingInterval.hashCode());
373         result = prime * result + ((this.simulator == null) ? 0 : this.simulator.hashCode());
374         return result;
375     }
376 
377     /** {@inheritDoc} */
378     @Override
379     public final boolean equals(final Object obj)
380     {
381         if (this == obj)
382         {
383             return true;
384         }
385         if (!super.equals(obj))
386         {
387             return false;
388         }
389         if (getClass() != obj.getClass())
390         {
391             return false;
392         }
393         RoadSampler other = (RoadSampler) obj;
394         if (this.eventsPerGtu == null)
395         {
396             if (other.eventsPerGtu != null)
397             {
398                 return false;
399             }
400         }
401         else if (!this.eventsPerGtu.equals(other.eventsPerGtu))
402         {
403             return false;
404         }
405         if (this.samplingInterval == null)
406         {
407             if (other.samplingInterval != null)
408             {
409                 return false;
410             }
411         }
412         else if (!this.samplingInterval.equals(other.samplingInterval))
413         {
414             return false;
415         }
416         if (this.simulator == null)
417         {
418             if (other.simulator != null)
419             {
420                 return false;
421             }
422         }
423         else if (!this.simulator.equals(other.simulator))
424         {
425             return false;
426         }
427         return true;
428     }
429 
430     /** {@inheritDoc} */
431     @Override
432     public final String toString()
433     {
434         return "RoadSampler [samplingInterval=" + this.samplingInterval + "]";
435         // do not use "this.eventPerGtu", it creates circular toString and hence stack overflow
436     }
437 
438     /**
439      * Returns a factory to create a sampler.
440      * @param network RoadNetwork; network
441      * @return Factory; factory to create a sampler
442      */
443     public static Factory build(final RoadNetwork network)
444     {
445         return new Factory(network);
446     }
447 
448     /** Factory for {@code RoadSampler}. */
449     public static final class Factory
450     {
451 
452         /** Simulator. */
453         private final RoadNetwork network;
454 
455         /** Registration of included extended data types. */
456         private final Set<ExtendedDataType<?, ?, ?, GtuDataRoad>> extendedDataTypes = new LinkedHashSet<>();
457 
458         /** Set of registered filter data types. */
459         private final Set<FilterDataType<?>> filterDataTypes = new LinkedHashSet<>();
460 
461         /** Frequency. */
462         private Frequency freq;
463 
464         /**
465          * Constructor.
466          * @param network RoadNetwork; network
467          */
468         Factory(final RoadNetwork network)
469         {
470             this.network = network;
471         }
472 
473         /**
474          * Register extended data type.
475          * @param extendedDataType ExtendedDataType&lt;?, ?, ?, GtuData&gt;; extended data type
476          * @return Factory; this factory
477          */
478         public Factory registerExtendedDataType(final ExtendedDataType<?, ?, ?, GtuDataRoad> extendedDataType)
479         {
480             Throw.whenNull(extendedDataType, "Extended data type may not be null.");
481             this.extendedDataTypes.add(extendedDataType);
482             return this;
483         }
484 
485         /**
486          * Register filter data type.
487          * @param filterDataType FilterDataType&lt;?&gt;; filter data type
488          * @return Factory; this factory
489          */
490         public Factory registerFilterDataType(final FilterDataType<?> filterDataType)
491         {
492             Throw.whenNull(filterDataType, "Filter data type may not be null.");
493             this.filterDataTypes.add(filterDataType);
494             return this;
495         }
496 
497         /**
498          * Sets the frequency. If no frequency is set, a sampler is created that records on move events of GTU's.
499          * @param frequency Frequency; frequency
500          * @return Factory; this factory
501          */
502         public Factory setFrequency(final Frequency frequency)
503         {
504             this.freq = frequency;
505             return this;
506         }
507 
508         /**
509          * Create sampler.
510          * @return RoadSampler; sampler
511          */
512         public RoadSampler create()
513         {
514             return this.freq == null ? new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network)
515                     : new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network, this.freq);
516         }
517 
518     }
519 
520 }