multimodalsim-viewer 0.0.3__py3-none-any.whl → 0.1.0.1__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 (32) hide show
  1. multimodalsim_viewer/common/environments/.env +2 -0
  2. multimodalsim_viewer/common/utils.py +11 -48
  3. multimodalsim_viewer/models/__init__.py +0 -0
  4. multimodalsim_viewer/models/environment.py +70 -0
  5. multimodalsim_viewer/models/leg.py +194 -0
  6. multimodalsim_viewer/models/passenger.py +148 -0
  7. multimodalsim_viewer/models/serializable.py +43 -0
  8. multimodalsim_viewer/models/simulation_information.py +84 -0
  9. multimodalsim_viewer/models/state.py +44 -0
  10. multimodalsim_viewer/models/stop.py +114 -0
  11. multimodalsim_viewer/models/update.py +616 -0
  12. multimodalsim_viewer/models/vehicle.py +151 -0
  13. multimodalsim_viewer/server/{simulation_visualization_data_collector.py → data_collector.py} +185 -198
  14. multimodalsim_viewer/server/data_manager.py +572 -0
  15. multimodalsim_viewer/server/http_routes.py +4 -7
  16. multimodalsim_viewer/server/log_manager.py +2 -2
  17. multimodalsim_viewer/server/server.py +8 -10
  18. multimodalsim_viewer/server/simulation.py +4 -5
  19. multimodalsim_viewer/server/simulation_manager.py +22 -23
  20. multimodalsim_viewer/ui/static/environment.json +2 -0
  21. multimodalsim_viewer/ui/static/index.html +2 -2
  22. multimodalsim_viewer/ui/static/{main-LUPJCMAF.js → main-7DV4COXP.js} +173 -173
  23. multimodalsim_viewer/ui/static/scripts/load-environment.script.js +1 -1
  24. multimodalsim_viewer/ui/static/styles-257KETL3.css +1 -0
  25. {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/METADATA +6 -12
  26. multimodalsim_viewer-0.1.0.1.dist-info/RECORD +53 -0
  27. multimodalsim_viewer/server/simulation_visualization_data_model.py +0 -1570
  28. multimodalsim_viewer/ui/static/styles-KU7LTPET.css +0 -1
  29. multimodalsim_viewer-0.0.3.dist-info/RECORD +0 -43
  30. {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/WHEEL +0 -0
  31. {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/entry_points.txt +0 -0
  32. {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,616 @@
1
+ from enum import Enum
2
+
3
+ from multimodalsim_viewer.models.environment import VisualizedEnvironment
4
+ from multimodalsim_viewer.models.leg import LegType, VisualizedLeg
5
+ from multimodalsim_viewer.models.passenger import (
6
+ VisualizedPassenger,
7
+ convert_passenger_status_to_string,
8
+ convert_string_to_passenger_status,
9
+ )
10
+ from multimodalsim_viewer.models.serializable import Serializable
11
+ from multimodalsim_viewer.models.stop import StopType, VisualizedStop
12
+ from multimodalsim_viewer.models.vehicle import (
13
+ VisualizedVehicle,
14
+ convert_string_to_vehicle_status,
15
+ convert_vehicle_status_to_string,
16
+ )
17
+
18
+
19
+ # MARK: UpdateType
20
+ class UpdateType(Enum):
21
+ PASSENGER = "passenger"
22
+ VEHICLE = "vehicle"
23
+ STATISTICS = "statistics"
24
+
25
+
26
+ # MARK: Update
27
+ class Update(Serializable):
28
+ """
29
+ Base class for updates in the simulation viewer.
30
+
31
+ Represents differences in the simulation environment caused by an event.
32
+
33
+ Updates can be applied sequentially to the environment to recreate the evolution of the simulation.
34
+ """
35
+
36
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
37
+ self, update_type: UpdateType, update_index: int, event_index: int, event_name: str, timestamp: float
38
+ ):
39
+ self.__update_type: UpdateType = update_type
40
+ self.__update_index: int = update_index
41
+ self.__event_index: int = event_index
42
+ self.__event_name: str = event_name
43
+ self.__timestamp: float = timestamp
44
+
45
+ @property
46
+ def update_type(self) -> UpdateType:
47
+ return self.__update_type
48
+
49
+ @property
50
+ def update_index(self) -> int:
51
+ return self.__update_index
52
+
53
+ @update_index.setter
54
+ def update_index(self, value: int) -> None:
55
+ self.__update_index = value
56
+
57
+ @property
58
+ def event_index(self) -> int:
59
+ return self.__event_index
60
+
61
+ @property
62
+ def event_name(self) -> str:
63
+ return self.__event_name
64
+
65
+ @property
66
+ def timestamp(self) -> float:
67
+ return self.__timestamp
68
+
69
+ def apply(self, environment: VisualizedEnvironment) -> None:
70
+ """
71
+ Apply the update to the given environment.
72
+
73
+ This method should be overridden by subclasses.
74
+ """
75
+
76
+ def serialize(self) -> dict:
77
+ return {
78
+ "updateType": self.update_type.value,
79
+ "updateIndex": self.update_index,
80
+ "eventIndex": self.event_index,
81
+ "eventName": self.event_name,
82
+ "timestamp": self.timestamp,
83
+ }
84
+
85
+ @classmethod
86
+ def deserialize(cls, serialized_data: dict | str) -> "Update":
87
+ serialized_data = cls.serialized_data_to_dict(serialized_data)
88
+
89
+ required_fields = [
90
+ "updateType",
91
+ "updateIndex",
92
+ "eventIndex",
93
+ "eventName",
94
+ "timestamp",
95
+ ]
96
+ cls.verify_required_fields(serialized_data, required_fields, "Update")
97
+
98
+ update_type = UpdateType(serialized_data.get("updateType"))
99
+ update_index = serialized_data.get("updateIndex")
100
+ event_index = serialized_data.get("eventIndex")
101
+ event_name = serialized_data.get("eventName")
102
+ timestamp = serialized_data.get("timestamp")
103
+
104
+ return cls(update_type, update_index, event_index, event_name, timestamp)
105
+
106
+
107
+ # MARK: PassengerUpdate
108
+ class PassengerUpdate(Update):
109
+ """
110
+ Differences in a passenger before and after an event.
111
+ """
112
+
113
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
114
+ self,
115
+ update_index: int,
116
+ event_index: int,
117
+ event_name: str,
118
+ timestamp: float,
119
+ old_passenger: VisualizedPassenger | None = None,
120
+ new_passenger: VisualizedPassenger | None = None,
121
+ should_compute_difference: bool = True,
122
+ ):
123
+ super().__init__(UpdateType.PASSENGER, update_index, event_index, event_name, timestamp)
124
+
125
+ self.__passenger_id: str | None = None
126
+
127
+ # Dictionary containing the new values of the fields that have changed
128
+ self.__differences: dict = {}
129
+
130
+ # Legs are more complex and will be handled separately
131
+ self.__number_of_legs_to_remove: int = 0
132
+ self.__legs_to_add: list[VisualizedLeg] = []
133
+
134
+ self.__legs_differences: list[dict] = []
135
+
136
+ if should_compute_difference:
137
+ self.__compute_difference(old_passenger, new_passenger)
138
+
139
+ def __compute_difference(
140
+ self, old_passenger: VisualizedPassenger | None, new_passenger: VisualizedPassenger | None
141
+ ) -> dict:
142
+ """
143
+ Compute the difference between the old and new passenger.
144
+ """
145
+
146
+ if new_passenger is None:
147
+ raise ValueError("New passenger cannot be None")
148
+
149
+ if old_passenger is not None and old_passenger.passenger_id != new_passenger.passenger_id:
150
+ raise ValueError("Old and new passenger must have the same ID")
151
+
152
+ self.__passenger_id = new_passenger.passenger_id
153
+
154
+ if old_passenger is None or old_passenger.name != new_passenger.name:
155
+ self.__differences["name"] = new_passenger.name
156
+
157
+ if old_passenger is None or old_passenger.status != new_passenger.status:
158
+ self.__differences["status"] = convert_passenger_status_to_string(new_passenger.status)
159
+
160
+ if old_passenger is None or old_passenger.number_of_passengers != new_passenger.number_of_passengers:
161
+ self.__differences["numberOfPassengers"] = new_passenger.number_of_passengers
162
+
163
+ if old_passenger is None or old_passenger.tags != new_passenger.tags:
164
+ self.__differences["tags"] = new_passenger.tags
165
+
166
+ all_old_legs = old_passenger.all_legs if old_passenger is not None else []
167
+ all_new_legs = new_passenger.all_legs
168
+
169
+ self.__number_of_legs_to_remove = max(0, len(all_old_legs) - len(all_new_legs))
170
+ self.__legs_to_add = all_new_legs[len(all_old_legs) :]
171
+
172
+ for index in range(min(len(all_old_legs), len(all_new_legs))):
173
+ old_leg = all_old_legs[index]
174
+ new_leg = all_new_legs[index]
175
+
176
+ leg_difference = self.__compute_leg_difference(old_leg, new_leg, index)
177
+ if leg_difference is not None:
178
+ self.__legs_differences.append(leg_difference)
179
+
180
+ def __compute_leg_difference(self, old_leg: VisualizedLeg, new_leg: VisualizedLeg, index: int) -> dict | None:
181
+ """
182
+ Compute the difference between the old and new leg.
183
+ """
184
+ leg_difference = {}
185
+
186
+ if old_leg.assigned_vehicle_id != new_leg.assigned_vehicle_id:
187
+ leg_difference["assignedVehicleId"] = new_leg.assigned_vehicle_id
188
+
189
+ if old_leg.boarding_stop_id != new_leg.boarding_stop_id:
190
+ leg_difference["boardingStopId"] = new_leg.boarding_stop_id
191
+
192
+ if old_leg.alighting_stop_id != new_leg.alighting_stop_id:
193
+ leg_difference["alightingStopId"] = new_leg.alighting_stop_id
194
+
195
+ if old_leg.boarding_stop_index != new_leg.boarding_stop_index:
196
+ leg_difference["boardingStopIndex"] = new_leg.boarding_stop_index
197
+
198
+ if old_leg.alighting_stop_index != new_leg.alighting_stop_index:
199
+ leg_difference["alightingStopIndex"] = new_leg.alighting_stop_index
200
+
201
+ if old_leg.boarding_time != new_leg.boarding_time:
202
+ leg_difference["boardingTime"] = new_leg.boarding_time
203
+
204
+ if old_leg.alighting_time != new_leg.alighting_time:
205
+ leg_difference["alightingTime"] = new_leg.alighting_time
206
+
207
+ if old_leg.tags != new_leg.tags:
208
+ leg_difference["tags"] = new_leg.tags
209
+
210
+ if old_leg.leg_type != new_leg.leg_type:
211
+ leg_difference["legType"] = new_leg.leg_type.value
212
+
213
+ if not leg_difference:
214
+ return None
215
+
216
+ leg_difference["index"] = index
217
+
218
+ return leg_difference
219
+
220
+ def apply(self, environment: VisualizedEnvironment) -> None:
221
+ passenger = environment.get_passenger(self.__passenger_id)
222
+
223
+ if passenger is None:
224
+ passenger = VisualizedPassenger(
225
+ self.__passenger_id,
226
+ self.__differences.get("name"),
227
+ convert_string_to_passenger_status(self.__differences.get("status")),
228
+ self.__differences.get("numberOfPassengers"),
229
+ [],
230
+ None,
231
+ [],
232
+ self.__differences.get("tags"),
233
+ )
234
+
235
+ environment.add_passenger(passenger)
236
+
237
+ else:
238
+ if "name" in self.__differences:
239
+ passenger.name = self.__differences.get("name")
240
+ if "status" in self.__differences:
241
+ passenger.status = convert_string_to_passenger_status(self.__differences.get("status"))
242
+ if "numberOfPassengers" in self.__differences:
243
+ passenger.number_of_passengers = self.__differences.get("numberOfPassengers")
244
+ if "tags" in self.__differences:
245
+ passenger.tags = self.__differences.get("tags")
246
+
247
+ self.__update_legs(passenger)
248
+
249
+ def __update_legs(self, passenger: VisualizedPassenger) -> None: # pylint: disable=too-many-branches
250
+ all_legs = passenger.all_legs
251
+
252
+ if self.__number_of_legs_to_remove > 0:
253
+ all_legs = all_legs[: -self.__number_of_legs_to_remove]
254
+
255
+ all_legs.extend(self.__legs_to_add)
256
+
257
+ for leg_difference in self.__legs_differences:
258
+ leg = all_legs[leg_difference.get("index")]
259
+
260
+ if "legType" in leg_difference:
261
+ leg.leg_type = LegType(leg_difference.get("legType"))
262
+ if "assignedVehicleId" in leg_difference:
263
+ leg.assigned_vehicle_id = leg_difference.get("assignedVehicleId")
264
+ if "boardingStopId" in leg_difference:
265
+ leg.boarding_stop_id = leg_difference.get("boardingStopId")
266
+ if "alightingStopId" in leg_difference:
267
+ leg.alighting_stop_id = leg_difference.get("alightingStopId")
268
+ if "boardingStopIndex" in leg_difference:
269
+ leg.boarding_stop_index = leg_difference.get("boardingStopIndex")
270
+ if "alightingStopIndex" in leg_difference:
271
+ leg.alighting_stop_index = leg_difference.get("alightingStopIndex")
272
+ if "boardingTime" in leg_difference:
273
+ leg.boarding_time = leg_difference.get("boardingTime")
274
+ if "alightingTime" in leg_difference:
275
+ leg.alighting_time = leg_difference.get("alightingTime")
276
+
277
+ passenger.previous_legs = []
278
+ passenger.current_leg = None
279
+ passenger.next_legs = []
280
+
281
+ for leg in all_legs:
282
+ if leg.leg_type == LegType.PREVIOUS:
283
+ passenger.previous_legs.append(leg)
284
+ elif leg.leg_type == LegType.CURRENT:
285
+ passenger.current_leg = leg
286
+ elif leg.leg_type == LegType.NEXT:
287
+ passenger.next_legs.append(leg)
288
+
289
+ def serialize(self) -> dict:
290
+ serialized_data = super().serialize()
291
+
292
+ serialized_data["passengerId"] = self.__passenger_id
293
+
294
+ if self.__differences:
295
+ serialized_data["differences"] = self.__differences
296
+ if self.__number_of_legs_to_remove > 0:
297
+ serialized_data["numberOfLegsToRemove"] = self.__number_of_legs_to_remove
298
+ if self.__legs_to_add:
299
+ serialized_data["legsToAdd"] = [leg.serialize() for leg in self.__legs_to_add]
300
+ if self.__legs_differences:
301
+ serialized_data["legsDifferences"] = self.__legs_differences
302
+
303
+ return serialized_data
304
+
305
+ @classmethod
306
+ def deserialize(cls, serialized_data: dict | str) -> "PassengerUpdate":
307
+ serialized_data = cls.serialized_data_to_dict(serialized_data)
308
+
309
+ update = Update.deserialize(serialized_data)
310
+
311
+ passenger_update = cls(
312
+ update.update_index,
313
+ update.event_index,
314
+ update.event_name,
315
+ update.timestamp,
316
+ should_compute_difference=False,
317
+ )
318
+
319
+ required_fields = [
320
+ "passengerId",
321
+ ]
322
+ cls.verify_required_fields(serialized_data, required_fields, "PassengerUpdate")
323
+
324
+ # pylint: disable=unused-private-member
325
+ passenger_update.__passenger_id = serialized_data.get("passengerId")
326
+ passenger_update.__differences = serialized_data.get("differences", {})
327
+ passenger_update.__number_of_legs_to_remove = serialized_data.get("numberOfLegsToRemove", 0)
328
+ passenger_update.__legs_to_add = [
329
+ VisualizedLeg.deserialize(leg_data) for leg_data in serialized_data.get("legsToAdd", [])
330
+ ]
331
+ passenger_update.__legs_differences = serialized_data.get("legsDifferences", [])
332
+ # pylint: enable=unused-private-member
333
+
334
+ return passenger_update
335
+
336
+
337
+ # MARK: VehicleUpdate
338
+ class VehicleUpdate(Update):
339
+ """
340
+ Differences in a vehicle before and after an event.
341
+ """
342
+
343
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
344
+ self,
345
+ update_index: int,
346
+ event_index: int,
347
+ event_name: str,
348
+ timestamp: float,
349
+ old_vehicle: VisualizedVehicle | None = None,
350
+ new_vehicle: VisualizedVehicle | None = None,
351
+ should_compute_difference: bool = True,
352
+ ):
353
+ super().__init__(UpdateType.VEHICLE, update_index, event_index, event_name, timestamp)
354
+
355
+ self.__vehicle_id: str | None = None
356
+
357
+ # Dictionary containing the new values of the fields that have changed
358
+ self.__differences: dict = {}
359
+
360
+ # Stops are more complex and will be handled separately
361
+ self.__number_of_stops_to_remove: int = 0
362
+ self.__stops_to_add: list[VisualizedStop] = []
363
+
364
+ self.__stops_differences: list[dict] = []
365
+
366
+ # Polylines are only used on the server side to update the polylines file when the simulation is running.
367
+ # We only need to store the new polylines here, and use it when applying the update.
368
+ # In the future, if we want to apply this update in the server by reading the save file,
369
+ # we will need to change this.
370
+ self.__new_polylines: dict[str, tuple[str, list[float]]] | None = (
371
+ new_vehicle.polylines if new_vehicle is not None else None
372
+ )
373
+
374
+ if should_compute_difference:
375
+ self.__compute_difference(old_vehicle, new_vehicle)
376
+
377
+ @property
378
+ def vehicle_id(self) -> str | None:
379
+ return self.__vehicle_id
380
+
381
+ def __compute_difference(
382
+ self, old_vehicle: VisualizedVehicle | None, new_vehicle: VisualizedVehicle | None
383
+ ) -> dict:
384
+ """
385
+ Compute the difference between the old and new vehicle.
386
+ """
387
+
388
+ if new_vehicle is None:
389
+ raise ValueError("New vehicle cannot be None")
390
+
391
+ if old_vehicle is not None and old_vehicle.vehicle_id != new_vehicle.vehicle_id:
392
+ raise ValueError("Old and new vehicle must have the same ID")
393
+
394
+ self.__vehicle_id = new_vehicle.vehicle_id
395
+
396
+ if old_vehicle is None or old_vehicle.mode != new_vehicle.mode:
397
+ self.__differences["mode"] = new_vehicle.mode
398
+
399
+ if old_vehicle is None or old_vehicle.status != new_vehicle.status:
400
+ self.__differences["status"] = convert_vehicle_status_to_string(new_vehicle.status)
401
+
402
+ if old_vehicle is None or old_vehicle.capacity != new_vehicle.capacity:
403
+ self.__differences["capacity"] = new_vehicle.capacity
404
+
405
+ if old_vehicle is None or old_vehicle.name != new_vehicle.name:
406
+ self.__differences["name"] = new_vehicle.name
407
+
408
+ if old_vehicle is None or old_vehicle.tags != new_vehicle.tags:
409
+ self.__differences["tags"] = new_vehicle.tags
410
+
411
+ all_old_stops = old_vehicle.all_stops if old_vehicle is not None else []
412
+ all_new_stops = new_vehicle.all_stops
413
+
414
+ self.__number_of_stops_to_remove = max(0, len(all_old_stops) - len(all_new_stops))
415
+ self.__stops_to_add = all_new_stops[len(all_old_stops) :]
416
+
417
+ for index in range(min(len(all_old_stops), len(all_new_stops))):
418
+ old_stop = all_old_stops[index]
419
+ new_stop = all_new_stops[index]
420
+
421
+ stop_difference = self.__compute_stop_difference(old_stop, new_stop, index)
422
+ if stop_difference is not None:
423
+ self.__stops_differences.append(stop_difference)
424
+
425
+ def __compute_stop_difference(self, old_stop: VisualizedStop, new_stop: VisualizedStop, index: int) -> dict | None:
426
+ """
427
+ Compute the difference between the old and new stop.
428
+ """
429
+ stop_difference = {}
430
+
431
+ if old_stop.arrival_time != new_stop.arrival_time:
432
+ stop_difference["arrivalTime"] = new_stop.arrival_time
433
+
434
+ if old_stop.departure_time != new_stop.departure_time:
435
+ stop_difference["departureTime"] = new_stop.departure_time
436
+
437
+ if old_stop.latitude != new_stop.latitude:
438
+ stop_difference["latitude"] = new_stop.latitude
439
+
440
+ if old_stop.longitude != new_stop.longitude:
441
+ stop_difference["longitude"] = new_stop.longitude
442
+
443
+ if old_stop.capacity != new_stop.capacity:
444
+ stop_difference["capacity"] = new_stop.capacity
445
+
446
+ if old_stop.label != new_stop.label:
447
+ stop_difference["label"] = new_stop.label
448
+
449
+ if old_stop.tags != new_stop.tags:
450
+ stop_difference["tags"] = new_stop.tags
451
+
452
+ if old_stop.stop_type != new_stop.stop_type:
453
+ stop_difference["stopType"] = new_stop.stop_type.value
454
+
455
+ if not stop_difference:
456
+ return None
457
+
458
+ stop_difference["index"] = index
459
+
460
+ return stop_difference
461
+
462
+ def apply(self, environment: VisualizedEnvironment) -> None:
463
+ vehicle = environment.get_vehicle(self.__vehicle_id)
464
+
465
+ if vehicle is None:
466
+ vehicle = VisualizedVehicle(
467
+ self.__vehicle_id,
468
+ self.__differences.get("mode"),
469
+ convert_string_to_vehicle_status(self.__differences.get("status")),
470
+ self.__new_polylines,
471
+ [],
472
+ None,
473
+ [],
474
+ self.__differences.get("capacity"),
475
+ self.__differences.get("name"),
476
+ self.__differences.get("tags"),
477
+ )
478
+
479
+ environment.add_vehicle(vehicle)
480
+
481
+ else:
482
+ vehicle.polylines = self.__new_polylines
483
+
484
+ if "mode" in self.__differences:
485
+ vehicle.mode = self.__differences.get("mode")
486
+ if "status" in self.__differences:
487
+ vehicle.status = convert_string_to_vehicle_status(self.__differences.get("status"))
488
+ if "capacity" in self.__differences:
489
+ vehicle.capacity = self.__differences.get("capacity")
490
+ if "name" in self.__differences:
491
+ vehicle.name = self.__differences.get("name")
492
+ if "tags" in self.__differences:
493
+ vehicle.tags = self.__differences.get("tags")
494
+
495
+ self.__update_stops(vehicle)
496
+
497
+ def __update_stops(self, vehicle: VisualizedVehicle) -> None: # pylint: disable=too-many-branches
498
+ all_stops = vehicle.all_stops
499
+
500
+ if self.__number_of_stops_to_remove > 0:
501
+ all_stops = all_stops[: -self.__number_of_stops_to_remove]
502
+
503
+ all_stops.extend(self.__stops_to_add)
504
+
505
+ for stop_difference in self.__stops_differences:
506
+ stop = all_stops[stop_difference.get("index")]
507
+
508
+ if "arrivalTime" in stop_difference:
509
+ stop.arrival_time = stop_difference.get("arrivalTime")
510
+ if "departureTime" in stop_difference:
511
+ stop.departure_time = stop_difference.get("departureTime")
512
+ if "latitude" in stop_difference:
513
+ stop.latitude = stop_difference.get("latitude")
514
+ if "longitude" in stop_difference:
515
+ stop.longitude = stop_difference.get("longitude")
516
+ if "capacity" in stop_difference:
517
+ stop.capacity = stop_difference.get("capacity")
518
+ if "label" in stop_difference:
519
+ stop.label = stop_difference.get("label")
520
+ if "tags" in stop_difference:
521
+ stop.tags = stop_difference.get("tags")
522
+ if "stopType" in stop_difference:
523
+ stop.stop_type = StopType(stop_difference.get("stopType"))
524
+
525
+ vehicle.previous_stops = []
526
+ vehicle.current_stop = None
527
+ vehicle.next_stops = []
528
+
529
+ for stop in all_stops:
530
+ if stop.stop_type == StopType.PREVIOUS:
531
+ vehicle.previous_stops.append(stop)
532
+ elif stop.stop_type == StopType.CURRENT:
533
+ vehicle.current_stop = stop
534
+ elif stop.stop_type == StopType.NEXT:
535
+ vehicle.next_stops.append(stop)
536
+
537
+ def serialize(self) -> dict:
538
+ serialized_data = super().serialize()
539
+
540
+ serialized_data["vehicleId"] = self.__vehicle_id
541
+
542
+ if self.__differences:
543
+ serialized_data["differences"] = self.__differences
544
+ if self.__number_of_stops_to_remove > 0:
545
+ serialized_data["numberOfStopsToRemove"] = self.__number_of_stops_to_remove
546
+ if self.__stops_to_add:
547
+ serialized_data["stopsToAdd"] = [stop.serialize() for stop in self.__stops_to_add]
548
+ if self.__stops_differences:
549
+ serialized_data["stopsDifferences"] = self.__stops_differences
550
+
551
+ return serialized_data
552
+
553
+ @classmethod
554
+ def deserialize(cls, serialized_data: dict | str) -> "VehicleUpdate":
555
+ serialized_data = cls.serialized_data_to_dict(serialized_data)
556
+
557
+ update = Update.deserialize(serialized_data)
558
+
559
+ vehicle_update = cls(
560
+ update.update_index,
561
+ update.event_index,
562
+ update.event_name,
563
+ update.timestamp,
564
+ should_compute_difference=False,
565
+ )
566
+
567
+ required_fields = [
568
+ "vehicleId",
569
+ ]
570
+ cls.verify_required_fields(serialized_data, required_fields, "VehicleUpdate")
571
+
572
+ # pylint: disable=unused-private-member
573
+ vehicle_update.__vehicle_id = serialized_data.get("vehicleId")
574
+ vehicle_update.__differences = serialized_data.get("differences", {})
575
+ vehicle_update.__number_of_stops_to_remove = serialized_data.get("numberOfStopsToRemove", 0)
576
+ vehicle_update.__stops_to_add = [
577
+ VisualizedStop.deserialize(stop_data) for stop_data in serialized_data.get("stopsToAdd", [])
578
+ ]
579
+ vehicle_update.__stops_differences = serialized_data.get("stopsDifferences", [])
580
+ # pylint: enable=unused-private-member
581
+
582
+ return vehicle_update
583
+
584
+
585
+ # MARK: StatisticsUpdate
586
+ class StatisticsUpdate(Update):
587
+ """
588
+ New statistics computed by the simulation.
589
+ """
590
+
591
+ def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
592
+ self, update_index: int, event_index: int, event_name: str, timestamp: float, statistics: dict
593
+ ):
594
+ super().__init__(UpdateType.STATISTICS, update_index, event_index, event_name, timestamp)
595
+ self.__statistics: dict = statistics
596
+
597
+ def apply(self, environment: VisualizedEnvironment) -> None:
598
+ environment.statistics = self.__statistics
599
+
600
+ def serialize(self) -> dict:
601
+ serialized_data = super().serialize()
602
+ serialized_data["statistics"] = self.__statistics
603
+ return serialized_data
604
+
605
+ @classmethod
606
+ def deserialize(cls, serialized_data: dict | str) -> "StatisticsUpdate":
607
+ serialized_data = cls.serialized_data_to_dict(serialized_data)
608
+
609
+ update = Update.deserialize(serialized_data)
610
+
611
+ required_fields = ["statistics"]
612
+ cls.verify_required_fields(serialized_data, required_fields, "StatisticsUpdate")
613
+
614
+ return cls(
615
+ update.update_index, update.event_index, update.event_name, update.timestamp, serialized_data["statistics"]
616
+ )