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