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