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