multimodalsim-viewer 0.0.1__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. multimodalsim_viewer/common/__init__.py +0 -0
  2. multimodalsim_viewer/common/environments/.env +4 -0
  3. multimodalsim_viewer/common/utils.py +223 -0
  4. multimodalsim_viewer/server/http_routes.py +135 -125
  5. multimodalsim_viewer/server/log_manager.py +10 -15
  6. multimodalsim_viewer/server/scripts.py +106 -32
  7. multimodalsim_viewer/server/server.py +196 -210
  8. multimodalsim_viewer/server/simulation.py +167 -154
  9. multimodalsim_viewer/server/simulation_manager.py +570 -607
  10. multimodalsim_viewer/server/simulation_visualization_data_collector.py +729 -756
  11. multimodalsim_viewer/server/simulation_visualization_data_model.py +1570 -1693
  12. multimodalsim_viewer/ui/angular_app.py +40 -0
  13. multimodalsim_viewer/ui/static/chunk-BQ2VC5TN.js +7 -0
  14. multimodalsim_viewer/ui/static/{chunk-MTC2LSCT.js → chunk-RHGMGEGM.js} +1 -1
  15. multimodalsim_viewer/ui/static/environment.json +7 -0
  16. multimodalsim_viewer/ui/static/images/undefined-texture.png +0 -0
  17. multimodalsim_viewer/ui/static/images/zoomed-out-stop.png +0 -0
  18. multimodalsim_viewer/ui/static/index.html +16 -15
  19. multimodalsim_viewer/ui/static/main-LUPJCMAF.js +3648 -0
  20. multimodalsim_viewer/ui/static/polyfills-FFHMD2TL.js +2 -2
  21. multimodalsim_viewer/ui/static/scripts/load-environment.script.js +20 -0
  22. multimodalsim_viewer/ui/static/styles-KU7LTPET.css +1 -1
  23. multimodalsim_viewer-0.0.3.dist-info/METADATA +70 -0
  24. multimodalsim_viewer-0.0.3.dist-info/RECORD +43 -0
  25. {multimodalsim_viewer-0.0.1.dist-info → multimodalsim_viewer-0.0.3.dist-info}/WHEEL +1 -1
  26. multimodalsim_viewer-0.0.3.dist-info/entry_points.txt +2 -0
  27. multimodalsim_viewer/server/server_utils.py +0 -129
  28. multimodalsim_viewer/ui/cli.py +0 -45
  29. multimodalsim_viewer/ui/server.py +0 -44
  30. multimodalsim_viewer/ui/static/chunk-U5CGW4P4.js +0 -7
  31. multimodalsim_viewer/ui/static/main-X7OVCS3N.js +0 -3648
  32. multimodalsim_viewer-0.0.1.dist-info/METADATA +0 -21
  33. multimodalsim_viewer-0.0.1.dist-info/RECORD +0 -38
  34. multimodalsim_viewer-0.0.1.dist-info/entry_points.txt +0 -8
  35. /multimodalsim_viewer/ui/static/images/{sample-wait.png → passenger.png} +0 -0
  36. /multimodalsim_viewer/ui/static/images/{sample-stop.png → stop.png} +0 -0
  37. /multimodalsim_viewer/ui/static/images/{sample-bus.png → vehicle.png} +0 -0
  38. /multimodalsim_viewer/ui/static/images/{zoom-out-passenger.png → zoomed-out-passenger.png} +0 -0
  39. /multimodalsim_viewer/ui/static/images/{zoom-out-vehicle.png → zoomed-out-vehicle.png} +0 -0
  40. {multimodalsim_viewer-0.0.1.dist-info → multimodalsim_viewer-0.0.3.dist-info}/top_level.txt +0 -0
