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
38
39
40
41
42
43
44
45
46 public class RoadSampler extends Sampler<GtuDataRoad, LaneDataRoad> implements EventListener
47 {
48
49
50 private static final long serialVersionUID = 20200228L;
51
52
53 private final OtsSimulatorInterface simulator;
54
55
56 private final RoadNetwork network;
57
58
59 private final Duration samplingInterval;
60
61
62 private final Map<String, Map<Lane, SimEventInterface<Duration>>> eventsPerGtu = new LinkedHashMap<>();
63
64
65 private final Map<String, Set<Lane>> activeLanesPerGtu = new LinkedHashMap<>();
66
67
68 private final Set<String> activeGtus = new LinkedHashSet<>();
69
70
71
72
73
74
75 public RoadSampler(final RoadNetwork network)
76 {
77 this(new LinkedHashSet<>(), new LinkedHashSet<>(), network);
78 }
79
80
81
82
83
84
85
86
87 public RoadSampler(final Set<ExtendedDataType<?, ?, ?, ? super GtuDataRoad>> extendedDataTypes,
88 final Set<FilterDataType<?, ? super GtuDataRoad>> 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
99
100
101
102
103
104 public RoadSampler(final RoadNetwork network, final Frequency frequency)
105 {
106 this(new LinkedHashSet<>(), new LinkedHashSet<>(), network, frequency);
107 }
108
109
110
111
112
113
114
115
116
117
118 public RoadSampler(final Set<ExtendedDataType<?, ?, ?, ? super GtuDataRoad>> extendedDataTypes,
119 final Set<FilterDataType<?, ? super GtuDataRoad>> filterDataTypes, final RoadNetwork network,
120 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
133 @Override
134 public final Time now()
135 {
136 return this.simulator.getSimulatorAbsTime();
137 }
138
139
140 @Override
141 public final void scheduleStartRecording(final Time time, final LaneDataRoad lane)
142 {
143 try
144 {
145 this.simulator.scheduleEventAbsTime(time, this, "startRecording", new Object[] {lane});
146 }
147 catch (SimRuntimeException exception)
148 {
149 throw new RuntimeException("Cannot start recording.", exception);
150 }
151 }
152
153
154 @Override
155 public final void scheduleStopRecording(final Time time, final LaneDataRoad lane)
156 {
157 try
158 {
159 this.simulator.scheduleEventAbsTime(time, this, "stopRecording", new Object[] {lane});
160 }
161 catch (SimRuntimeException exception)
162 {
163 throw new RuntimeException("Cannot stop recording.", exception);
164 }
165 }
166
167
168 @Override
169 public final void initRecording(final LaneDataRoad lane)
170 {
171 Lane roadLane = lane.getLane();
172
173 roadLane.addListener(this, Lane.GTU_ADD_EVENT, ReferenceType.WEAK);
174 roadLane.addListener(this, Lane.GTU_REMOVE_EVENT, ReferenceType.WEAK);
175
176 int count = 1;
177 for (LaneBasedGtu gtu : roadLane.getGtuList())
178 {
179 try
180 {
181
182 notify(new TimedEvent<>(Lane.GTU_ADD_EVENT,
183 new Object[] {gtu.getId(), count, roadLane.getId(), roadLane.getLink().getId()},
184 gtu.getSimulator().getSimulatorTime()));
185 count++;
186 }
187 catch (Exception exception)
188 {
189 throw new RuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception);
190 }
191 }
192 }
193
194
195 @Override
196 public final void finalizeRecording(final LaneDataRoad lane)
197 {
198 Lane roadLane = lane.getLane();
199 roadLane.removeListener(this, Lane.GTU_ADD_EVENT);
200 roadLane.removeListener(this, Lane.GTU_REMOVE_EVENT);
201 }
202
203
204 @Override
205
206 public final void notify(final Event event) throws RemoteException
207 {
208 if (event.getType().equals(LaneBasedGtu.LANEBASED_MOVE_EVENT))
209 {
210
211
212
213 Object[] payload = (Object[]) event.getContent();
214 CrossSectionLink link = (CrossSectionLink) this.network.getLink(payload[7].toString());
215 Lane lane = (Lane) link.getCrossSectionElement(payload[8].toString());
216 LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU(payload[0].toString());
217 LaneDataRoad laneData = new LaneDataRoad(lane);
218
219 if (!this.activeGtus.contains(gtu.getId()) && this.getSamplerData().contains(laneData))
220 {
221
222 processGtuAddEvent(laneData, new GtuDataRoad(gtu));
223 this.activeGtus.add(gtu.getId());
224 }
225 processGtuMoveEvent(laneData, (Length) payload[9], (Speed) payload[3], (Acceleration) payload[4], now(),
226 new GtuDataRoad(gtu));
227 }
228 else if (event.getType().equals(Lane.GTU_ADD_EVENT))
229 {
230
231 Object[] payload = (Object[]) event.getContent();
232 Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[3]))
233 .getCrossSectionElement((String) payload[2]);
234 LaneDataRoad laneData = new LaneDataRoad(lane);
235 if (!getSamplerData().contains(laneData))
236 {
237 return;
238 }
239 LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU((String) payload[0]);
240
241
242
243 boolean active = this.activeGtus.contains(gtu.getId());
244 if (active)
245 {
246 Length position = Try.assign(() -> gtu.position(lane, RelativePosition.REFERENCE_POSITION),
247 "Could not determine position.");
248 Speed speed = gtu.getSpeed();
249 Acceleration acceleration = gtu.getAcceleration();
250 processGtuAddEventWithMove(laneData, position, speed, acceleration, now(), new GtuDataRoad(gtu));
251 }
252
253 if (isIntervalBased())
254 {
255 double currentTime = now().getSI();
256 double next = Math.ceil(currentTime / this.samplingInterval.getSI()) * this.samplingInterval.getSI();
257 if (next > currentTime)
258 {
259
260 notifySample(gtu, lane, false);
261 scheduleSamplingInterval(gtu, lane, Duration.instantiateSI(next - currentTime));
262 }
263 else
264 {
265
266 notifySample(gtu, lane, true);
267 }
268 }
269 else
270 {
271 this.activeLanesPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashSet<>()).add(lane);
272 gtu.addListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT, ReferenceType.WEAK);
273 }
274 }
275 else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
276 {
277
278
279 Object[] payload = (Object[]) event.getContent();
280 Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[5]))
281 .getCrossSectionElement((String) payload[4]);
282 LaneDataRoad laneData = new LaneDataRoad(lane);
283 LaneBasedGtu gtu = (LaneBasedGtu) payload[1];
284 Length position = (Length) payload[3];
285 Speed speed = gtu.getSpeed();
286 Acceleration acceleration = gtu.getAcceleration();
287
288 processGtuRemoveEventWithMove(laneData, position, speed, acceleration, now(), new GtuDataRoad(gtu));
289
290 if (isIntervalBased())
291 {
292 Map<Lane, SimEventInterface<Duration>> events = this.eventsPerGtu.get(gtu.getId());
293 SimEventInterface<Duration> e = events.remove(lane);
294 if (e != null)
295 {
296 this.simulator.cancelEvent(e);
297 }
298 if (events.isEmpty())
299 {
300 this.eventsPerGtu.remove(gtu.getId());
301 this.activeGtus.remove(gtu.getId());
302 }
303 }
304 else
305 {
306 this.activeLanesPerGtu.get(gtu.getId()).remove(lane);
307 if (this.activeLanesPerGtu.get(gtu.getId()).isEmpty())
308 {
309 this.activeLanesPerGtu.remove(gtu.getId());
310 gtu.removeListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT);
311 this.activeGtus.remove(gtu.getId());
312 }
313 }
314 }
315 }
316
317
318
319
320 private boolean isIntervalBased()
321 {
322 return this.samplingInterval != null;
323 }
324
325
326
327
328
329
330
331 private void scheduleSamplingInterval(final LaneBasedGtu gtu, final Lane lane, final Duration inTime)
332 {
333 SimEventInterface<Duration> simEvent;
334 try
335 {
336 simEvent = this.simulator.scheduleEventRel(inTime, this, "notifySample", new Object[] {gtu, lane, true});
337 }
338 catch (SimRuntimeException exception)
339 {
340
341 throw new RuntimeException("Scheduling sampling in the past.", exception);
342 }
343 this.eventsPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashMap<>()).put(lane, simEvent);
344 }
345
346
347
348
349
350
351
352 public final void notifySample(final LaneBasedGtu gtu, final Lane lane, final boolean scheduleNext)
353 {
354 LaneDataRoad laneData = new LaneDataRoad(lane);
355 try
356 {
357 Length position = gtu.position(lane, RelativePosition.REFERENCE_POSITION);
358 if (this.activeGtus.contains(gtu.getId()))
359 {
360
361 processGtuMoveEvent(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(), new GtuDataRoad(gtu));
362 }
363 else
364 {
365
366 processGtuAddEventWithMove(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(),
367 new GtuDataRoad(gtu));
368 this.activeGtus.add(gtu.getId());
369 }
370 }
371 catch (GtuException exception)
372 {
373 throw new RuntimeException("Requesting position on lane, but the GTU is not on the lane.", exception);
374 }
375 if (scheduleNext)
376 {
377 scheduleSamplingInterval(gtu, lane, this.samplingInterval);
378 }
379 }
380
381
382 @Override
383 public final int hashCode()
384 {
385 final int prime = 31;
386 int result = super.hashCode();
387 result = prime * result + ((this.eventsPerGtu == null) ? 0 : this.eventsPerGtu.hashCode());
388 result = prime * result + ((this.samplingInterval == null) ? 0 : this.samplingInterval.hashCode());
389 result = prime * result + ((this.simulator == null) ? 0 : this.simulator.hashCode());
390 return result;
391 }
392
393
394 @Override
395 public final boolean equals(final Object obj)
396 {
397 if (this == obj)
398 {
399 return true;
400 }
401 if (!super.equals(obj))
402 {
403 return false;
404 }
405 if (getClass() != obj.getClass())
406 {
407 return false;
408 }
409 RoadSampler other = (RoadSampler) obj;
410 if (this.eventsPerGtu == null)
411 {
412 if (other.eventsPerGtu != null)
413 {
414 return false;
415 }
416 }
417 else if (!this.eventsPerGtu.equals(other.eventsPerGtu))
418 {
419 return false;
420 }
421 if (this.samplingInterval == null)
422 {
423 if (other.samplingInterval != null)
424 {
425 return false;
426 }
427 }
428 else if (!this.samplingInterval.equals(other.samplingInterval))
429 {
430 return false;
431 }
432 if (this.simulator == null)
433 {
434 if (other.simulator != null)
435 {
436 return false;
437 }
438 }
439 else if (!this.simulator.equals(other.simulator))
440 {
441 return false;
442 }
443 return true;
444 }
445
446
447 @Override
448 public final String toString()
449 {
450 return "RoadSampler [samplingInterval=" + this.samplingInterval + "]";
451
452 }
453
454
455
456
457
458
459 public static Factory build(final RoadNetwork network)
460 {
461 return new Factory(network);
462 }
463
464
465 public static final class Factory
466 {
467
468
469 private final RoadNetwork network;
470
471
472 private final Set<ExtendedDataType<?, ?, ?, ? super GtuDataRoad>> extendedDataTypes = new LinkedHashSet<>();
473
474
475 private final Set<FilterDataType<?, ? super GtuDataRoad>> filterDataTypes = new LinkedHashSet<>();
476
477
478 private Frequency freq;
479
480
481
482
483
484 Factory(final RoadNetwork network)
485 {
486 this.network = network;
487 }
488
489
490
491
492
493
494 public Factory registerExtendedDataType(final ExtendedDataType<?, ?, ?, ? super GtuDataRoad> extendedDataType)
495 {
496 Throw.whenNull(extendedDataType, "Extended data type may not be null.");
497 this.extendedDataTypes.add(extendedDataType);
498 return this;
499 }
500
501
502
503
504
505
506 public Factory registerFilterDataType(final FilterDataType<?, ? super GtuDataRoad> filterDataType)
507 {
508 Throw.whenNull(filterDataType, "Filter data type may not be null.");
509 this.filterDataTypes.add(filterDataType);
510 return this;
511 }
512
513
514
515
516
517
518 public Factory setFrequency(final Frequency frequency)
519 {
520 this.freq = frequency;
521 return this;
522 }
523
524
525
526
527
528 public RoadSampler create()
529 {
530 return this.freq == null ? new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network)
531 : new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network, this.freq);
532 }
533
534 }
535
536 }