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.getParentLink().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()))
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 Duration nowOnFirstEncounterOtherwiseAtInterval = active ? this.samplingInterval : Duration.ZERO;
255 scheduleSamplingInterval(gtu, lane, nowOnFirstEncounterOtherwiseAtInterval);
256 }
257 else
258 {
259 this.activeLanesPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashSet<>()).add(lane);
260 gtu.addListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT, ReferenceType.WEAK);
261 }
262 }
263 else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
264 {
265
266
267 Object[] payload = (Object[]) event.getContent();
268 Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[5]))
269 .getCrossSectionElement((String) payload[4]);
270 LaneDataRoad laneData = new LaneDataRoad(lane);
271 LaneBasedGtu gtu = (LaneBasedGtu) payload[1];
272 Length position = (Length) payload[3];
273 Speed speed = gtu.getSpeed();
274 Acceleration acceleration = gtu.getAcceleration();
275
276 processGtuRemoveEventWithMove(laneData, position, speed, acceleration, now(), new GtuDataRoad(gtu));
277
278 if (isIntervalBased())
279 {
280 Map<Lane, SimEventInterface<Duration>> events = this.eventsPerGtu.get(gtu.getId());
281 SimEventInterface<Duration> e = events.remove(lane);
282 if (e != null)
283 {
284 this.simulator.cancelEvent(e);
285 }
286 if (events.isEmpty())
287 {
288 this.eventsPerGtu.remove(gtu.getId());
289 this.activeGtus.remove(gtu.getId());
290 }
291 }
292 else
293 {
294 this.activeLanesPerGtu.get(gtu.getId()).remove(lane);
295 if (this.activeLanesPerGtu.get(gtu.getId()).isEmpty())
296 {
297 this.activeLanesPerGtu.remove(gtu.getId());
298 gtu.removeListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT);
299 this.activeGtus.remove(gtu.getId());
300 }
301 }
302 }
303 }
304
305
306
307
308 private boolean isIntervalBased()
309 {
310 return this.samplingInterval != null;
311 }
312
313
314
315
316
317
318
319 private void scheduleSamplingInterval(final LaneBasedGtu gtu, final Lane lane, final Duration inTime)
320 {
321 SimEventInterface<Duration> simEvent;
322 try
323 {
324 simEvent = this.simulator.scheduleEventRel(inTime, this, "notifySample", new Object[] {gtu, lane});
325 }
326 catch (SimRuntimeException exception)
327 {
328
329 throw new RuntimeException("Scheduling sampling in the past.", exception);
330 }
331 this.eventsPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashMap<>()).put(lane, simEvent);
332 }
333
334
335
336
337
338
339 public final void notifySample(final LaneBasedGtu gtu, final Lane lane)
340 {
341 LaneDataRoad laneData = new LaneDataRoad(lane);
342 try
343 {
344 Length position = gtu.position(lane, RelativePosition.REFERENCE_POSITION);
345 if (this.activeGtus.contains(gtu.getId()))
346 {
347
348 processGtuMoveEvent(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(), new GtuDataRoad(gtu));
349 }
350 else
351 {
352
353 processGtuAddEventWithMove(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(),
354 new GtuDataRoad(gtu));
355 this.activeGtus.add(gtu.getId());
356 }
357 }
358 catch (GtuException exception)
359 {
360 throw new RuntimeException("Requesting position on lane, but the GTU is not on the lane.", exception);
361 }
362 scheduleSamplingInterval(gtu, lane, this.samplingInterval);
363 }
364
365
366 @Override
367 public final int hashCode()
368 {
369 final int prime = 31;
370 int result = super.hashCode();
371 result = prime * result + ((this.eventsPerGtu == null) ? 0 : this.eventsPerGtu.hashCode());
372 result = prime * result + ((this.samplingInterval == null) ? 0 : this.samplingInterval.hashCode());
373 result = prime * result + ((this.simulator == null) ? 0 : this.simulator.hashCode());
374 return result;
375 }
376
377
378 @Override
379 public final boolean equals(final Object obj)
380 {
381 if (this == obj)
382 {
383 return true;
384 }
385 if (!super.equals(obj))
386 {
387 return false;
388 }
389 if (getClass() != obj.getClass())
390 {
391 return false;
392 }
393 RoadSampler other = (RoadSampler) obj;
394 if (this.eventsPerGtu == null)
395 {
396 if (other.eventsPerGtu != null)
397 {
398 return false;
399 }
400 }
401 else if (!this.eventsPerGtu.equals(other.eventsPerGtu))
402 {
403 return false;
404 }
405 if (this.samplingInterval == null)
406 {
407 if (other.samplingInterval != null)
408 {
409 return false;
410 }
411 }
412 else if (!this.samplingInterval.equals(other.samplingInterval))
413 {
414 return false;
415 }
416 if (this.simulator == null)
417 {
418 if (other.simulator != null)
419 {
420 return false;
421 }
422 }
423 else if (!this.simulator.equals(other.simulator))
424 {
425 return false;
426 }
427 return true;
428 }
429
430
431 @Override
432 public final String toString()
433 {
434 return "RoadSampler [samplingInterval=" + this.samplingInterval + "]";
435
436 }
437
438
439
440
441
442
443 public static Factory build(final RoadNetwork network)
444 {
445 return new Factory(network);
446 }
447
448
449 public static final class Factory
450 {
451
452
453 private final RoadNetwork network;
454
455
456 private final Set<ExtendedDataType<?, ?, ?, GtuDataRoad>> extendedDataTypes = new LinkedHashSet<>();
457
458
459 private final Set<FilterDataType<?>> filterDataTypes = new LinkedHashSet<>();
460
461
462 private Frequency freq;
463
464
465
466
467
468 Factory(final RoadNetwork network)
469 {
470 this.network = network;
471 }
472
473
474
475
476
477
478 public Factory registerExtendedDataType(final ExtendedDataType<?, ?, ?, GtuDataRoad> extendedDataType)
479 {
480 Throw.whenNull(extendedDataType, "Extended data type may not be null.");
481 this.extendedDataTypes.add(extendedDataType);
482 return this;
483 }
484
485
486
487
488
489
490 public Factory registerFilterDataType(final FilterDataType<?> filterDataType)
491 {
492 Throw.whenNull(filterDataType, "Filter data type may not be null.");
493 this.filterDataTypes.add(filterDataType);
494 return this;
495 }
496
497
498
499
500
501
502 public Factory setFrequency(final Frequency frequency)
503 {
504 this.freq = frequency;
505 return this;
506 }
507
508
509
510
511
512 public RoadSampler create()
513 {
514 return this.freq == null ? new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network)
515 : new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network, this.freq);
516 }
517
518 }
519
520 }