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