@@ -1,756 +1,729 @@
1
- import threading
2
- from typing import Optional
3
-
4
- from multimodalsim.observer.data_collector import DataCollector
5
- from multimodalsim.simulator.environment import Environment
6
- from multimodalsim.simulator.event import Event, RecurrentTimeSyncEvent
7
- from multimodalsim.simulator.optimization_event import (
8
- EnvironmentIdle,
9
- EnvironmentUpdate,
10
- Hold,
11
- Optimize,
12
- )
13
- from multimodalsim.simulator.passenger_event import (
14
- PassengerAlighting,
15
- PassengerAssignment,
16
- PassengerReady,
17
- PassengerRelease,
18
- PassengerToBoard,
19
- )
20
- from multimodalsim.simulator.simulation import Simulation
21
- from multimodalsim.simulator.vehicle import Vehicle
22
- from multimodalsim.simulator.vehicle_event import (
23
- VehicleAlighted,
24
- VehicleArrival,
25
- VehicleBoarded,
26
- VehicleBoarding,
27
- VehicleComplete,
28
- VehicleDeparture,
29
- VehicleNotification,
30
- VehicleReady,
31
- VehicleUpdatePositionEvent,
32
- VehicleWaiting,
33
- )
34
- from multimodalsim.statistics.data_analyzer import DataAnalyzer
35
- from multimodalsim_viewer.server.log_manager import register_log
36
- from multimodalsim_viewer.server.server_utils import (
37
- HOST,
38
- PORT,
39
- STATE_SAVE_STEP,
40
- SimulationStatus,
41
- build_simulation_id,
42
- )
43
- from multimodalsim_viewer.server.simulation_visualization_data_model import (
44
- PassengerLegsUpdate,
45
- PassengerStatusUpdate,
46
- SimulationInformation,
47
- SimulationVisualizationDataManager,
48
- StatisticUpdate,
49
- Update,
50
- UpdateType,
51
- VehicleStatusUpdate,
52
- VehicleStopsUpdate,
53
- VisualizedEnvironment,
54
- VisualizedPassenger,
55
- VisualizedStop,
56
- VisualizedVehicle,
57
- )
58
- from socketio import Client
59
-
60
-
61
- # MARK: Data Collector
62
- class SimulationVisualizationDataCollector(DataCollector):
63
- simulation_id: str
64
- update_counter: int
65
- visualized_environment: VisualizedEnvironment
66
- simulation_information: SimulationInformation
67
- current_save_file_path: str
68
-
69
- max_duration: float | None
70
- "Maximum duration of the simulation in in-simulation time (seconds). The simulation will stop if it exceeds this duration."
71
-
72
- # Special events
73
- last_queued_event_time: float
74
- passenger_assignment_event_queue: list[PassengerAssignment]
75
- vehicle_notification_event_queue: list[VehicleNotification]
76
-
77
- # Statistics
78
- data_analyzer: DataAnalyzer
79
- statistics_delta_time: int
80
- last_statistics_update_time: int
81
-
82
- # Communication
83
- sio: Client | None = None
84
- stop_event: threading.Event | None = None
85
- connection_thread: threading.Thread | None = None
86
- _simulation: Simulation | None = None
87
- status: SimulationStatus | None = None
88
-
89
- # Polylines
90
- saved_polylines_coordinates_pairs: set[str] = set()
91
-
92
- # Estimated end time
93
- last_estimated_end_time: float | None = None
94
-
95
- def __init__(
96
- self,
97
- data_analyzer: DataAnalyzer,
98
- statistics_delta_time: int = 10,
99
- name: str = "simulation",
100
- input_data_description: str = "unknown",
101
- simulation_id: str | None = None,
102
- max_duration: float | None = None,
103
- offline: bool = False,
104
- stop_event: threading.Event | None = None,
105
- ) -> None:
106
- if simulation_id is None:
107
- simulation_id, _ = build_simulation_id(name)
108
-
109
- self.simulation_id = simulation_id
110
- self.update_counter = 0
111
- self.visualized_environment = VisualizedEnvironment()
112
-
113
- self.simulation_information = SimulationInformation(
114
- simulation_id, input_data_description, None, None, None, None
115
- )
116
-
117
- self.current_save_file_path = None
118
-
119
- self.max_duration = max_duration
120
-
121
- self.passenger_assignment_event_queue = []
122
- self.vehicle_notification_event_queue = []
123
- self.last_queued_event_time = 0
124
-
125
- self.data_analyzer = data_analyzer
126
- self.statistics_delta_time = statistics_delta_time
127
- self.last_statistics_update_time = None
128
-
129
- self.stop_event = stop_event
130
-
131
- if not offline:
132
- self.initialize_communication()
133
-
134
- @property
135
- def isConnected(self) -> bool:
136
- return self.sio is not None and self.sio.connected
137
-
138
- # MARK: +- Communication
139
- def initialize_communication(self) -> None:
140
- sio = Client(reconnection_attempts=1)
141
-
142
- self.sio = sio
143
- self.status = SimulationStatus.RUNNING
144
-
145
- @sio.on("pause-simulation")
146
- def pauseSimulator():
147
- if self._simulation is not None:
148
- self._simulation.pause()
149
- self.status = SimulationStatus.PAUSED
150
- if self.isConnected:
151
- self.sio.emit("simulation-pause", self.simulation_id)
152
-
153
- @sio.on("resume-simulation")
154
- def resumeSimulator():
155
- if self._simulation is not None:
156
- self._simulation.resume()
157
- self.status = SimulationStatus.RUNNING
158
- if self.isConnected:
159
- self.sio.emit("simulation-resume", self.simulation_id)
160
-
161
- @sio.on("stop-simulation")
162
- def stopSimulator():
163
- if self._simulation is not None:
164
- self._simulation.stop()
165
- self.status = SimulationStatus.STOPPING
166
-
167
- @sio.on("connect")
168
- def on_connect():
169
- sio.emit(
170
- "simulation-identification",
171
- (
172
- self.simulation_id,
173
- self.simulation_information.data,
174
- self.simulation_information.simulation_start_time,
175
- self.visualized_environment.timestamp,
176
- self.visualized_environment.estimated_end_time,
177
- self.max_duration,
178
- self.status.value,
179
- ),
180
- )
181
-
182
- @sio.on("edit-simulation-configuration")
183
- def on_edit_simulation_configuration(max_duration: float | None):
184
- self.max_duration = max_duration
185
-
186
- if self.last_estimated_end_time is None:
187
- return
188
-
189
- # Notify the server if the estimated end time has changed
190
- new_estimated_end_time = min(
191
- self.last_estimated_end_time,
192
- (
193
- self.simulation_information.simulation_start_time
194
- + self.max_duration
195
- if self.max_duration is not None
196
- else self.last_estimated_end_time
197
- ),
198
- )
199
-
200
- if new_estimated_end_time != self.visualized_environment.estimated_end_time:
201
- self.sio.emit(
202
- "simulation-update-estimated-end-time",
203
- (self.simulation_id, new_estimated_end_time),
204
- )
205
- self.visualized_environment.estimated_end_time = new_estimated_end_time
206
-
207
- if self.stop_event is None:
208
- self.stop_event = threading.Event()
209
-
210
- self.connection_thread = threading.Thread(target=self.handle_connection)
211
- self.connection_thread.start()
212
-
213
- def handle_connection(self) -> None:
214
- while not self.stop_event.is_set():
215
-
216
- if not self.sio.connected:
217
- try:
218
- print("Trying to reconnect")
219
- self.sio.connect(
220
- f"http://{HOST}:{PORT}", auth={"type": "simulation"}
221
- )
222
- print("Connected")
223
- except Exception as e:
224
- print(f"Failed to connect to server: {e}")
225
- print("Continuing in offline mode")
226
-
227
- self.sio.sleep(5) # Check every 5 seconds
228
-
229
- self.sio.disconnect()
230
- self.sio.wait()
231
-
232
- # MARK: +- Collect
233
- def collect(
234
- self,
235
- env: Environment,
236
- current_event: Optional[Event] = None,
237
- event_index: Optional[int] = None,
238
- event_priority: Optional[int] = None,
239
- ) -> None:
240
- env.simulation_config.max_time = (
241
- (
242
- (
243
- self.simulation_information.simulation_start_time
244
- if self.simulation_information.simulation_start_time is not None
245
- else env.current_time
246
- )
247
- + self.max_duration
248
- )
249
- if self.max_duration is not None
250
- else env.simulation_config.max_time
251
- )
252
-
253
- if current_event is None:
254
- return
255
-
256
- message = self.process_event(current_event, env)
257
- register_log(self.simulation_id, message)
258
-
259
- if self.isConnected:
260
- self.sio.emit("log", (self.simulation_id, message))
261
-
262
- if (
263
- self.last_statistics_update_time == None
264
- or current_event.time
265
- >= self.last_statistics_update_time + self.statistics_delta_time
266
- ):
267
- self.last_statistics_update_time = current_event.time
268
- self.add_update(
269
- Update(
270
- UpdateType.UPDATE_STATISTIC,
271
- StatisticUpdate(self.data_analyzer.get_statistics()),
272
- current_event.time,
273
- ),
274
- env,
275
- )
276
-
277
- # MARK: +- Add Update
278
- def add_update(self, update: Update, environment: Environment) -> None:
279
- update.order = self.update_counter
280
- self.visualized_environment.order = self.update_counter
281
-
282
- if self.update_counter == 0:
283
- # Add the simulation start time to the simulation information
284
- self.simulation_information.simulation_start_time = update.timestamp
285
-
286
- # Save the simulation information
287
- SimulationVisualizationDataManager.set_simulation_information(
288
- self.simulation_id, self.simulation_information
289
- )
290
-
291
- # Notify the server that the simulation has started and send the simulation start time
292
- if self.isConnected:
293
- self.sio.emit(
294
- "simulation-start", (self.simulation_id, update.timestamp)
295
- )
296
-
297
- if self.visualized_environment.timestamp != update.timestamp:
298
- # Notify the server that the simulation time has been updated
299
- if self.isConnected:
300
- self.sio.emit(
301
- "simulation-update-time",
302
- (
303
- self.simulation_id,
304
- update.timestamp,
305
- ),
306
- )
307
- self.visualized_environment.timestamp = update.timestamp
308
-
309
- # Remember the last estimated end time in case of max_duration updates
310
- self.last_estimated_end_time = environment.estimated_end_time
311
- estimated_end_time = min(
312
- environment.estimated_end_time,
313
- (
314
- self.simulation_information.simulation_start_time + self.max_duration
315
- if self.max_duration is not None
316
- else environment.estimated_end_time
317
- ),
318
- )
319
- if estimated_end_time != self.visualized_environment.estimated_end_time:
320
- # Notify the server that the simulation estimated end time has been updated
321
- if self.isConnected:
322
- self.sio.emit(
323
- "simulation-update-estimated-end-time",
324
- (self.simulation_id, estimated_end_time),
325
- )
326
- self.visualized_environment.estimated_end_time = estimated_end_time
327
-
328
- # Save the state of the simulation every SAVE_STATE_STEP events before applying the update
329
- if self.update_counter % STATE_SAVE_STEP == 0:
330
- self.current_save_file_path = SimulationVisualizationDataManager.save_state(
331
- self.simulation_id, self.visualized_environment
332
- )
333
-
334
- if update.type == UpdateType.CREATE_PASSENGER:
335
- self.visualized_environment.add_passenger(update.data)
336
- elif update.type == UpdateType.CREATE_VEHICLE:
337
- self.visualized_environment.add_vehicle(update.data)
338
- data: VisualizedVehicle = update.data
339
- if data.polylines is not None:
340
- self.update_polylines_if_needed(data)
341
- elif update.type == UpdateType.UPDATE_PASSENGER_STATUS:
342
- passenger = self.visualized_environment.get_passenger(
343
- update.data.passenger_id
344
- )
345
- passenger.status = update.data.status
346
- elif update.type == UpdateType.UPDATE_PASSENGER_LEGS:
347
- passenger = self.visualized_environment.get_passenger(
348
- update.data.passenger_id
349
- )
350
- legs_update: PassengerLegsUpdate = update.data
351
- passenger.previous_legs = legs_update.previous_legs
352
- passenger.next_legs = legs_update.next_legs
353
- passenger.current_leg = legs_update.current_leg
354
- elif update.type == UpdateType.UPDATE_VEHICLE_STATUS:
355
- vehicle = self.visualized_environment.get_vehicle(update.data.vehicle_id)
356
- vehicle.status = update.data.status
357
- elif update.type == UpdateType.UPDATE_VEHICLE_STOPS:
358
- vehicle = self.visualized_environment.get_vehicle(update.data.vehicle_id)
359
- stops_update: VehicleStopsUpdate = update.data
360
- vehicle.previous_stops = stops_update.previous_stops
361
- vehicle.next_stops = stops_update.next_stops
362
- vehicle.current_stop = stops_update.current_stop
363
- if vehicle.polylines is not None:
364
- self.update_polylines_if_needed(vehicle)
365
- elif update.type == UpdateType.UPDATE_STATISTIC:
366
- statistic_update: StatisticUpdate = update.data
367
- self.visualized_environment.statistic = statistic_update.statistic
368
-
369
- SimulationVisualizationDataManager.save_update(
370
- self.current_save_file_path, update
371
- )
372
-
373
- self.update_counter += 1
374
-
375
- # MARK: +- Polylines
376
- def update_polylines_if_needed(self, vehicle: VisualizedVehicle) -> None:
377
- polylines = vehicle.polylines
378
- stops = vehicle.all_stops
379
-
380
- # A polyline needs to have at least 2 points
381
- if len(stops) < 2:
382
- return
383
-
384
- # Notify if their are not enough polylines
385
- if len(polylines) < len(stops) - 1:
386
- raise ValueError(
387
- f"Vehicle {vehicle.vehicle_id} has not enough polylines for its stops"
388
- )
389
-
390
- stops_pairs: list[
391
- tuple[tuple[VisualizedStop, VisualizedStop], tuple[str, list[float]]]
392
- ] = zip(
393
- [(stops[i], stops[i + 1]) for i in range(len(stops) - 1)],
394
- polylines.values(),
395
- strict=False, # There may be more polylines than stops
396
- )
397
-
398
- polylines_to_save: dict[str, tuple[str, list[float]]] = {}
399
-
400
- for stop_pair, polyline in stops_pairs:
401
- first_stop, second_stop = stop_pair
402
-
403
- if (
404
- first_stop.latitude is None
405
- or first_stop.longitude is None
406
- or second_stop.latitude is None
407
- or second_stop.longitude is None
408
- ):
409
- raise ValueError(
410
- f"Vehicle {vehicle.vehicle_id} has stops without coordinates"
411
- )
412
-
413
- coordinates_pair = f"{first_stop.latitude},{first_stop.longitude},{second_stop.latitude},{second_stop.longitude}"
414
-
415
- if coordinates_pair not in self.saved_polylines_coordinates_pairs:
416
- polylines_to_save[coordinates_pair] = polyline
417
- self.saved_polylines_coordinates_pairs.add(coordinates_pair)
418
-
419
- if len(polylines_to_save) > 0:
420
- SimulationVisualizationDataManager.set_polylines(
421
- self.simulation_id, polylines_to_save
422
- )
423
-
424
- if self.isConnected:
425
- self.sio.emit(
426
- "simulation-update-polylines-version",
427
- self.simulation_id,
428
- )
429
-
430
- # MARK: +- Flush
431
- def flush(self, environment) -> None:
432
- for event in self.passenger_assignment_event_queue:
433
- self.add_update(
434
- Update(
435
- UpdateType.UPDATE_PASSENGER_STATUS,
436
- PassengerStatusUpdate.from_trip(
437
- event.state_machine.owner,
438
- ),
439
- event.time,
440
- ),
441
- environment,
442
- )
443
- previous_passenger = self.visualized_environment.get_passenger(
444
- event.state_machine.owner.id
445
- )
446
- self.add_update(
447
- Update(
448
- UpdateType.UPDATE_PASSENGER_LEGS,
449
- PassengerLegsUpdate.from_trip_environment_and_previous_passenger(
450
- event.state_machine.owner, environment, previous_passenger
451
- ),
452
- event.time,
453
- ),
454
- environment,
455
- )
456
-
457
- for event in self.vehicle_notification_event_queue:
458
- vehicle = event._VehicleNotification__vehicle
459
- route = event._VehicleNotification__route
460
- existing_vehicle = self.visualized_environment.get_vehicle(vehicle.id)
461
- if vehicle.polylines != existing_vehicle.polylines:
462
- existing_vehicle.polylines = vehicle.polylines
463
-
464
- self.add_update(
465
- Update(
466
- UpdateType.UPDATE_VEHICLE_STOPS,
467
- VehicleStopsUpdate.from_vehicle_and_route(vehicle, route),
468
- event.time,
469
- ),
470
- environment,
471
- )
472
-
473
- self.passenger_assignment_event_queue = []
474
- self.vehicle_notification_event_queue = []
475
-
476
- @property
477
- def hasToFlush(self) -> bool:
478
- return (
479
- len(self.passenger_assignment_event_queue) > 0
480
- or len(self.vehicle_notification_event_queue) > 0
481
- )
482
-
483
- # MARK: +- Process Event
484
- def process_event(self, event: Event, environment: Environment) -> str:
485
- # In case that a queued event is not linked to EnvironmentIdle
486
- if self.hasToFlush and event.time > self.last_queued_event_time:
487
- self.flush(environment)
488
-
489
- # Optimize
490
- if isinstance(event, Optimize):
491
- # Do nothing ?
492
- return f"{event.time} TODO Optimize"
493
-
494
- # EnvironmentUpdate
495
- elif isinstance(event, EnvironmentUpdate):
496
- # Do nothing ?
497
- return f"{event.time} TODO EnvironmentUpdate"
498
-
499
- # EnvironmentIdle
500
- elif isinstance(event, EnvironmentIdle):
501
- self.flush(environment)
502
- return f"{event.time} TODO EnvironmentIdle"
503
-
504
- # PassengerRelease
505
- elif isinstance(event, PassengerRelease):
506
- passenger = VisualizedPassenger.from_trip_and_environment(
507
- event.trip, environment
508
- )
509
- self.add_update(
510
- Update(
511
- UpdateType.CREATE_PASSENGER,
512
- passenger,
513
- event.time,
514
- ),
515
- environment,
516
- )
517
- return f"{event.time} TODO PassengerRelease"
518
-
519
- # PassengerAssignment
520
- elif isinstance(event, PassengerAssignment):
521
- self.passenger_assignment_event_queue.append(event)
522
- self.last_queued_event_time = event.time
523
- return f"{event.time} TODO PassengerAssignment"
524
-
525
- # PassengerReady
526
- elif isinstance(event, PassengerReady):
527
- self.add_update(
528
- Update(
529
- UpdateType.UPDATE_PASSENGER_STATUS,
530
- PassengerStatusUpdate.from_trip(
531
- event.state_machine.owner,
532
- ),
533
- event.time,
534
- ),
535
- environment,
536
- )
537
- return f"{event.time} TODO PassengerReady"
538
-
539
- # PassengerToBoard
540
- elif isinstance(event, PassengerToBoard):
541
- self.add_update(
542
- Update(
543
- UpdateType.UPDATE_PASSENGER_STATUS,
544
- PassengerStatusUpdate.from_trip(
545
- event.state_machine.owner,
546
- ),
547
- event.time,
548
- ),
549
- environment,
550
- )
551
- previous_passenger = self.visualized_environment.get_passenger(
552
- event.state_machine.owner.id
553
- )
554
- self.add_update(
555
- Update(
556
- UpdateType.UPDATE_PASSENGER_LEGS,
557
- PassengerLegsUpdate.from_trip_environment_and_previous_passenger(
558
- event.state_machine.owner, environment, previous_passenger
559
- ),
560
- event.time,
561
- ),
562
- environment,
563
- )
564
- return f"{event.time} TODO PassengerToBoard"
565
-
566
- # PassengerAlighting
567
- elif isinstance(event, PassengerAlighting):
568
- self.add_update(
569
- Update(
570
- UpdateType.UPDATE_PASSENGER_STATUS,
571
- PassengerStatusUpdate.from_trip(
572
- event.state_machine.owner,
573
- ),
574
- event.time,
575
- ),
576
- environment,
577
- )
578
- previous_passenger = self.visualized_environment.get_passenger(
579
- event.state_machine.owner.id
580
- )
581
- self.add_update(
582
- Update(
583
- UpdateType.UPDATE_PASSENGER_LEGS,
584
- PassengerLegsUpdate.from_trip_environment_and_previous_passenger(
585
- event.state_machine.owner, environment, previous_passenger
586
- ),
587
- event.time,
588
- ),
589
- environment,
590
- )
591
- return f"{event.time} TODO PassengerAlighting"
592
-
593
- # VehicleWaiting
594
- elif isinstance(event, VehicleWaiting):
595
- self.add_update(
596
- Update(
597
- UpdateType.UPDATE_VEHICLE_STATUS,
598
- VehicleStatusUpdate.from_vehicle(event.state_machine.owner),
599
- event.time,
600
- ),
601
- environment,
602
- )
603
- return f"{event.time} TODO VehicleWaiting"
604
-
605
- # VehicleBoarding
606
- elif isinstance(event, VehicleBoarding):
607
- self.add_update(
608
- Update(
609
- UpdateType.UPDATE_VEHICLE_STATUS,
610
- VehicleStatusUpdate.from_vehicle(
611
- event.state_machine.owner,
612
- ),
613
- event.time,
614
- ),
615
- environment,
616
- )
617
- return f"{event.time} TODO VehicleBoarding"
618
-
619
- # VehicleDeparture
620
- elif isinstance(event, VehicleDeparture):
621
- route = event._VehicleDeparture__route
622
- vehicle = event.state_machine.owner
623
-
624
- self.add_update(
625
- Update(
626
- UpdateType.UPDATE_VEHICLE_STATUS,
627
- VehicleStatusUpdate.from_vehicle(
628
- event.state_machine.owner,
629
- ),
630
- event.time,
631
- ),
632
- environment,
633
- )
634
-
635
- self.add_update(
636
- Update(
637
- UpdateType.UPDATE_VEHICLE_STOPS,
638
- VehicleStopsUpdate.from_vehicle_and_route(vehicle, route),
639
- event.time,
640
- ),
641
- environment,
642
- )
643
- return f"{event.time} TODO VehicleDeparture"
644
-
645
- # VehicleArrival
646
- elif isinstance(event, VehicleArrival):
647
- route = event._VehicleArrival__route
648
- vehicle = event.state_machine.owner
649
-
650
- self.add_update(
651
- Update(
652
- UpdateType.UPDATE_VEHICLE_STATUS,
653
- VehicleStatusUpdate.from_vehicle(
654
- event.state_machine.owner,
655
- ),
656
- event.time,
657
- ),
658
- environment,
659
- )
660
-
661
- self.add_update(
662
- Update(
663
- UpdateType.UPDATE_VEHICLE_STOPS,
664
- VehicleStopsUpdate.from_vehicle_and_route(vehicle, route),
665
- event.time,
666
- ),
667
- environment,
668
- )
669
-
670
- return f"{event.time} TODO VehicleArrival"
671
-
672
- # VehicleComplete
673
- elif isinstance(event, VehicleComplete):
674
- self.add_update(
675
- Update(
676
- UpdateType.UPDATE_VEHICLE_STATUS,
677
- VehicleStatusUpdate.from_vehicle(
678
- event.state_machine.owner,
679
- ),
680
- event.time,
681
- ),
682
- environment,
683
- )
684
- return f"{event.time} TODO VehicleComplete"
685
-
686
- # VehicleReady
687
- elif isinstance(event, VehicleReady):
688
- vehicle = VisualizedVehicle.from_vehicle_and_route(
689
- event.vehicle, event._VehicleReady__route
690
- )
691
- self.add_update(
692
- Update(
693
- UpdateType.CREATE_VEHICLE,
694
- vehicle,
695
- event.time,
696
- ),
697
- environment,
698
- )
699
- return f"{event.time} TODO VehicleReady"
700
-
701
- # VehicleNotification
702
- elif isinstance(event, VehicleNotification):
703
- self.vehicle_notification_event_queue.append(event)
704
- self.last_queued_event_time = event.time
705
- return f"{event.time} TODO VehicleNotification"
706
-
707
- # VehicleBoarded
708
- elif isinstance(event, VehicleBoarded):
709
- return f"{event.time} TODO VehicleBoarded"
710
-
711
- # VehicleAlighted
712
- elif isinstance(event, VehicleAlighted):
713
- return f"{event.time} TODO VehicleAlighted"
714
-
715
- # VehicleUpdatePositionEvent
716
- elif isinstance(event, VehicleUpdatePositionEvent):
717
- # Do nothing ?
718
- return f"{event.time} TODO VehicleUpdatePositionEvent"
719
-
720
- # RecurrentTimeSyncEvent
721
- elif isinstance(event, RecurrentTimeSyncEvent):
722
- # Do nothing ?
723
- return f"{event.time} TODO RecurrentTimeSyncEvent"
724
-
725
- # Hold
726
- elif isinstance(event, Hold):
727
- # Do nothing ?
728
- return f"{event.time} TODO Hold"
729
-
730
- else:
731
- raise NotImplementedError(f"Event {event} not implemented")
732
-
733
- # MARK: +- Clean Up
734
- def clean_up(self, env):
735
- self.simulation_information.simulation_end_time = (
736
- self.visualized_environment.timestamp
737
- )
738
- self.simulation_information.last_update_order = (
739
- self.visualized_environment.order
740
- )
741
-
742
- SimulationVisualizationDataManager.set_simulation_information(
743
- self.simulation_id, self.simulation_information
744
- )
745
-
746
- if self.stop_event is not None:
747
- self.stop_event.set()
748
-
749
- if self.connection_thread is not None:
750
- self.connection_thread.join()
751
-
752
- if self.isConnected:
753
- self.sio.disconnect()
754
-
755
- if self.sio is not None:
756
- self.sio.wait()
1
+ import threading
2
+ from typing import Optional
3
+
4
+ from multimodalsim.observer.data_collector import DataCollector
5
+ from multimodalsim.simulator.environment import Environment
6
+ from multimodalsim.simulator.event import Event, RecurrentTimeSyncEvent
7
+ from multimodalsim.simulator.optimization_event import (
8
+ EnvironmentIdle,
9
+ EnvironmentUpdate,
10
+ Hold,
11
+ Optimize,
12
+ )
13
+ from multimodalsim.simulator.passenger_event import (
14
+ PassengerAlighting,
15
+ PassengerAssignment,
16
+ PassengerReady,
17
+ PassengerRelease,
18
+ PassengerToBoard,
19
+ )
20
+ from multimodalsim.simulator.simulation import Simulation
21
+ from multimodalsim.simulator.vehicle_event import (
22
+ VehicleAlighted,
23
+ VehicleArrival,
24
+ VehicleBoarded,
25
+ VehicleBoarding,
26
+ VehicleComplete,
27
+ VehicleDeparture,
28
+ VehicleNotification,
29
+ VehicleReady,
30
+ VehicleUpdatePositionEvent,
31
+ VehicleWaiting,
32
+ )
33
+ from multimodalsim.statistics.data_analyzer import DataAnalyzer
34
+ from socketio import Client
35
+
36
+ from multimodalsim_viewer.common.utils import (
37
+ HOST,
38
+ SERVER_PORT,
39
+ STATE_SAVE_STEP,
40
+ SimulationStatus,
41
+ build_simulation_id,
42
+ )
43
+ from multimodalsim_viewer.server.log_manager import register_log
44
+ from multimodalsim_viewer.server.simulation_visualization_data_model import (
45
+ PassengerLegsUpdate,
46
+ PassengerStatusUpdate,
47
+ SimulationInformation,
48
+ SimulationVisualizationDataManager,
49
+ StatisticUpdate,
50
+ Update,
51
+ UpdateType,
52
+ VehicleStatusUpdate,
53
+ VehicleStopsUpdate,
54
+ VisualizedEnvironment,
55
+ VisualizedPassenger,
56
+ VisualizedStop,
57
+ VisualizedVehicle,
58
+ )
59
+
60
+
61
+ # MARK: Data Collector
62
+ class SimulationVisualizationDataCollector(DataCollector): # pylint: disable=too-many-instance-attributes
63
+ simulation_id: str
64
+ update_counter: int
65
+ visualized_environment: VisualizedEnvironment
66
+ simulation_information: SimulationInformation
67
+ current_save_file_path: str
68
+
69
+ max_duration: float | None
70
+ """
71
+ Maximum duration of the simulation in in-simulation time (seconds).
72
+ The simulation will stop if it exceeds this duration.
73
+ """
74
+
75
+ # Special events
76
+ last_queued_event_time: float
77
+ passenger_assignment_event_queue: list[PassengerAssignment]
78
+ vehicle_notification_event_queue: list[VehicleNotification]
79
+
80
+ # Statistics
81
+ data_analyzer: DataAnalyzer
82
+ statistics_delta_time: int
83
+ last_statistics_update_time: int
84
+
85
+ # Communication
86
+ sio: Client | None = None
87
+ stop_event: threading.Event | None = None
88
+ connection_thread: threading.Thread | None = None
89
+ _simulation: Simulation | None = None
90
+ status: SimulationStatus | None = None
91
+
92
+ # Polylines
93
+ saved_polylines_coordinates_pairs: set[str] = set()
94
+
95
+ # Estimated end time
96
+ last_estimated_end_time: float | None = None
97
+
98
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
99
+ self,
100
+ data_analyzer: DataAnalyzer,
101
+ statistics_delta_time: int = 10,
102
+ name: str = "simulation",
103
+ input_data_description: str = "unknown",
104
+ simulation_id: str | None = None,
105
+ max_duration: float | None = None,
106
+ offline: bool = False,
107
+ stop_event: threading.Event | None = None,
108
+ ) -> None:
109
+ super().__init__()
110
+
111
+ if simulation_id is None:
112
+ simulation_id, _ = build_simulation_id(name)
113
+
114
+ self.simulation_id = simulation_id
115
+ self.update_counter = 0
116
+ self.visualized_environment = VisualizedEnvironment()
117
+
118
+ self.simulation_information = SimulationInformation(
119
+ simulation_id, input_data_description, None, None, None, None
120
+ )
121
+
122
+ self.current_save_file_path = None
123
+
124
+ self.max_duration = max_duration
125
+
126
+ self.passenger_assignment_event_queue = []
127
+ self.vehicle_notification_event_queue = []
128
+ self.last_queued_event_time = 0
129
+
130
+ self.data_analyzer = data_analyzer
131
+ self.statistics_delta_time = statistics_delta_time
132
+ self.last_statistics_update_time = None
133
+
134
+ self.stop_event = stop_event
135
+
136
+ if not offline:
137
+ self.initialize_communication()
138
+
139
+ @property
140
+ def is_connected(self) -> bool:
141
+ return self.sio is not None and self.sio.connected
142
+
143
+ # MARK: +- Communication
144
+ def initialize_communication(self) -> None:
145
+ sio = Client(reconnection_attempts=1)
146
+
147
+ self.sio = sio
148
+ self.status = SimulationStatus.RUNNING
149
+
150
+ @sio.on("pause-simulation")
151
+ def pause_simulator():
152
+ if self._simulation is not None:
153
+ self._simulation.pause()
154
+ self.status = SimulationStatus.PAUSED
155
+ if self.is_connected:
156
+ self.sio.emit("simulation-pause", self.simulation_id)
157
+
158
+ @sio.on("resume-simulation")
159
+ def resume_simulator():
160
+ if self._simulation is not None:
161
+ self._simulation.resume()
162
+ self.status = SimulationStatus.RUNNING
163
+ if self.is_connected:
164
+ self.sio.emit("simulation-resume", self.simulation_id)
165
+
166
+ @sio.on("stop-simulation")
167
+ def stop_simulator():
168
+ if self._simulation is not None:
169
+ self._simulation.stop()
170
+ self.status = SimulationStatus.STOPPING
171
+
172
+ @sio.on("connect")
173
+ def on_connect():
174
+ sio.emit(
175
+ "simulation-identification",
176
+ (
177
+ self.simulation_id,
178
+ self.simulation_information.data,
179
+ self.simulation_information.simulation_start_time,
180
+ self.visualized_environment.timestamp,
181
+ self.visualized_environment.estimated_end_time,
182
+ self.max_duration,
183
+ self.status.value,
184
+ ),
185
+ )
186
+
187
+ @sio.on("edit-simulation-configuration")
188
+ def on_edit_simulation_configuration(max_duration: float | None):
189
+ self.max_duration = max_duration
190
+
191
+ if self.last_estimated_end_time is None:
192
+ return
193
+
194
+ # Notify the server if the estimated end time has changed
195
+ new_estimated_end_time = min(
196
+ self.last_estimated_end_time,
197
+ (
198
+ self.simulation_information.simulation_start_time + self.max_duration
199
+ if self.max_duration is not None
200
+ else self.last_estimated_end_time
201
+ ),
202
+ )
203
+
204
+ if new_estimated_end_time != self.visualized_environment.estimated_end_time:
205
+ self.sio.emit(
206
+ "simulation-update-estimated-end-time",
207
+ (self.simulation_id, new_estimated_end_time),
208
+ )
209
+ self.visualized_environment.estimated_end_time = new_estimated_end_time
210
+
211
+ if self.stop_event is None:
212
+ self.stop_event = threading.Event()
213
+
214
+ self.connection_thread = threading.Thread(target=self.handle_connection)
215
+ self.connection_thread.start()
216
+
217
+ def handle_connection(self) -> None:
218
+ while not self.stop_event.is_set():
219
+
220
+ if not self.sio.connected:
221
+ try:
222
+ print("Trying to reconnect")
223
+ self.sio.connect(f"http://{HOST}:{SERVER_PORT}", auth={"type": "simulation"})
224
+ print("Connected")
225
+ except Exception as e: # pylint: disable=broad-exception-caught
226
+ print(f"Failed to connect to server: {e}")
227
+ print("Continuing in offline mode")
228
+
229
+ self.sio.sleep(5) # Check every 5 seconds
230
+
231
+ self.sio.disconnect()
232
+ self.sio.wait()
233
+
234
+ # MARK: +- Collect
235
+ def collect(
236
+ self,
237
+ env: Environment,
238
+ current_event: Optional[Event] = None,
239
+ event_index: Optional[int] = None,
240
+ event_priority: Optional[int] = None,
241
+ ) -> None:
242
+ env.simulation_config.max_time = (
243
+ (
244
+ (
245
+ self.simulation_information.simulation_start_time
246
+ if self.simulation_information.simulation_start_time is not None
247
+ else env.current_time
248
+ )
249
+ + self.max_duration
250
+ )
251
+ if self.max_duration is not None
252
+ else env.simulation_config.max_time
253
+ )
254
+
255
+ if current_event is None:
256
+ return
257
+
258
+ message = self.process_event(current_event, env)
259
+ register_log(self.simulation_id, message)
260
+
261
+ if self.is_connected:
262
+ self.sio.emit("log", (self.simulation_id, message))
263
+
264
+ if (
265
+ self.last_statistics_update_time is None
266
+ or current_event.time >= self.last_statistics_update_time + self.statistics_delta_time
267
+ ):
268
+ self.last_statistics_update_time = current_event.time
269
+ self.add_update(
270
+ Update(
271
+ UpdateType.UPDATE_STATISTIC,
272
+ StatisticUpdate(self.data_analyzer.get_statistics()),
273
+ current_event.time,
274
+ ),
275
+ env,
276
+ )
277
+
278
+ # MARK: +- Add Update
279
+ def add_update( # pylint: disable=too-many-branches, too-many-statements
280
+ self, update: Update, environment: Environment
281
+ ) -> None:
282
+ update.order = self.update_counter
283
+ self.visualized_environment.order = self.update_counter
284
+
285
+ if self.update_counter == 0:
286
+ # Add the simulation start time to the simulation information
287
+ self.simulation_information.simulation_start_time = update.timestamp
288
+
289
+ # Save the simulation information
290
+ SimulationVisualizationDataManager.set_simulation_information(
291
+ self.simulation_id, self.simulation_information
292
+ )
293
+
294
+ # Notify the server that the simulation has started and send the simulation start time
295
+ if self.is_connected:
296
+ self.sio.emit("simulation-start", (self.simulation_id, update.timestamp))
297
+
298
+ if self.visualized_environment.timestamp != update.timestamp:
299
+ # Notify the server that the simulation time has been updated
300
+ if self.is_connected:
301
+ self.sio.emit(
302
+ "simulation-update-time",
303
+ (
304
+ self.simulation_id,
305
+ update.timestamp,
306
+ ),
307
+ )
308
+ self.visualized_environment.timestamp = update.timestamp
309
+
310
+ # Remember the last estimated end time in case of max_duration updates
311
+ self.last_estimated_end_time = environment.estimated_end_time
312
+ estimated_end_time = min(
313
+ environment.estimated_end_time,
314
+ (
315
+ self.simulation_information.simulation_start_time + self.max_duration
316
+ if self.max_duration is not None
317
+ else environment.estimated_end_time
318
+ ),
319
+ )
320
+ if estimated_end_time != self.visualized_environment.estimated_end_time:
321
+ # Notify the server that the simulation estimated end time has been updated
322
+ if self.is_connected:
323
+ self.sio.emit(
324
+ "simulation-update-estimated-end-time",
325
+ (self.simulation_id, estimated_end_time),
326
+ )
327
+ self.visualized_environment.estimated_end_time = estimated_end_time
328
+
329
+ # Save the state of the simulation every SAVE_STATE_STEP events before applying the update
330
+ if self.update_counter % STATE_SAVE_STEP == 0:
331
+ self.current_save_file_path = SimulationVisualizationDataManager.save_state(
332
+ self.simulation_id, self.visualized_environment
333
+ )
334
+
335
+ if update.update_type == UpdateType.CREATE_PASSENGER:
336
+ self.visualized_environment.add_passenger(update.data)
337
+ elif update.update_type == UpdateType.CREATE_VEHICLE:
338
+ self.visualized_environment.add_vehicle(update.data)
339
+ data: VisualizedVehicle = update.data
340
+ if data.polylines is not None:
341
+ self.update_polylines_if_needed(data)
342
+ elif update.update_type == UpdateType.UPDATE_PASSENGER_STATUS:
343
+ passenger = self.visualized_environment.get_passenger(update.data.passenger_id)
344
+ passenger.status = update.data.status
345
+ elif update.update_type == UpdateType.UPDATE_PASSENGER_LEGS:
346
+ passenger = self.visualized_environment.get_passenger(update.data.passenger_id)
347
+ legs_update: PassengerLegsUpdate = update.data
348
+ passenger.previous_legs = legs_update.previous_legs
349
+ passenger.next_legs = legs_update.next_legs
350
+ passenger.current_leg = legs_update.current_leg
351
+ elif update.update_type == UpdateType.UPDATE_VEHICLE_STATUS:
352
+ vehicle = self.visualized_environment.get_vehicle(update.data.vehicle_id)
353
+ vehicle.status = update.data.status
354
+ elif update.update_type == UpdateType.UPDATE_VEHICLE_STOPS:
355
+ vehicle = self.visualized_environment.get_vehicle(update.data.vehicle_id)
356
+ stops_update: VehicleStopsUpdate = update.data
357
+ vehicle.previous_stops = stops_update.previous_stops
358
+ vehicle.next_stops = stops_update.next_stops
359
+ vehicle.current_stop = stops_update.current_stop
360
+ if vehicle.polylines is not None:
361
+ self.update_polylines_if_needed(vehicle)
362
+ elif update.update_type == UpdateType.UPDATE_STATISTIC:
363
+ statistic_update: StatisticUpdate = update.data
364
+ self.visualized_environment.statistic = statistic_update.statistic
365
+
366
+ SimulationVisualizationDataManager.save_update(self.current_save_file_path, update)
367
+
368
+ self.update_counter += 1
369
+
370
+ # MARK: +- Polylines
371
+ def update_polylines_if_needed(self, vehicle: VisualizedVehicle) -> None:
372
+ polylines = vehicle.polylines
373
+ stops = vehicle.all_stops
374
+
375
+ # A polyline needs to have at least 2 points
376
+ if len(stops) < 2:
377
+ return
378
+
379
+ # Notify if their are not enough polylines
380
+ if len(polylines) < len(stops) - 1:
381
+ raise ValueError(f"Vehicle {vehicle.vehicle_id} has not enough polylines for its stops")
382
+
383
+ stops_pairs: list[tuple[tuple[VisualizedStop, VisualizedStop], tuple[str, list[float]]]] = zip(
384
+ [(stops[i], stops[i + 1]) for i in range(len(stops) - 1)],
385
+ polylines.values(),
386
+ strict=False, # There may be more polylines than stops
387
+ )
388
+
389
+ polylines_to_save: dict[str, tuple[str, list[float]]] = {}
390
+
391
+ for stop_pair, polyline in stops_pairs:
392
+ first_stop, second_stop = stop_pair
393
+
394
+ if (
395
+ first_stop.latitude is None
396
+ or first_stop.longitude is None
397
+ or second_stop.latitude is None
398
+ or second_stop.longitude is None
399
+ ):
400
+ raise ValueError(f"Vehicle {vehicle.vehicle_id} has stops without coordinates")
401
+
402
+ coordinates_pair = (
403
+ f"{first_stop.latitude},{first_stop.longitude},{second_stop.latitude},{second_stop.longitude}"
404
+ )
405
+
406
+ if coordinates_pair not in self.saved_polylines_coordinates_pairs:
407
+ polylines_to_save[coordinates_pair] = polyline
408
+ self.saved_polylines_coordinates_pairs.add(coordinates_pair)
409
+
410
+ if len(polylines_to_save) > 0:
411
+ SimulationVisualizationDataManager.set_polylines(self.simulation_id, polylines_to_save)
412
+
413
+ if self.is_connected:
414
+ self.sio.emit(
415
+ "simulation-update-polylines-version",
416
+ self.simulation_id,
417
+ )
418
+
419
+ # MARK: +- Flush
420
+ def flush(self, environment) -> None:
421
+ for event in self.passenger_assignment_event_queue:
422
+ self.add_update(
423
+ Update(
424
+ UpdateType.UPDATE_PASSENGER_STATUS,
425
+ PassengerStatusUpdate.from_trip(
426
+ event.state_machine.owner,
427
+ ),
428
+ event.time,
429
+ ),
430
+ environment,
431
+ )
432
+ previous_passenger = self.visualized_environment.get_passenger(event.state_machine.owner.id)
433
+ self.add_update(
434
+ Update(
435
+ UpdateType.UPDATE_PASSENGER_LEGS,
436
+ PassengerLegsUpdate.from_trip_environment_and_previous_passenger(
437
+ event.state_machine.owner, environment, previous_passenger
438
+ ),
439
+ event.time,
440
+ ),
441
+ environment,
442
+ )
443
+
444
+ for event in self.vehicle_notification_event_queue:
445
+ vehicle = event._VehicleNotification__vehicle # pylint: disable=protected-access
446
+ route = event._VehicleNotification__route # pylint: disable=protected-access
447
+ existing_vehicle = self.visualized_environment.get_vehicle(vehicle.id)
448
+ if vehicle.polylines != existing_vehicle.polylines:
449
+ existing_vehicle.polylines = vehicle.polylines
450
+
451
+ self.add_update(
452
+ Update(
453
+ UpdateType.UPDATE_VEHICLE_STOPS,
454
+ VehicleStopsUpdate.from_vehicle_and_route(vehicle, route),
455
+ event.time,
456
+ ),
457
+ environment,
458
+ )
459
+
460
+ self.passenger_assignment_event_queue = []
461
+ self.vehicle_notification_event_queue = []
462
+
463
+ @property
464
+ def has_to_flush(self) -> bool:
465
+ return len(self.passenger_assignment_event_queue) > 0 or len(self.vehicle_notification_event_queue) > 0
466
+
467
+ # MARK: +- Process Event
468
+ def process_event( # pylint: disable=too-many-branches, too-many-statements, too-many-return-statements
469
+ self, event: Event, environment: Environment
470
+ ) -> str:
471
+ # In case that a queued event is not linked to EnvironmentIdle
472
+ if self.has_to_flush and event.time > self.last_queued_event_time:
473
+ self.flush(environment)
474
+
475
+ # Optimize
476
+ if isinstance(event, Optimize):
477
+ # Do nothing ?
478
+ return f"{event.time} TODO Optimize"
479
+
480
+ # EnvironmentUpdate
481
+ if isinstance(event, EnvironmentUpdate):
482
+ # Do nothing ?
483
+ return f"{event.time} TODO EnvironmentUpdate"
484
+
485
+ # EnvironmentIdle
486
+ if isinstance(event, EnvironmentIdle):
487
+ self.flush(environment)
488
+ return f"{event.time} TODO EnvironmentIdle"
489
+
490
+ # PassengerRelease
491
+ if isinstance(event, PassengerRelease):
492
+ passenger = VisualizedPassenger.from_trip_and_environment(event.trip, environment)
493
+ self.add_update(
494
+ Update(
495
+ UpdateType.CREATE_PASSENGER,
496
+ passenger,
497
+ event.time,
498
+ ),
499
+ environment,
500
+ )
501
+ return f"{event.time} TODO PassengerRelease"
502
+
503
+ # PassengerAssignment
504
+ if isinstance(event, PassengerAssignment):
505
+ self.passenger_assignment_event_queue.append(event)
506
+ self.last_queued_event_time = event.time
507
+ return f"{event.time} TODO PassengerAssignment"
508
+
509
+ # PassengerReady
510
+ if isinstance(event, PassengerReady):
511
+ self.add_update(
512
+ Update(
513
+ UpdateType.UPDATE_PASSENGER_STATUS,
514
+ PassengerStatusUpdate.from_trip(
515
+ event.state_machine.owner,
516
+ ),
517
+ event.time,
518
+ ),
519
+ environment,
520
+ )
521
+ return f"{event.time} TODO PassengerReady"
522
+
523
+ # PassengerToBoard
524
+ if isinstance(event, PassengerToBoard):
525
+ self.add_update(
526
+ Update(
527
+ UpdateType.UPDATE_PASSENGER_STATUS,
528
+ PassengerStatusUpdate.from_trip(
529
+ event.state_machine.owner,
530
+ ),
531
+ event.time,
532
+ ),
533
+ environment,
534
+ )
535
+ previous_passenger = self.visualized_environment.get_passenger(event.state_machine.owner.id)
536
+ self.add_update(
537
+ Update(
538
+ UpdateType.UPDATE_PASSENGER_LEGS,
539
+ PassengerLegsUpdate.from_trip_environment_and_previous_passenger(
540
+ event.state_machine.owner, environment, previous_passenger
541
+ ),
542
+ event.time,
543
+ ),
544
+ environment,
545
+ )
546
+ return f"{event.time} TODO PassengerToBoard"
547
+
548
+ # PassengerAlighting
549
+ if isinstance(event, PassengerAlighting):
550
+ self.add_update(
551
+ Update(
552
+ UpdateType.UPDATE_PASSENGER_STATUS,
553
+ PassengerStatusUpdate.from_trip(
554
+ event.state_machine.owner,
555
+ ),
556
+ event.time,
557
+ ),
558
+ environment,
559
+ )
560
+ previous_passenger = self.visualized_environment.get_passenger(event.state_machine.owner.id)
561
+ self.add_update(
562
+ Update(
563
+ UpdateType.UPDATE_PASSENGER_LEGS,
564
+ PassengerLegsUpdate.from_trip_environment_and_previous_passenger(
565
+ event.state_machine.owner, environment, previous_passenger
566
+ ),
567
+ event.time,
568
+ ),
569
+ environment,
570
+ )
571
+ return f"{event.time} TODO PassengerAlighting"
572
+
573
+ # VehicleWaiting
574
+ if isinstance(event, VehicleWaiting):
575
+ self.add_update(
576
+ Update(
577
+ UpdateType.UPDATE_VEHICLE_STATUS,
578
+ VehicleStatusUpdate.from_vehicle(event.state_machine.owner),
579
+ event.time,
580
+ ),
581
+ environment,
582
+ )
583
+ return f"{event.time} TODO VehicleWaiting"
584
+
585
+ # VehicleBoarding
586
+ if isinstance(event, VehicleBoarding):
587
+ self.add_update(
588
+ Update(
589
+ UpdateType.UPDATE_VEHICLE_STATUS,
590
+ VehicleStatusUpdate.from_vehicle(
591
+ event.state_machine.owner,
592
+ ),
593
+ event.time,
594
+ ),
595
+ environment,
596
+ )
597
+ return f"{event.time} TODO VehicleBoarding"
598
+
599
+ # VehicleDeparture
600
+ if isinstance(event, VehicleDeparture):
601
+ route = event._VehicleDeparture__route # pylint: disable=protected-access
602
+ vehicle = event.state_machine.owner
603
+
604
+ self.add_update(
605
+ Update(
606
+ UpdateType.UPDATE_VEHICLE_STATUS,
607
+ VehicleStatusUpdate.from_vehicle(
608
+ event.state_machine.owner,
609
+ ),
610
+ event.time,
611
+ ),
612
+ environment,
613
+ )
614
+
615
+ self.add_update(
616
+ Update(
617
+ UpdateType.UPDATE_VEHICLE_STOPS,
618
+ VehicleStopsUpdate.from_vehicle_and_route(vehicle, route),
619
+ event.time,
620
+ ),
621
+ environment,
622
+ )
623
+ return f"{event.time} TODO VehicleDeparture"
624
+
625
+ # VehicleArrival
626
+ if isinstance(event, VehicleArrival):
627
+ route = event._VehicleArrival__route # pylint: disable=protected-access
628
+ vehicle = event.state_machine.owner
629
+
630
+ self.add_update(
631
+ Update(
632
+ UpdateType.UPDATE_VEHICLE_STATUS,
633
+ VehicleStatusUpdate.from_vehicle(
634
+ event.state_machine.owner,
635
+ ),
636
+ event.time,
637
+ ),
638
+ environment,
639
+ )
640
+
641
+ self.add_update(
642
+ Update(
643
+ UpdateType.UPDATE_VEHICLE_STOPS,
644
+ VehicleStopsUpdate.from_vehicle_and_route(vehicle, route),
645
+ event.time,
646
+ ),
647
+ environment,
648
+ )
649
+
650
+ return f"{event.time} TODO VehicleArrival"
651
+
652
+ # VehicleComplete
653
+ if isinstance(event, VehicleComplete):
654
+ self.add_update(
655
+ Update(
656
+ UpdateType.UPDATE_VEHICLE_STATUS,
657
+ VehicleStatusUpdate.from_vehicle(
658
+ event.state_machine.owner,
659
+ ),
660
+ event.time,
661
+ ),
662
+ environment,
663
+ )
664
+ return f"{event.time} TODO VehicleComplete"
665
+
666
+ # VehicleReady
667
+ if isinstance(event, VehicleReady):
668
+ vehicle = VisualizedVehicle.from_vehicle_and_route(
669
+ event.vehicle, event._VehicleReady__route # pylint: disable=protected-access
670
+ )
671
+ self.add_update(
672
+ Update(
673
+ UpdateType.CREATE_VEHICLE,
674
+ vehicle,
675
+ event.time,
676
+ ),
677
+ environment,
678
+ )
679
+ return f"{event.time} TODO VehicleReady"
680
+
681
+ # VehicleNotification
682
+ if isinstance(event, VehicleNotification):
683
+ self.vehicle_notification_event_queue.append(event)
684
+ self.last_queued_event_time = event.time
685
+ return f"{event.time} TODO VehicleNotification"
686
+
687
+ # VehicleBoarded
688
+ if isinstance(event, VehicleBoarded):
689
+ return f"{event.time} TODO VehicleBoarded"
690
+
691
+ # VehicleAlighted
692
+ if isinstance(event, VehicleAlighted):
693
+ return f"{event.time} TODO VehicleAlighted"
694
+
695
+ # VehicleUpdatePositionEvent
696
+ if isinstance(event, VehicleUpdatePositionEvent):
697
+ # Do nothing ?
698
+ return f"{event.time} TODO VehicleUpdatePositionEvent"
699
+
700
+ # RecurrentTimeSyncEvent
701
+ if isinstance(event, RecurrentTimeSyncEvent):
702
+ # Do nothing ?
703
+ return f"{event.time} TODO RecurrentTimeSyncEvent"
704
+
705
+ # Hold
706
+ if isinstance(event, Hold):
707
+ # Do nothing ?
708
+ return f"{event.time} TODO Hold"
709
+
710
+ raise NotImplementedError(f"Event {event} not implemented")
711
+
712
+ # MARK: +- Clean Up
713
+ def clean_up(self, env):
714
+ self.simulation_information.simulation_end_time = self.visualized_environment.timestamp
715
+ self.simulation_information.last_update_order = self.visualized_environment.order
716
+
717
+ SimulationVisualizationDataManager.set_simulation_information(self.simulation_id, self.simulation_information)
718
+
719
+ if self.stop_event is not None:
720
+ self.stop_event.set()
721
+
722
+ if self.connection_thread is not None:
723
+ self.connection_thread.join()
724
+
725
+ if self.is_connected:
726
+ self.sio.disconnect()
727
+
728
+ if self.sio is not None:
729
+ self.sio.wait()