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