1 package org.opentrafficsim.road.network.sampling;
2
3 import java.util.LinkedHashMap;
4 import java.util.LinkedHashSet;
5 import java.util.Map;
6 import java.util.Objects;
7 import java.util.Set;
8 import java.util.UUID;
9
10 import org.djunits.unit.DurationUnit;
11 import org.djunits.value.vdouble.scalar.Acceleration;
12 import org.djunits.value.vdouble.scalar.Duration;
13 import org.djunits.value.vdouble.scalar.Frequency;
14 import org.djunits.value.vdouble.scalar.Length;
15 import org.djunits.value.vdouble.scalar.Speed;
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.opentrafficsim.base.OtsRuntimeException;
22 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
23 import org.opentrafficsim.core.gtu.RelativePosition;
24 import org.opentrafficsim.kpi.sampling.Sampler;
25 import org.opentrafficsim.kpi.sampling.data.ExtendedDataType;
26 import org.opentrafficsim.kpi.sampling.filter.FilterDataType;
27 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
28 import org.opentrafficsim.road.network.RoadNetwork;
29 import org.opentrafficsim.road.network.lane.CrossSectionLink;
30 import org.opentrafficsim.road.network.lane.Lane;
31
32 import nl.tudelft.simulation.dsol.SimRuntimeException;
33 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
34
35
36
37
38
39
40
41
42
43
44
45 public class RoadSampler extends Sampler<GtuDataRoad, LaneDataRoad> implements EventListener
46 {
47
48
49 private final OtsSimulatorInterface simulator;
50
51
52 private final RoadNetwork network;
53
54
55 private final Duration samplingInterval;
56
57
58 private final Map<String, Map<Lane, SimEventInterface<Duration>>> eventsPerGtu = new LinkedHashMap<>();
59
60
61 private final Map<String, Set<Lane>> activeLanesPerGtu = new LinkedHashMap<>();
62
63
64 private final UUID uuid = UUID.randomUUID();
65
66
67
68
69
70
71 public RoadSampler(final RoadNetwork network)
72 {
73 this(new LinkedHashSet<>(), new LinkedHashSet<>(), network);
74 }
75
76
77
78
79
80
81
82
83 public RoadSampler(final Set<ExtendedDataType<?, ?, ?, ? super GtuDataRoad>> extendedDataTypes,
84 final Set<FilterDataType<?, ? super GtuDataRoad>> filterDataTypes, final RoadNetwork network)
85 {
86 super(extendedDataTypes, filterDataTypes);
87 Throw.whenNull(network, "Network may not be null.");
88 this.network = network;
89 this.simulator = network.getSimulator();
90 this.samplingInterval = null;
91 }
92
93
94
95
96
97
98
99
100 public RoadSampler(final RoadNetwork network, final Frequency frequency)
101 {
102 this(new LinkedHashSet<>(), new LinkedHashSet<>(), network, frequency);
103 }
104
105
106
107
108
109
110
111
112
113
114 public RoadSampler(final Set<ExtendedDataType<?, ?, ?, ? super GtuDataRoad>> extendedDataTypes,
115 final Set<FilterDataType<?, ? super GtuDataRoad>> filterDataTypes, final RoadNetwork network,
116 final Frequency frequency)
117 {
118 super(extendedDataTypes, filterDataTypes);
119 Throw.whenNull(network, "Network may not be null.");
120 Throw.whenNull(frequency, "Frequency may not be null.");
121 Throw.when(frequency.le(Frequency.ZERO), IllegalArgumentException.class,
122 "Negative or zero sampling frequency is not permitted.");
123 this.network = network;
124 this.simulator = network.getSimulator();
125 this.samplingInterval = new Duration(1.0 / frequency.si, DurationUnit.SI);
126 }
127
128 @Override
129 public final Duration now()
130 {
131 return this.simulator.getSimulatorTime();
132 }
133
134 @Override
135 public final void scheduleStartRecording(final Duration time, final LaneDataRoad lane)
136 {
137 try
138 {
139 this.simulator.scheduleEventAbs(Duration.ofSI(time.si), () -> startRecording(lane));
140 }
141 catch (SimRuntimeException exception)
142 {
143 throw new OtsRuntimeException("Cannot start recording.", exception);
144 }
145 }
146
147 @Override
148 public final void scheduleStopRecording(final Duration time, final LaneDataRoad lane)
149 {
150 try
151 {
152 this.simulator.scheduleEventAbs(Duration.ofSI(time.si), () -> stopRecording(lane));
153 }
154 catch (SimRuntimeException exception)
155 {
156 throw new OtsRuntimeException("Cannot stop recording.", exception);
157 }
158 }
159
160 @Override
161 public final void initRecording(final LaneDataRoad lane)
162 {
163 Lane roadLane = lane.getLane();
164
165 roadLane.addListener(this, Lane.GTU_ADD_EVENT, ReferenceType.WEAK);
166 roadLane.addListener(this, Lane.GTU_REMOVE_EVENT, ReferenceType.WEAK);
167
168 int count = 1;
169 for (LaneBasedGtu gtu : roadLane.getGtuList())
170 {
171 try
172 {
173
174 notify(new TimedEvent<>(Lane.GTU_ADD_EVENT,
175 new Object[] {gtu.getId(), count, roadLane.getId(), roadLane.getLink().getId()},
176 gtu.getSimulator().getSimulatorTime()));
177 count++;
178 }
179 catch (Exception exception)
180 {
181 throw new OtsRuntimeException("Position cannot be obtained for GTU that is registered on a lane", exception);
182 }
183 }
184 }
185
186 @Override
187 public final void finalizeRecording(final LaneDataRoad lane)
188 {
189 Lane roadLane = lane.getLane();
190 roadLane.removeListener(this, Lane.GTU_ADD_EVENT);
191 roadLane.removeListener(this, Lane.GTU_REMOVE_EVENT);
192 }
193
194 @Override
195
196 public final void notify(final Event event)
197 {
198 if (event.getType().equals(LaneBasedGtu.LANEBASED_MOVE_EVENT))
199 {
200
201
202
203 Object[] payload = (Object[]) event.getContent();
204 CrossSectionLink link = (CrossSectionLink) this.network.getLink(payload[7].toString())
205 .orElseThrow(() -> new OtsRuntimeException("Payload refers to non-existent link."));
206 Lane lane = (Lane) link.getCrossSectionElement(payload[8].toString()).orElseThrow();
207 LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU(payload[0].toString())
208 .orElseThrow(() -> new OtsRuntimeException("Sampling of GTU not in the network."));
209 LaneDataRoad laneData = new LaneDataRoad(lane);
210 snapshot(laneData, (Length) payload[9], (Speed) payload[3], (Acceleration) payload[4], now(), new GtuDataRoad(gtu));
211 }
212 else if (event.getType().equals(Lane.GTU_ADD_EVENT))
213 {
214
215 Object[] payload = (Object[]) event.getContent();
216 Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[3])
217 .orElseThrow(() -> new OtsRuntimeException("Payload refers to non-existent link.")))
218 .getCrossSectionElement((String) payload[2]).orElseThrow();
219 LaneDataRoad laneData = new LaneDataRoad(lane);
220 if (!getSamplerData().contains(laneData))
221 {
222 return;
223 }
224 LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU((String) payload[0])
225 .orElseThrow(() -> new OtsRuntimeException("Sampling of GTU not in the network."));
226
227 Length position = gtu.getPosition(lane, RelativePosition.REFERENCE_POSITION);
228 GtuDataRoad gtuData = new GtuDataRoad(gtu);
229 addGtuWithSnapshot(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(), gtuData);
230
231 if (isIntervalBased())
232 {
233 double currentTime = now().getSI();
234 int steps = (int) Math.ceil(currentTime / this.samplingInterval.getSI());
235
236 scheduleSamplingInterval(gtu, lane, steps + (steps * this.samplingInterval.getSI() > currentTime ? 0 : 1));
237 }
238 else
239 {
240 Set<Lane> lanes = this.activeLanesPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashSet<>());
241 if (lanes.isEmpty())
242 {
243 gtu.addListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT, ReferenceType.WEAK);
244 }
245 lanes.add(lane);
246 }
247 }
248 else if (event.getType().equals(Lane.GTU_REMOVE_EVENT))
249 {
250
251
252 Object[] payload = (Object[]) event.getContent();
253 Lane lane = (Lane) ((CrossSectionLink) this.network.getLink((String) payload[5])
254 .orElseThrow(() -> new OtsRuntimeException("Payload refers to non-existent link.")))
255 .getCrossSectionElement((String) payload[4]).orElseThrow();
256 LaneDataRoad laneData = new LaneDataRoad(lane);
257 LaneBasedGtu gtu = (LaneBasedGtu) payload[1];
258 Length position = (Length) payload[3];
259 Speed speed = gtu.getSpeed();
260 Acceleration acceleration = gtu.getAcceleration();
261
262 removeGtuWithSnapshot(laneData, position, speed, acceleration, now(), new GtuDataRoad(gtu));
263
264 if (isIntervalBased())
265 {
266 Map<Lane, SimEventInterface<Duration>> events = this.eventsPerGtu.get(gtu.getId());
267 SimEventInterface<Duration> e = events.remove(lane);
268 if (e != null)
269 {
270 this.simulator.cancelEvent(e);
271 }
272 if (events.isEmpty())
273 {
274 this.eventsPerGtu.remove(gtu.getId());
275 }
276 }
277 else
278 {
279 Set<Lane> lanes = this.activeLanesPerGtu.get(gtu.getId());
280 lanes.remove(lane);
281 if (lanes.isEmpty())
282 {
283 this.activeLanesPerGtu.remove(gtu.getId());
284 gtu.removeListener(this, LaneBasedGtu.LANEBASED_MOVE_EVENT);
285 }
286 }
287 }
288 }
289
290
291
292
293 private boolean isIntervalBased()
294 {
295 return this.samplingInterval != null;
296 }
297
298
299
300
301
302
303
304 private void scheduleSamplingInterval(final LaneBasedGtu gtu, final Lane lane, final int steps)
305 {
306 SimEventInterface<Duration> simEvent;
307 try
308 {
309 simEvent =
310 this.simulator.scheduleEventAbs(this.samplingInterval.times(steps), () -> notifySample(gtu, lane, steps));
311 }
312 catch (SimRuntimeException exception)
313 {
314
315 throw new OtsRuntimeException("Scheduling sampling in the past.", exception);
316 }
317 this.eventsPerGtu.computeIfAbsent(gtu.getId(), (key) -> new LinkedHashMap<>()).put(lane, simEvent);
318 }
319
320
321
322
323
324
325
326 private void notifySample(final LaneBasedGtu gtu, final Lane lane, final int steps)
327 {
328 LaneDataRoad laneData = new LaneDataRoad(lane);
329 Length position = gtu.getPosition(lane, RelativePosition.REFERENCE_POSITION);
330 snapshot(laneData, position, gtu.getSpeed(), gtu.getAcceleration(), now(), new GtuDataRoad(gtu));
331 scheduleSamplingInterval(gtu, lane, steps + 1);
332 }
333
334 @Override
335 public int hashCode()
336 {
337 final int prime = 31;
338 int result = super.hashCode();
339 result = prime * result + Objects.hash(this.uuid);
340 return result;
341 }
342
343 @Override
344 public boolean equals(final Object obj)
345 {
346 if (this == obj)
347 {
348 return true;
349 }
350 if (!super.equals(obj))
351 {
352 return false;
353 }
354 if (getClass() != obj.getClass())
355 {
356 return false;
357 }
358 RoadSampler other = (RoadSampler) obj;
359 return Objects.equals(this.uuid, other.uuid);
360 }
361
362 @Override
363 public final String toString()
364 {
365 return "RoadSampler [samplingInterval=" + this.samplingInterval + "]";
366 }
367
368
369
370
371
372
373 public static Factory build(final RoadNetwork network)
374 {
375 return new Factory(network);
376 }
377
378
379 public static final class Factory
380 {
381
382
383 private final RoadNetwork network;
384
385
386 private final Set<ExtendedDataType<?, ?, ?, ? super GtuDataRoad>> extendedDataTypes = new LinkedHashSet<>();
387
388
389 private final Set<FilterDataType<?, ? super GtuDataRoad>> filterDataTypes = new LinkedHashSet<>();
390
391
392 private Frequency freq;
393
394
395
396
397
398 Factory(final RoadNetwork network)
399 {
400 this.network = network;
401 }
402
403
404
405
406
407
408 public Factory registerExtendedDataType(final ExtendedDataType<?, ?, ?, ? super GtuDataRoad> extendedDataType)
409 {
410 Throw.whenNull(extendedDataType, "Extended data type may not be null.");
411 this.extendedDataTypes.add(extendedDataType);
412 return this;
413 }
414
415
416
417
418
419
420 public Factory registerFilterDataType(final FilterDataType<?, ? super GtuDataRoad> filterDataType)
421 {
422 Throw.whenNull(filterDataType, "Filter data type may not be null.");
423 this.filterDataTypes.add(filterDataType);
424 return this;
425 }
426
427
428
429
430
431
432 public Factory setFrequency(final Frequency frequency)
433 {
434 this.freq = frequency;
435 return this;
436 }
437
438
439
440
441
442 public RoadSampler create()
443 {
444 return this.freq == null ? new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network)
445 : new RoadSampler(this.extendedDataTypes, this.filterDataTypes, this.network, this.freq);
446 }
447
448 }
449
450 }