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.road.gtu.lane.LaneBasedGTU;
28  import org.opentrafficsim.road.network.lane.Lane;
29  import org.opentrafficsim.road.network.lane.LaneDirection;
30  
31  import nl.tudelft.simulation.dsol.SimRuntimeException;
32  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
33  import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
34  import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
35  
36  /**
37   * Implementation of kpi sampler for OTS.
38   * <p>
39   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
40   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
41   * <p>
42   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 12 okt. 2016 <br>
43   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
44   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
45   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
46   */
47  public class RoadSampler extends Sampler<GtuData> implements EventListenerInterface
48  {
49  
50      /** Simulator. */
51      private final DEVSSimulatorInterface.TimeDoubleUnit simulator;
52  
53      /** Sampling interval. */
54      private final Duration samplingInterval;
55  
56      /** Registration of sampling events of each GTU per lane, if interval based. */
57      private final Map<String, Map<LaneDirection, SimEventInterface<SimTimeDoubleUnit>>> eventPerGtu = new LinkedHashMap<>();
58  
59      /** List of lane the sampler is listening to for each GTU. Usually 1, could be 2 during a trajectory transition. */
60      private final Map<String, Set<LaneDirection>> listenersPerGtu = new LinkedHashMap<>();
61  
62      /**
63       * Constructor which uses the operational plan updates of GTU's as sampling interval.
64       * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
65       * @throws NullPointerException if the simulator is {@code null}
66       */
67      public RoadSampler(final DEVSSimulatorInterface.TimeDoubleUnit simulator)
68      {
69          Throw.whenNull(simulator, "Simulator may not be null.");
70          this.simulator = simulator;
71          this.samplingInterval = null;
72      }
73  
74      /**
75       * Constructor which uses the given frequency to determine the sampling interval.
76       * @param simulator DEVSSimulatorInterface.TimeDoubleUnit; simulator
77       * @param frequency Frequency; sampling frequency
78       * @throws NullPointerException if an input is {@code null}
79       * @throws IllegalArgumentException if frequency is negative or zero
80       */
81      public RoadSampler(final DEVSSimulatorInterface.TimeDoubleUnit simulator, final Frequency frequency)
82      {
83          Throw.whenNull(simulator, "Simulator may not be null.");
84          Throw.whenNull(frequency, "Frequency may not be null.");
85          Throw.when(frequency.le(Frequency.ZERO), IllegalArgumentException.class,
86                  "Negative or zero sampling frequency is not permitted.");
87          this.simulator = simulator;
88          this.samplingInterval = new Duration(1.0 / frequency.si, DurationUnit.SI);
89      }
90  
91      /** {@inheritDoc} */
92      @Override
93      public final Time now()
94      {
95          return this.simulator.getSimulatorTime();
96      }
97  
98      /** {@inheritDoc} */
99      @Override
100     public final void scheduleStartRecording(final Time time, final KpiLaneDirection kpiLaneDirection)
101     {
102         try
103         {
104             this.simulator.scheduleEventAbs(time, this, this, "startRecording", new Object[] {kpiLaneDirection});
105         }
106         catch (SimRuntimeException exception)
107         {
108             throw new RuntimeException("Cannot start recording.", exception);
109         }
110     }
111 
112     /** {@inheritDoc} */
113     @Override
114     public final void scheduleStopRecording(final Time time, final KpiLaneDirection kpiLaneDirection)
115     {
116         try
117         {
118             this.simulator.scheduleEventAbs(time, this, this, "stopRecording", new Object[] {kpiLaneDirection});
119         }
120         catch (SimRuntimeException exception)
121         {
122             throw new RuntimeException("Cannot stop recording.", exception);
123         }
124     }
125 
126     /** {@inheritDoc} */
127     @Override
128     public final void initRecording(final KpiLaneDirection kpiLaneDirection)
129     {
130         Lane lane = ((LaneData) kpiLaneDirection.getLaneData()).getLane();
131         lane.addListener(this, Lane.GTU_ADD_EVENT, ReferenceType.WEAK);
132         lane.addListener(this, Lane.GTU_REMOVE_EVENT, ReferenceType.WEAK);
133         int count = 1;
134         for (LaneBasedGTU gtu : lane.getGtuList())
135         {
136             try
137             {
138                 if (sameDirection(kpiLaneDirection.getKpiDirection(), gtu.getDirection(lane)))
139                 {
140                     // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_addition}
141                     notify(new TimedEvent<>(Lane.GTU_ADD_EVENT, lane, new Object[] {gtu.getId(), gtu, count},
142                             gtu.getSimulator().getSimulatorTime()));
143                 }
144                 count++;
145             }
146             catch (RemoteException | GTUException exception)
147             {
148                 throw new RuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception);
149             }
150         }
151     }
152 
153     /** {@inheritDoc} */
154     @Override
155     public final void finalizeRecording(final KpiLaneDirection kpiLaneDirection)
156     {
157         Lane lane = ((LaneData) kpiLaneDirection.getLaneData()).getLane();
158         lane.removeListener(this, Lane.GTU_ADD_EVENT);
159         lane.removeListener(this, Lane.GTU_REMOVE_EVENT);
160         // Lane lane = ((LaneData) kpiLaneDirection.getLaneData()).getLane();
161         // int count = 0;
162         // List<LaneBasedGTU> currentGtus = new ArrayList<>();
163         // try
164         // {
165         // for (LaneBasedGTU gtu : lane.getGtuList())
166         // {
167         // DirectedLanePosition dlp = gtu.getReferencePosition();
168         // if (dlp.getLane().equals(lane) && sameDirection(kpiLaneDirection.getKpiDirection(), dlp.getGtuDirection()))
169         // {
170         // currentGtus.add(gtu);
171         // count++;
172         // }
173         // }
174         // for (LaneBasedGTU gtu : currentGtus)
175         // {
176         // // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_removal, Length position}
177         // notify(new TimedEvent<>(Lane.GTU_REMOVE_EVENT, lane, new Object[] { gtu.getId(), gtu, count },
178         // gtu.getSimulator().getSimulatorTime()));
179         // count--;
180         // }
181         // }
182         // catch (RemoteException | GTUException exception)
183         // {
184         // throw new RuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception);
185         // }
186     }
187 
188     /**
189      * Compares a {@link KpiGtuDirectionality} and a {@link GTUDirectionality}.
190      * @param kpiGtuDirectionality KpiGtuDirectionality; kpi gtu direction
191      * @param gtuDirectionality GTUDirectionality; gtu direction
192      * @return whether both are in the same direction
193      */
194     private boolean sameDirection(final KpiGtuDirectionality kpiGtuDirectionality, final GTUDirectionality gtuDirectionality)
195     {
196         if (kpiGtuDirectionality.equals(KpiGtuDirectionality.DIR_PLUS))
197         {
198             return gtuDirectionality.equals(GTUDirectionality.DIR_PLUS);
199         }
200         return gtuDirectionality.equals(GTUDirectionality.DIR_MINUS);
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     public final void notify(final EventInterface event) throws RemoteException
206     {
207         if (event.getType().equals(LaneBasedGTU.LANEBASED_MOVE_EVENT))
208         {
209             // Payload: [String gtuId, DirectedPoint position, Speed speed, Acceleration acceleration, TurnIndicatorStatus
210             // turnIndicatorStatus, Length odometer, Lane referenceLane, Length positionOnReferenceLane]
211             Object[] payload = (Object[]) event.getContent();
212             KpiLaneDirection laneDirection =
213                     new KpiLaneDirection(new LaneData((Lane) payload[6]), KpiGtuDirectionality.DIR_PLUS);
214             processGtuMoveEvent(laneDirection, (Length) payload[7], (Speed) payload[2], (Acceleration) payload[3], now(),
215                     new GtuData((LaneBasedGTU) event.getSourceId()));
216         }
217         else if (event.getType().equals(Lane.GTU_ADD_EVENT))
218         {
219             // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_addition}
220             // Assumes that the lane itself is the sourceId
221             Laneef="../../../../../org/opentrafficsim/road/network/lane/Lane.html#Lane">Lane lane = (Lane) event.getSourceId();
222             // TODO GTUDirectionality from Lane.GTU_ADD_EVENT
223             KpiLaneDirection laneDirection = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS);
224             if (!contains(laneDirection))
225             {
226                 // we are not sampling this LaneDirection
227                 return;
228             }
229             Object[] payload = (Object[]) event.getContent();
230             LaneBasedGTU../../../../org/opentrafficsim/road/gtu/lane/LaneBasedGTU.html#LaneBasedGTU">LaneBasedGTU gtu = (LaneBasedGTU) payload[1];
231             Length position;
232             try
233             {
234                 // TODO Length from Lane.GTU_ADD_EVENT
235                 position = gtu.position(lane, RelativePosition.REFERENCE_POSITION);
236             }
237             catch (GTUException exception)
238             {
239                 throw new RuntimeException(exception);
240             }
241             Speed speed = gtu.getSpeed();
242             Acceleration acceleration = gtu.getAcceleration();
243             processGtuAddEvent(laneDirection, position, speed, acceleration, now(), new GtuData(gtu));
244             LaneDirection/LaneDirection.html#LaneDirection">LaneDirection lDirection = new LaneDirection(lane, GTUDirectionality.DIR_PLUS);
245             if (isIntervalBased())
246             {
247                 scheduleSamplingEvent(gtu, lDirection);
248             }
249             else
250             {
251                 if (!this.listenersPerGtu.containsKey(gtu.getId()))
252                 {
253                     this.listenersPerGtu.put(gtu.getId(), new LinkedHashSet<>());
254                 }
255                 this.listenersPerGtu.get(gtu.getId()).add(lDirection);
256                 gtu.addListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT, ReferenceType.WEAK);
257             }
258         }
259         else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
260         {
261             // Payload: Object[] {String gtuId, LaneBasedGTU gtu, int count_after_removal, Length position}
262             // Assumes that the lane itself is the sourceId
263             Laneef="../../../../../org/opentrafficsim/road/network/lane/Lane.html#Lane">Lane lane = (Lane) event.getSourceId();
264             // TODO GTUDirectionality from Lane.GTU_REMOVE_EVENT
265             KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(lane), KpiGtuDirectionality.DIR_PLUS);
266             Object[] payload = (Object[]) event.getContent();
267             LaneBasedGTU../../../../org/opentrafficsim/road/gtu/lane/LaneBasedGTU.html#LaneBasedGTU">LaneBasedGTU gtu = (LaneBasedGTU) payload[1];
268             Length position = (Length) payload[3];
269             Speed speed = gtu.getSpeed();
270             Acceleration acceleration = gtu.getAcceleration();
271             processGtuRemoveEvent(kpiLaneDirection, position, speed, acceleration, now(), new GtuData(gtu));
272             LaneDirection/LaneDirection.html#LaneDirection">LaneDirection lDirection = new LaneDirection(lane, GTUDirectionality.DIR_PLUS);
273             if (isIntervalBased())
274             {
275                 String gtuId = (String) payload[0];
276 
277                 if (this.eventPerGtu.get(gtuId) != null)
278                 {
279                     if (this.eventPerGtu.get(gtuId).containsKey(lDirection))
280                     {
281                         this.simulator.cancelEvent(this.eventPerGtu.get(gtuId).get(lDirection));
282                     }
283                     this.eventPerGtu.get(gtuId).remove(lDirection);
284                     if (this.eventPerGtu.get(gtuId).isEmpty())
285                     {
286                         this.eventPerGtu.remove(gtuId);
287                     }
288                 }
289             }
290             else
291             {
292                 // Should not remove if just added on other lane
293                 this.listenersPerGtu.get(gtu.getId()).remove(lDirection);
294                 if (this.listenersPerGtu.get(gtu.getId()).isEmpty())
295                 {
296                     this.listenersPerGtu.remove(gtu.getId());
297                     gtu.removeListener(this, LaneBasedGTU.LANEBASED_MOVE_EVENT);
298                 }
299             }
300         }
301 
302     }
303 
304     /**
305      * @return whether sampling is interval based
306      */
307     private boolean isIntervalBased()
308     {
309         return this.samplingInterval != null;
310     }
311 
312     /**
313      * Schedules a sampling event for the given gtu on the given lane for the sampling interval from the current time.
314      * @param gtu LaneBasedGTU; gtu to sample
315      * @param laneDirection LaneDirection; lane direction where the gtu is at
316      */
317     private void scheduleSamplingEvent(final LaneBasedGTU gtu, final LaneDirection laneDirection)
318     {
319         SimEventInterface<SimTimeDoubleUnit> simEvent;
320         try
321         {
322             // this.simulator.scheduleEvent(simEvent);
323             simEvent = this.simulator.scheduleEventRel(this.samplingInterval, this, this, "notifySample",
324                     new Object[] {gtu, laneDirection});
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         String gtuId = gtu.getId();
332         if (!this.eventPerGtu.containsKey(gtuId))
333         {
334             Map<LaneDirection, SimEventInterface<SimTimeDoubleUnit>> map = new LinkedHashMap<>();
335             this.eventPerGtu.put(gtuId, map);
336         }
337         this.eventPerGtu.get(gtuId).put(laneDirection, simEvent);
338     }
339 
340     /**
341      * Samples a gtu and schedules the next sampling event.
342      * @param gtu LaneBasedGTU; gtu to sample
343      * @param laneDirection LaneDirection; lane direction where the gtu is at
344      */
345     public final void notifySample(final LaneBasedGTU gtu, final LaneDirection laneDirection)
346     {
347         KpiLaneDirection kpiLaneDirection = new KpiLaneDirection(new LaneData(laneDirection.getLane()),
348                 laneDirection.getDirection().isPlus() ? KpiGtuDirectionality.DIR_PLUS : KpiGtuDirectionality.DIR_MINUS);
349         try
350         {
351             this.processGtuMoveEvent(kpiLaneDirection,
352                     gtu.position(laneDirection.getLane(), RelativePosition.REFERENCE_POSITION), gtu.getSpeed(),
353                     gtu.getAcceleration(), now(), new GtuData(gtu));
354         }
355         catch (GTUException exception)
356         {
357             throw new RuntimeException("Requesting position on lane, but the GTU is not on the lane.", exception);
358         }
359         scheduleSamplingEvent(gtu, laneDirection);
360     }
361 
362     /** {@inheritDoc} */
363     @Override
364     public final int hashCode()
365     {
366         final int prime = 31;
367         int result = super.hashCode();
368         result = prime * result + ((this.eventPerGtu == null) ? 0 : this.eventPerGtu.hashCode());
369         result = prime * result + ((this.samplingInterval == null) ? 0 : this.samplingInterval.hashCode());
370         result = prime * result + ((this.simulator == null) ? 0 : this.simulator.hashCode());
371         return result;
372     }
373 
374     /** {@inheritDoc} */
375     @Override
376     public final boolean equals(final Object obj)
377     {
378         if (this == obj)
379         {
380             return true;
381         }
382         if (!super.equals(obj))
383         {
384             return false;
385         }
386         if (getClass() != obj.getClass())
387         {
388             return false;
389         }
390         RoadSampler./../../../org/opentrafficsim/road/network/sampling/RoadSampler.html#RoadSampler">RoadSampler other = (RoadSampler) obj;
391         if (this.eventPerGtu == null)
392         {
393             if (other.eventPerGtu != null)
394             {
395                 return false;
396             }
397         }
398         else if (!this.eventPerGtu.equals(other.eventPerGtu))
399         {
400             return false;
401         }
402         if (this.samplingInterval == null)
403         {
404             if (other.samplingInterval != null)
405             {
406                 return false;
407             }
408         }
409         else if (!this.samplingInterval.equals(other.samplingInterval))
410         {
411             return false;
412         }
413         if (this.simulator == null)
414         {
415             if (other.simulator != null)
416             {
417                 return false;
418             }
419         }
420         else if (!this.simulator.equals(other.simulator))
421         {
422             return false;
423         }
424         return true;
425     }
426 
427     /** {@inheritDoc} */
428     @Override
429     public final String toString()
430     {
431         return "RoadSampler [samplingInterval=" + this.samplingInterval + "]";
432         // do not use "this.eventPerGtu", it creates circular toString and hence stack overflow
433     }
434 
435 }