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.
- multimodalsim_viewer/common/environments/.env +2 -0
- multimodalsim_viewer/common/utils.py +11 -48
- multimodalsim_viewer/models/__init__.py +0 -0
- multimodalsim_viewer/models/environment.py +70 -0
- multimodalsim_viewer/models/leg.py +194 -0
- multimodalsim_viewer/models/passenger.py +148 -0
- multimodalsim_viewer/models/serializable.py +43 -0
- multimodalsim_viewer/models/simulation_information.py +84 -0
- multimodalsim_viewer/models/state.py +44 -0
- multimodalsim_viewer/models/stop.py +114 -0
- multimodalsim_viewer/models/update.py +616 -0
- multimodalsim_viewer/models/vehicle.py +151 -0
- multimodalsim_viewer/server/{simulation_visualization_data_collector.py → data_collector.py} +185 -198
- multimodalsim_viewer/server/data_manager.py +572 -0
- multimodalsim_viewer/server/http_routes.py +4 -7
- multimodalsim_viewer/server/log_manager.py +2 -2
- multimodalsim_viewer/server/server.py +8 -10
- multimodalsim_viewer/server/simulation.py +4 -5
- multimodalsim_viewer/server/simulation_manager.py +22 -23
- multimodalsim_viewer/ui/static/environment.json +2 -0
- multimodalsim_viewer/ui/static/index.html +2 -2
- multimodalsim_viewer/ui/static/{main-LUPJCMAF.js → main-7DV4COXP.js} +173 -173
- multimodalsim_viewer/ui/static/scripts/load-environment.script.js +1 -1
- multimodalsim_viewer/ui/static/styles-257KETL3.css +1 -0
- {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/METADATA +6 -12
- multimodalsim_viewer-0.1.0.1.dist-info/RECORD +53 -0
- multimodalsim_viewer/server/simulation_visualization_data_model.py +0 -1570
- multimodalsim_viewer/ui/static/styles-KU7LTPET.css +0 -1
- multimodalsim_viewer-0.0.3.dist-info/RECORD +0 -43
- {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/WHEEL +0 -0
- {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/entry_points.txt +0 -0
- {multimodalsim_viewer-0.0.3.dist-info → multimodalsim_viewer-0.1.0.1.dist-info}/top_level.txt +0 -0
@@ -1,1570 +0,0 @@
|
|
1
|
-
# pylint: disable=too-many-lines
|
2
|
-
import json
|
3
|
-
import math
|
4
|
-
import os
|
5
|
-
from enum import Enum
|
6
|
-
|
7
|
-
import multimodalsim.optimization.dispatcher # (To avoid circular import error) pylint: disable=unused-import
|
8
|
-
from filelock import FileLock
|
9
|
-
from multimodalsim.simulator.environment import Environment
|
10
|
-
from multimodalsim.simulator.request import Leg, Trip
|
11
|
-
from multimodalsim.simulator.stop import Stop
|
12
|
-
from multimodalsim.simulator.vehicle import Route, Vehicle
|
13
|
-
from multimodalsim.state_machine.status import PassengerStatus, VehicleStatus
|
14
|
-
|
15
|
-
from multimodalsim_viewer.common.utils import (
|
16
|
-
SAVE_VERSION,
|
17
|
-
SIMULATION_SAVE_FILE_SEPARATOR,
|
18
|
-
get_data_directory_path,
|
19
|
-
)
|
20
|
-
|
21
|
-
|
22
|
-
# MARK: Enums
|
23
|
-
def convert_passenger_status_to_string(status: PassengerStatus) -> str:
|
24
|
-
if status == PassengerStatus.RELEASE:
|
25
|
-
return "release"
|
26
|
-
if status == PassengerStatus.ASSIGNED:
|
27
|
-
return "assigned"
|
28
|
-
if status == PassengerStatus.READY:
|
29
|
-
return "ready"
|
30
|
-
if status == PassengerStatus.ONBOARD:
|
31
|
-
return "onboard"
|
32
|
-
if status == PassengerStatus.COMPLETE:
|
33
|
-
return "complete"
|
34
|
-
raise ValueError(f"Unknown PassengerStatus {status}")
|
35
|
-
|
36
|
-
|
37
|
-
def convert_vehicle_status_to_string(status: VehicleStatus) -> str:
|
38
|
-
if status == VehicleStatus.RELEASE:
|
39
|
-
return "release"
|
40
|
-
if status == VehicleStatus.IDLE:
|
41
|
-
return "idle"
|
42
|
-
if status == VehicleStatus.BOARDING:
|
43
|
-
return "boarding"
|
44
|
-
if status == VehicleStatus.ENROUTE:
|
45
|
-
return "enroute"
|
46
|
-
if status == VehicleStatus.ALIGHTING:
|
47
|
-
return "alighting"
|
48
|
-
if status == VehicleStatus.COMPLETE:
|
49
|
-
return "complete"
|
50
|
-
raise ValueError(f"Unknown VehicleStatus {status}")
|
51
|
-
|
52
|
-
|
53
|
-
def convert_string_to_passenger_status(status: str) -> PassengerStatus:
|
54
|
-
if status == "release":
|
55
|
-
return PassengerStatus.RELEASE
|
56
|
-
if status == "assigned":
|
57
|
-
return PassengerStatus.ASSIGNED
|
58
|
-
if status == "ready":
|
59
|
-
return PassengerStatus.READY
|
60
|
-
if status == "onboard":
|
61
|
-
return PassengerStatus.ONBOARD
|
62
|
-
if status == "complete":
|
63
|
-
return PassengerStatus.COMPLETE
|
64
|
-
raise ValueError(f"Unknown PassengerStatus {status}")
|
65
|
-
|
66
|
-
|
67
|
-
def convert_string_to_vehicle_status(status: str) -> VehicleStatus:
|
68
|
-
if status == "release":
|
69
|
-
return VehicleStatus.RELEASE
|
70
|
-
if status == "idle":
|
71
|
-
return VehicleStatus.IDLE
|
72
|
-
if status == "boarding":
|
73
|
-
return VehicleStatus.BOARDING
|
74
|
-
if status == "enroute":
|
75
|
-
return VehicleStatus.ENROUTE
|
76
|
-
if status == "alighting":
|
77
|
-
return VehicleStatus.ALIGHTING
|
78
|
-
if status == "complete":
|
79
|
-
return VehicleStatus.COMPLETE
|
80
|
-
raise ValueError(f"Unknown VehicleStatus {status}")
|
81
|
-
|
82
|
-
|
83
|
-
# MARK: Serializable
|
84
|
-
class Serializable:
|
85
|
-
def serialize(self) -> dict:
|
86
|
-
raise NotImplementedError()
|
87
|
-
|
88
|
-
@staticmethod
|
89
|
-
def deserialize(data: str) -> "Serializable":
|
90
|
-
"""
|
91
|
-
Deserialize a dictionary into an instance of the class.
|
92
|
-
|
93
|
-
If the dictionary is not valid, return None.
|
94
|
-
"""
|
95
|
-
raise NotImplementedError()
|
96
|
-
|
97
|
-
|
98
|
-
# MARK: Leg
|
99
|
-
class VisualizedLeg(Serializable):
|
100
|
-
assigned_vehicle_id: str | None
|
101
|
-
boarding_stop_index: int | None
|
102
|
-
alighting_stop_index: int | None
|
103
|
-
boarding_time: float | None
|
104
|
-
alighting_time: float | None
|
105
|
-
assigned_time: float | None
|
106
|
-
tags: list[str]
|
107
|
-
|
108
|
-
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
109
|
-
self,
|
110
|
-
assigned_vehicle_id: str | None,
|
111
|
-
boarding_stop_index: int | None,
|
112
|
-
alighting_stop_index: int | None,
|
113
|
-
boarding_time: float | None,
|
114
|
-
alighting_time: float | None,
|
115
|
-
assigned_time: float | None,
|
116
|
-
tags: list[str],
|
117
|
-
) -> None:
|
118
|
-
self.assigned_vehicle_id = assigned_vehicle_id
|
119
|
-
self.boarding_stop_index = boarding_stop_index
|
120
|
-
self.alighting_stop_index = alighting_stop_index
|
121
|
-
self.boarding_time = boarding_time
|
122
|
-
self.alighting_time = alighting_time
|
123
|
-
self.assigned_time = assigned_time
|
124
|
-
self.tags = tags
|
125
|
-
|
126
|
-
@classmethod
|
127
|
-
def from_leg_environment_and_trip( # pylint: disable=too-many-locals, too-many-branches
|
128
|
-
cls,
|
129
|
-
leg: Leg,
|
130
|
-
environment: Environment,
|
131
|
-
trip: Trip,
|
132
|
-
previous_leg: Leg | None = None,
|
133
|
-
) -> "VisualizedLeg":
|
134
|
-
boarding_stop_index = None
|
135
|
-
alighting_stop_index = None
|
136
|
-
|
137
|
-
route = (
|
138
|
-
environment.get_route_by_vehicle_id(leg.assigned_vehicle.id) if leg.assigned_vehicle is not None else None
|
139
|
-
)
|
140
|
-
|
141
|
-
all_legs = trip.previous_legs + ([trip.current_leg] if trip.current_leg else []) + trip.next_legs
|
142
|
-
|
143
|
-
same_vehicle_leg_index = 0
|
144
|
-
for i, other_leg in enumerate(all_legs):
|
145
|
-
if other_leg.assigned_vehicle == leg.assigned_vehicle:
|
146
|
-
if other_leg == leg:
|
147
|
-
break
|
148
|
-
same_vehicle_leg_index += 1
|
149
|
-
|
150
|
-
if route is not None:
|
151
|
-
all_stops = route.previous_stops.copy()
|
152
|
-
if route.current_stop is not None:
|
153
|
-
all_stops.append(route.current_stop)
|
154
|
-
all_stops += route.next_stops
|
155
|
-
|
156
|
-
trip_found_count = 0
|
157
|
-
|
158
|
-
for i, stop in enumerate(all_stops):
|
159
|
-
if boarding_stop_index is None and trip in (
|
160
|
-
stop.passengers_to_board + stop.boarding_passengers + stop.boarded_passengers
|
161
|
-
):
|
162
|
-
if trip_found_count == same_vehicle_leg_index:
|
163
|
-
boarding_stop_index = i
|
164
|
-
break
|
165
|
-
trip_found_count += 1
|
166
|
-
|
167
|
-
trip_found_count = 0
|
168
|
-
|
169
|
-
for i, stop in enumerate(all_stops):
|
170
|
-
if alighting_stop_index is None and trip in (
|
171
|
-
stop.passengers_to_alight + stop.alighting_passengers + stop.alighted_passengers
|
172
|
-
):
|
173
|
-
if trip_found_count == same_vehicle_leg_index:
|
174
|
-
alighting_stop_index = i
|
175
|
-
break
|
176
|
-
trip_found_count += 1
|
177
|
-
|
178
|
-
assigned_vehicle_id = leg.assigned_vehicle.id if leg.assigned_vehicle is not None else None
|
179
|
-
|
180
|
-
assigned_time = None
|
181
|
-
if assigned_vehicle_id is not None:
|
182
|
-
if previous_leg is not None and previous_leg.assigned_time is not None:
|
183
|
-
assigned_time = previous_leg.assigned_time
|
184
|
-
else:
|
185
|
-
assigned_time = environment.current_time
|
186
|
-
|
187
|
-
return cls(
|
188
|
-
assigned_vehicle_id,
|
189
|
-
boarding_stop_index,
|
190
|
-
alighting_stop_index,
|
191
|
-
leg.boarding_time,
|
192
|
-
leg.alighting_time,
|
193
|
-
assigned_time,
|
194
|
-
leg.tags,
|
195
|
-
)
|
196
|
-
|
197
|
-
def serialize(self) -> dict:
|
198
|
-
serialized = {}
|
199
|
-
|
200
|
-
if self.assigned_vehicle_id is not None:
|
201
|
-
serialized["assignedVehicleId"] = self.assigned_vehicle_id
|
202
|
-
|
203
|
-
if self.boarding_stop_index is not None:
|
204
|
-
serialized["boardingStopIndex"] = self.boarding_stop_index
|
205
|
-
|
206
|
-
if self.alighting_stop_index is not None:
|
207
|
-
serialized["alightingStopIndex"] = self.alighting_stop_index
|
208
|
-
|
209
|
-
if self.boarding_time is not None:
|
210
|
-
serialized["boardingTime"] = self.boarding_time
|
211
|
-
|
212
|
-
if self.alighting_time is not None:
|
213
|
-
serialized["alightingTime"] = self.alighting_time
|
214
|
-
if self.assigned_time is not None:
|
215
|
-
serialized["assignedTime"] = self.assigned_time
|
216
|
-
|
217
|
-
if len(self.tags) > 0:
|
218
|
-
serialized["tags"] = self.tags
|
219
|
-
|
220
|
-
return serialized
|
221
|
-
|
222
|
-
@staticmethod
|
223
|
-
def deserialize(data: str) -> "VisualizedLeg":
|
224
|
-
if isinstance(data, str):
|
225
|
-
data = json.loads(data.replace("'", '"'))
|
226
|
-
|
227
|
-
assigned_vehicle_id = data.get("assignedVehicleId", None)
|
228
|
-
boarding_stop_index = data.get("boardingStopIndex", None)
|
229
|
-
alighting_stop_index = data.get("alightingStopIndex", None)
|
230
|
-
boarding_time = data.get("boardingTime", None)
|
231
|
-
alighting_time = data.get("alightingTime", None)
|
232
|
-
assigned_time = data.get("assignedTime", None)
|
233
|
-
tags = data.get("tags", [])
|
234
|
-
|
235
|
-
return VisualizedLeg(
|
236
|
-
assigned_vehicle_id,
|
237
|
-
boarding_stop_index,
|
238
|
-
alighting_stop_index,
|
239
|
-
boarding_time,
|
240
|
-
alighting_time,
|
241
|
-
assigned_time,
|
242
|
-
tags,
|
243
|
-
)
|
244
|
-
|
245
|
-
|
246
|
-
# MARK: Passenger
|
247
|
-
class VisualizedPassenger(Serializable): # pylint: disable=too-many-instance-attributes
|
248
|
-
passenger_id: str
|
249
|
-
name: str | None
|
250
|
-
status: PassengerStatus
|
251
|
-
number_of_passengers: int
|
252
|
-
|
253
|
-
previous_legs: list[VisualizedLeg]
|
254
|
-
current_leg: VisualizedLeg | None
|
255
|
-
next_legs: list[VisualizedLeg]
|
256
|
-
|
257
|
-
tags: list[str]
|
258
|
-
|
259
|
-
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
260
|
-
self,
|
261
|
-
passenger_id: str,
|
262
|
-
name: str | None,
|
263
|
-
status: PassengerStatus,
|
264
|
-
number_of_passengers: int,
|
265
|
-
previous_legs: list[VisualizedLeg],
|
266
|
-
current_leg: VisualizedLeg | None,
|
267
|
-
next_legs: list[VisualizedLeg],
|
268
|
-
tags: list[str],
|
269
|
-
) -> None:
|
270
|
-
self.passenger_id = passenger_id
|
271
|
-
self.name = name
|
272
|
-
self.status = status
|
273
|
-
self.number_of_passengers = number_of_passengers
|
274
|
-
|
275
|
-
self.previous_legs = previous_legs
|
276
|
-
self.current_leg = current_leg
|
277
|
-
self.next_legs = next_legs
|
278
|
-
|
279
|
-
self.tags = tags
|
280
|
-
|
281
|
-
@classmethod
|
282
|
-
def from_trip_and_environment(cls, trip: Trip, environment: Environment) -> "VisualizedPassenger":
|
283
|
-
previous_legs = [
|
284
|
-
VisualizedLeg.from_leg_environment_and_trip(leg, environment, trip) for leg in trip.previous_legs
|
285
|
-
]
|
286
|
-
current_leg = (
|
287
|
-
VisualizedLeg.from_leg_environment_and_trip(trip.current_leg, environment, trip)
|
288
|
-
if trip.current_leg is not None
|
289
|
-
else None
|
290
|
-
)
|
291
|
-
next_legs = [VisualizedLeg.from_leg_environment_and_trip(leg, environment, trip) for leg in trip.next_legs]
|
292
|
-
|
293
|
-
return cls(
|
294
|
-
trip.id, trip.name, trip.status, trip.nb_passengers, previous_legs, current_leg, next_legs, trip.tags
|
295
|
-
)
|
296
|
-
|
297
|
-
def serialize(self) -> dict:
|
298
|
-
serialized = {
|
299
|
-
"id": self.passenger_id,
|
300
|
-
"status": convert_passenger_status_to_string(self.status),
|
301
|
-
"numberOfPassengers": self.number_of_passengers,
|
302
|
-
}
|
303
|
-
|
304
|
-
if self.name is not None:
|
305
|
-
serialized["name"] = self.name
|
306
|
-
|
307
|
-
serialized["previousLegs"] = [leg.serialize() for leg in self.previous_legs]
|
308
|
-
|
309
|
-
if self.current_leg is not None:
|
310
|
-
serialized["currentLeg"] = self.current_leg.serialize()
|
311
|
-
|
312
|
-
serialized["nextLegs"] = [leg.serialize() for leg in self.next_legs]
|
313
|
-
|
314
|
-
if len(self.tags) > 0:
|
315
|
-
serialized["tags"] = self.tags
|
316
|
-
|
317
|
-
return serialized
|
318
|
-
|
319
|
-
@staticmethod
|
320
|
-
def deserialize(data: str) -> "VisualizedPassenger":
|
321
|
-
if isinstance(data, str):
|
322
|
-
data = json.loads(data.replace("'", '"'))
|
323
|
-
|
324
|
-
if (
|
325
|
-
"id" not in data
|
326
|
-
or "status" not in data
|
327
|
-
or "previousLegs" not in data
|
328
|
-
or "nextLegs" not in data
|
329
|
-
or "numberOfPassengers" not in data
|
330
|
-
):
|
331
|
-
raise ValueError("Invalid data for VisualizedPassenger")
|
332
|
-
|
333
|
-
passenger_id = str(data["id"])
|
334
|
-
name = data.get("name", None)
|
335
|
-
status = convert_string_to_passenger_status(data["status"])
|
336
|
-
number_of_passengers = int(data["numberOfPassengers"])
|
337
|
-
|
338
|
-
previous_legs = [VisualizedLeg.deserialize(leg_data) for leg_data in data["previousLegs"]]
|
339
|
-
next_legs = [VisualizedLeg.deserialize(leg_data) for leg_data in data["nextLegs"]]
|
340
|
-
|
341
|
-
current_leg = data.get("currentLeg", None)
|
342
|
-
if current_leg is not None:
|
343
|
-
current_leg = VisualizedLeg.deserialize(current_leg)
|
344
|
-
|
345
|
-
tags = data.get("tags", [])
|
346
|
-
|
347
|
-
return VisualizedPassenger(
|
348
|
-
passenger_id, name, status, number_of_passengers, previous_legs, current_leg, next_legs, tags
|
349
|
-
)
|
350
|
-
|
351
|
-
|
352
|
-
# MARK: Stop
|
353
|
-
class VisualizedStop(Serializable):
|
354
|
-
arrival_time: float
|
355
|
-
departure_time: float | None
|
356
|
-
latitude: float | None
|
357
|
-
longitude: float | None
|
358
|
-
capacity: int | None
|
359
|
-
label: str
|
360
|
-
tags: list[str]
|
361
|
-
|
362
|
-
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
363
|
-
self,
|
364
|
-
arrival_time: float,
|
365
|
-
departure_time: float,
|
366
|
-
latitude: float | None,
|
367
|
-
longitude: float | None,
|
368
|
-
capacity: int | None,
|
369
|
-
label: str,
|
370
|
-
tags: str,
|
371
|
-
) -> None:
|
372
|
-
self.arrival_time = arrival_time
|
373
|
-
self.departure_time = departure_time
|
374
|
-
self.latitude = latitude
|
375
|
-
self.longitude = longitude
|
376
|
-
self.capacity = capacity
|
377
|
-
self.label = label
|
378
|
-
self.tags = tags
|
379
|
-
|
380
|
-
@classmethod
|
381
|
-
def from_stop(cls, stop: Stop) -> "VisualizedStop":
|
382
|
-
return cls(
|
383
|
-
stop.arrival_time,
|
384
|
-
stop.departure_time if stop.departure_time != math.inf else None,
|
385
|
-
stop.location.lat,
|
386
|
-
stop.location.lon,
|
387
|
-
stop.capacity,
|
388
|
-
stop.location.label,
|
389
|
-
stop.tags,
|
390
|
-
)
|
391
|
-
|
392
|
-
def serialize(self) -> dict:
|
393
|
-
serialized = {"arrivalTime": self.arrival_time}
|
394
|
-
|
395
|
-
if self.departure_time is not None:
|
396
|
-
serialized["departureTime"] = self.departure_time
|
397
|
-
|
398
|
-
if self.latitude is not None and self.longitude is not None:
|
399
|
-
serialized["position"] = {
|
400
|
-
"latitude": self.latitude,
|
401
|
-
"longitude": self.longitude,
|
402
|
-
}
|
403
|
-
|
404
|
-
if self.capacity is not None:
|
405
|
-
serialized["capacity"] = self.capacity
|
406
|
-
|
407
|
-
serialized["label"] = self.label
|
408
|
-
|
409
|
-
if len(self.tags) > 0:
|
410
|
-
serialized["tags"] = self.tags
|
411
|
-
|
412
|
-
return serialized
|
413
|
-
|
414
|
-
@staticmethod
|
415
|
-
def deserialize(data: str) -> "VisualizedStop":
|
416
|
-
if isinstance(data, str):
|
417
|
-
data = json.loads(data.replace("'", '"'))
|
418
|
-
|
419
|
-
if "arrivalTime" not in data or "label" not in data:
|
420
|
-
raise ValueError("Invalid data for VisualizedStop")
|
421
|
-
|
422
|
-
arrival_time = float(data["arrivalTime"])
|
423
|
-
departure_time = data.get("departureTime", None)
|
424
|
-
|
425
|
-
latitude = None
|
426
|
-
longitude = None
|
427
|
-
|
428
|
-
position = data.get("position", None)
|
429
|
-
|
430
|
-
if position is not None:
|
431
|
-
latitude = position.get("latitude", None)
|
432
|
-
longitude = position.get("longitude", None)
|
433
|
-
|
434
|
-
capacity = data.get("capacity", None)
|
435
|
-
|
436
|
-
if capacity is not None:
|
437
|
-
capacity = int(capacity)
|
438
|
-
|
439
|
-
label = data["label"]
|
440
|
-
|
441
|
-
tags = data.get("tags", [])
|
442
|
-
|
443
|
-
return VisualizedStop(arrival_time, departure_time, latitude, longitude, capacity, label, tags)
|
444
|
-
|
445
|
-
|
446
|
-
# MARK: Vehicle
|
447
|
-
class VisualizedVehicle(Serializable): # pylint: disable=too-many-instance-attributes
|
448
|
-
vehicle_id: str
|
449
|
-
mode: str | None
|
450
|
-
status: VehicleStatus
|
451
|
-
polylines: dict[str, tuple[str, list[float]]] | None
|
452
|
-
previous_stops: list[VisualizedStop]
|
453
|
-
current_stop: VisualizedStop | None
|
454
|
-
next_stops: list[VisualizedStop]
|
455
|
-
capacity: int
|
456
|
-
name: str | None
|
457
|
-
tags: list[str]
|
458
|
-
|
459
|
-
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
460
|
-
self,
|
461
|
-
vehicle_id: str | int,
|
462
|
-
mode: str | None,
|
463
|
-
status: VehicleStatus,
|
464
|
-
polylines: dict[str, tuple[str, list[float]]] | None,
|
465
|
-
previous_stops: list[VisualizedStop],
|
466
|
-
current_stop: VisualizedStop | None,
|
467
|
-
next_stops: list[VisualizedStop],
|
468
|
-
capacity: int,
|
469
|
-
name: str | None,
|
470
|
-
tags: list[str],
|
471
|
-
) -> None:
|
472
|
-
self.vehicle_id = str(vehicle_id)
|
473
|
-
self.mode = mode
|
474
|
-
self.status = status
|
475
|
-
self.polylines = polylines
|
476
|
-
|
477
|
-
self.previous_stops = previous_stops
|
478
|
-
self.current_stop = current_stop
|
479
|
-
self.next_stops = next_stops
|
480
|
-
|
481
|
-
self.capacity = capacity
|
482
|
-
self.name = name
|
483
|
-
|
484
|
-
self.tags = tags
|
485
|
-
|
486
|
-
@property
|
487
|
-
def all_stops(self) -> list[VisualizedStop]:
|
488
|
-
return self.previous_stops + ([self.current_stop] if self.current_stop is not None else []) + self.next_stops
|
489
|
-
|
490
|
-
@classmethod
|
491
|
-
def from_vehicle_and_route(cls, vehicle: Vehicle, route: Route) -> "VisualizedVehicle":
|
492
|
-
previous_stops = [VisualizedStop.from_stop(stop) for stop in route.previous_stops]
|
493
|
-
current_stop = VisualizedStop.from_stop(route.current_stop) if route.current_stop is not None else None
|
494
|
-
next_stops = [VisualizedStop.from_stop(stop) for stop in route.next_stops]
|
495
|
-
return cls(
|
496
|
-
vehicle.id,
|
497
|
-
vehicle.mode,
|
498
|
-
vehicle.status,
|
499
|
-
vehicle.polylines,
|
500
|
-
previous_stops,
|
501
|
-
current_stop,
|
502
|
-
next_stops,
|
503
|
-
vehicle.capacity,
|
504
|
-
vehicle.name,
|
505
|
-
vehicle.tags,
|
506
|
-
)
|
507
|
-
|
508
|
-
def serialize(self) -> dict:
|
509
|
-
serialized = {
|
510
|
-
"id": self.vehicle_id,
|
511
|
-
"status": convert_vehicle_status_to_string(self.status),
|
512
|
-
"previousStops": [stop.serialize() for stop in self.previous_stops],
|
513
|
-
"nextStops": [stop.serialize() for stop in self.next_stops],
|
514
|
-
"capacity": self.capacity,
|
515
|
-
"name": self.name,
|
516
|
-
}
|
517
|
-
|
518
|
-
if self.mode is not None:
|
519
|
-
serialized["mode"] = self.mode
|
520
|
-
|
521
|
-
if self.current_stop is not None:
|
522
|
-
serialized["currentStop"] = self.current_stop.serialize()
|
523
|
-
|
524
|
-
if len(self.tags) > 0:
|
525
|
-
serialized["tags"] = self.tags
|
526
|
-
|
527
|
-
return serialized
|
528
|
-
|
529
|
-
@staticmethod
|
530
|
-
def deserialize(data: str | dict) -> "VisualizedVehicle":
|
531
|
-
if isinstance(data, str):
|
532
|
-
data = json.loads(data.replace("'", '"'))
|
533
|
-
|
534
|
-
required_keys = [
|
535
|
-
"id",
|
536
|
-
"status",
|
537
|
-
"previousStops",
|
538
|
-
"nextStops",
|
539
|
-
"capacity",
|
540
|
-
"name",
|
541
|
-
]
|
542
|
-
if any(key not in data for key in required_keys):
|
543
|
-
raise ValueError("Invalid data for VisualizedVehicle")
|
544
|
-
|
545
|
-
vehicle_id = str(data["id"])
|
546
|
-
mode = data.get("mode", None)
|
547
|
-
status = convert_string_to_vehicle_status(data["status"])
|
548
|
-
previous_stops = [VisualizedStop.deserialize(stop_data) for stop_data in data["previousStops"]]
|
549
|
-
next_stops = [VisualizedStop.deserialize(stop_data) for stop_data in data["nextStops"]]
|
550
|
-
capacity = int(data["capacity"])
|
551
|
-
name = data.get("name", None)
|
552
|
-
|
553
|
-
current_stop = data.get("currentStop", None)
|
554
|
-
if current_stop is not None:
|
555
|
-
current_stop = VisualizedStop.deserialize(current_stop)
|
556
|
-
|
557
|
-
tags = data.get("tags", [])
|
558
|
-
|
559
|
-
return VisualizedVehicle(
|
560
|
-
vehicle_id, mode, status, None, previous_stops, current_stop, next_stops, capacity, name, tags
|
561
|
-
)
|
562
|
-
|
563
|
-
|
564
|
-
# MARK: Environment
|
565
|
-
class VisualizedEnvironment(Serializable):
|
566
|
-
passengers: dict[str, VisualizedPassenger]
|
567
|
-
vehicles: dict[str, VisualizedVehicle]
|
568
|
-
statistic: dict[str, dict[str, dict[str, int]]]
|
569
|
-
timestamp: float
|
570
|
-
estimated_end_time: float
|
571
|
-
order: int
|
572
|
-
|
573
|
-
def __init__(self) -> None:
|
574
|
-
self.passengers = {}
|
575
|
-
self.vehicles = {}
|
576
|
-
self.timestamp = 0
|
577
|
-
self.estimated_end_time = 0
|
578
|
-
self.order = 0
|
579
|
-
self.statistic = None
|
580
|
-
|
581
|
-
def add_passenger(self, passenger: VisualizedPassenger) -> None:
|
582
|
-
self.passengers[passenger.passenger_id] = passenger
|
583
|
-
|
584
|
-
def get_passenger(self, passenger_id: str) -> VisualizedPassenger:
|
585
|
-
if passenger_id in self.passengers:
|
586
|
-
return self.passengers[passenger_id]
|
587
|
-
raise ValueError(f"Passenger {passenger_id} not found")
|
588
|
-
|
589
|
-
def add_vehicle(self, vehicle: VisualizedVehicle) -> None:
|
590
|
-
self.vehicles[vehicle.vehicle_id] = vehicle
|
591
|
-
|
592
|
-
def get_vehicle(self, vehicle_id: str) -> VisualizedVehicle:
|
593
|
-
if vehicle_id in self.vehicles:
|
594
|
-
return self.vehicles[vehicle_id]
|
595
|
-
raise ValueError(f"Vehicle {vehicle_id} not found")
|
596
|
-
|
597
|
-
def serialize(self) -> dict:
|
598
|
-
return {
|
599
|
-
"passengers": [passenger.serialize() for passenger in self.passengers.values()],
|
600
|
-
"vehicles": [vehicle.serialize() for vehicle in self.vehicles.values()],
|
601
|
-
"timestamp": self.timestamp,
|
602
|
-
"estimatedEndTime": self.estimated_end_time,
|
603
|
-
"statistic": self.statistic if self.statistic is not None else {},
|
604
|
-
"order": self.order,
|
605
|
-
}
|
606
|
-
|
607
|
-
@staticmethod
|
608
|
-
def deserialize(data: str) -> "VisualizedEnvironment":
|
609
|
-
if isinstance(data, str):
|
610
|
-
data = json.loads(data.replace("'", '"'))
|
611
|
-
|
612
|
-
required_keys = [
|
613
|
-
"passengers",
|
614
|
-
"vehicles",
|
615
|
-
"timestamp",
|
616
|
-
"estimatedEndTime",
|
617
|
-
"statistic",
|
618
|
-
"order",
|
619
|
-
]
|
620
|
-
if any(key not in data for key in required_keys):
|
621
|
-
raise ValueError("Invalid data for VisualizedEnvironment")
|
622
|
-
|
623
|
-
environment = VisualizedEnvironment()
|
624
|
-
for passenger_data in data["passengers"]:
|
625
|
-
passenger = VisualizedPassenger.deserialize(passenger_data)
|
626
|
-
environment.add_passenger(passenger)
|
627
|
-
|
628
|
-
for vehicle_data in data["vehicles"]:
|
629
|
-
vehicle = VisualizedVehicle.deserialize(vehicle_data)
|
630
|
-
environment.add_vehicle(vehicle)
|
631
|
-
|
632
|
-
environment.timestamp = data["timestamp"]
|
633
|
-
environment.estimated_end_time = data["estimatedEndTime"]
|
634
|
-
environment.statistic = data["statistic"]
|
635
|
-
environment.order = data["order"]
|
636
|
-
|
637
|
-
return environment
|
638
|
-
|
639
|
-
|
640
|
-
# MARK: Updates
|
641
|
-
class UpdateType(Enum):
|
642
|
-
CREATE_PASSENGER = "createPassenger"
|
643
|
-
CREATE_VEHICLE = "createVehicle"
|
644
|
-
UPDATE_PASSENGER_STATUS = "updatePassengerStatus"
|
645
|
-
UPDATE_PASSENGER_LEGS = "updatePassengerLegs"
|
646
|
-
UPDATE_VEHICLE_STATUS = "updateVehicleStatus"
|
647
|
-
UPDATE_VEHICLE_STOPS = "updateVehicleStops"
|
648
|
-
UPDATE_STATISTIC = "updateStatistic"
|
649
|
-
|
650
|
-
|
651
|
-
class StatisticUpdate(Serializable):
|
652
|
-
statistic: dict[str, dict[str, dict[str, int]]]
|
653
|
-
|
654
|
-
def __init__(self, statistic: dict) -> None:
|
655
|
-
self.statistic = statistic
|
656
|
-
|
657
|
-
def serialize(self) -> dict[str, dict[str, dict[str, int]]]:
|
658
|
-
return {"statistic": self.statistic}
|
659
|
-
|
660
|
-
@staticmethod
|
661
|
-
def deserialize(data: str) -> "StatisticUpdate":
|
662
|
-
if isinstance(data, str):
|
663
|
-
data = json.loads(data.replace("'", '"'))
|
664
|
-
|
665
|
-
if "statistic" not in data:
|
666
|
-
raise ValueError("Invalid data for StatisticUpdate")
|
667
|
-
|
668
|
-
return StatisticUpdate(data.statistic)
|
669
|
-
|
670
|
-
|
671
|
-
class PassengerStatusUpdate(Serializable):
|
672
|
-
passenger_id: str
|
673
|
-
status: PassengerStatus
|
674
|
-
|
675
|
-
def __init__(self, passenger_id: str, status: PassengerStatus) -> None:
|
676
|
-
self.passenger_id = passenger_id
|
677
|
-
self.status = status
|
678
|
-
|
679
|
-
@classmethod
|
680
|
-
def from_trip(cls, trip: Trip) -> "PassengerStatusUpdate":
|
681
|
-
return cls(trip.id, trip.status)
|
682
|
-
|
683
|
-
def serialize(self) -> dict:
|
684
|
-
return {
|
685
|
-
"id": self.passenger_id,
|
686
|
-
"status": convert_passenger_status_to_string(self.status),
|
687
|
-
}
|
688
|
-
|
689
|
-
@staticmethod
|
690
|
-
def deserialize(data: str) -> "PassengerStatusUpdate":
|
691
|
-
if isinstance(data, str):
|
692
|
-
data = json.loads(data.replace("'", '"'))
|
693
|
-
|
694
|
-
if "id" not in data or "status" not in data:
|
695
|
-
raise ValueError("Invalid data for PassengerStatusUpdate")
|
696
|
-
|
697
|
-
passenger_id = str(data["id"])
|
698
|
-
status = convert_string_to_passenger_status(data["status"])
|
699
|
-
return PassengerStatusUpdate(passenger_id, status)
|
700
|
-
|
701
|
-
|
702
|
-
class PassengerLegsUpdate(Serializable):
|
703
|
-
passenger_id: str
|
704
|
-
previous_legs: list[VisualizedLeg]
|
705
|
-
current_leg: VisualizedLeg | None
|
706
|
-
next_legs: list[VisualizedLeg]
|
707
|
-
|
708
|
-
def __init__(
|
709
|
-
self,
|
710
|
-
passenger_id: str,
|
711
|
-
previous_legs: list[VisualizedLeg],
|
712
|
-
current_leg: VisualizedLeg | None,
|
713
|
-
next_legs: list[VisualizedLeg],
|
714
|
-
) -> None:
|
715
|
-
self.passenger_id = passenger_id
|
716
|
-
self.previous_legs = previous_legs
|
717
|
-
self.current_leg = current_leg
|
718
|
-
self.next_legs = next_legs
|
719
|
-
|
720
|
-
@classmethod
|
721
|
-
def from_trip_environment_and_previous_passenger(
|
722
|
-
cls,
|
723
|
-
trip: Trip,
|
724
|
-
environment: Environment,
|
725
|
-
previous_passenger: VisualizedPassenger,
|
726
|
-
) -> "PassengerLegsUpdate":
|
727
|
-
all_previous_legs = (
|
728
|
-
previous_passenger.previous_legs
|
729
|
-
+ ([previous_passenger.current_leg] if previous_passenger.current_leg is not None else [])
|
730
|
-
+ previous_passenger.next_legs
|
731
|
-
)
|
732
|
-
current_index = 0
|
733
|
-
|
734
|
-
previous_legs = []
|
735
|
-
for leg in trip.previous_legs:
|
736
|
-
previous_leg = None
|
737
|
-
if current_index < len(all_previous_legs):
|
738
|
-
previous_leg = all_previous_legs[current_index]
|
739
|
-
current_index += 1
|
740
|
-
previous_legs.append(VisualizedLeg.from_leg_environment_and_trip(leg, environment, trip, previous_leg))
|
741
|
-
|
742
|
-
previous_leg = None
|
743
|
-
if trip.current_leg is not None and current_index < len(all_previous_legs):
|
744
|
-
previous_leg = all_previous_legs[current_index]
|
745
|
-
current_index += 1
|
746
|
-
current_leg = (
|
747
|
-
VisualizedLeg.from_leg_environment_and_trip(trip.current_leg, environment, trip, previous_leg)
|
748
|
-
if trip.current_leg is not None
|
749
|
-
else None
|
750
|
-
)
|
751
|
-
|
752
|
-
next_legs = []
|
753
|
-
for leg in trip.next_legs:
|
754
|
-
next_leg = None
|
755
|
-
if current_index < len(all_previous_legs):
|
756
|
-
next_leg = all_previous_legs[current_index]
|
757
|
-
current_index += 1
|
758
|
-
next_legs.append(VisualizedLeg.from_leg_environment_and_trip(leg, environment, trip, next_leg))
|
759
|
-
|
760
|
-
return cls(trip.id, previous_legs, current_leg, next_legs)
|
761
|
-
|
762
|
-
def serialize(self) -> dict:
|
763
|
-
serialized = {
|
764
|
-
"id": self.passenger_id,
|
765
|
-
"previousLegs": [leg.serialize() for leg in self.previous_legs],
|
766
|
-
"nextLegs": [leg.serialize() for leg in self.next_legs],
|
767
|
-
}
|
768
|
-
|
769
|
-
if self.current_leg is not None:
|
770
|
-
serialized["currentLeg"] = self.current_leg.serialize()
|
771
|
-
|
772
|
-
return serialized
|
773
|
-
|
774
|
-
@staticmethod
|
775
|
-
def deserialize(data: str) -> "PassengerLegsUpdate":
|
776
|
-
if isinstance(data, str):
|
777
|
-
data = json.loads(data.replace("'", '"'))
|
778
|
-
|
779
|
-
if "id" not in data or "previousLegs" not in data or "nextLegs" not in data:
|
780
|
-
raise ValueError("Invalid data for PassengerLegsUpdate")
|
781
|
-
|
782
|
-
passenger_id = str(data["id"])
|
783
|
-
previous_legs = [VisualizedLeg.deserialize(leg_data) for leg_data in data["previousLegs"]]
|
784
|
-
next_legs = [VisualizedLeg.deserialize(leg_data) for leg_data in data["nextLegs"]]
|
785
|
-
|
786
|
-
current_leg = data.get("currentLeg", None)
|
787
|
-
if current_leg is not None:
|
788
|
-
current_leg = VisualizedLeg.deserialize(current_leg)
|
789
|
-
|
790
|
-
return PassengerLegsUpdate(passenger_id, previous_legs, current_leg, next_legs)
|
791
|
-
|
792
|
-
|
793
|
-
class VehicleStatusUpdate(Serializable):
|
794
|
-
vehicle_id: str
|
795
|
-
status: VehicleStatus
|
796
|
-
|
797
|
-
def __init__(self, vehicle_id: str, status: VehicleStatus) -> None:
|
798
|
-
self.vehicle_id = vehicle_id
|
799
|
-
self.status = status
|
800
|
-
|
801
|
-
@classmethod
|
802
|
-
def from_vehicle(cls, vehicle: Vehicle) -> "VehicleStatusUpdate":
|
803
|
-
return cls(vehicle.id, vehicle.status)
|
804
|
-
|
805
|
-
def serialize(self) -> dict:
|
806
|
-
return {
|
807
|
-
"id": self.vehicle_id,
|
808
|
-
"status": convert_vehicle_status_to_string(self.status),
|
809
|
-
}
|
810
|
-
|
811
|
-
@staticmethod
|
812
|
-
def deserialize(data: str) -> "VehicleStatusUpdate":
|
813
|
-
if isinstance(data, str):
|
814
|
-
data = json.loads(data.replace("'", '"'))
|
815
|
-
|
816
|
-
if "id" not in data or "status" not in data:
|
817
|
-
raise ValueError("Invalid data for VehicleStatusUpdate")
|
818
|
-
|
819
|
-
vehicle_id = str(data["id"])
|
820
|
-
status = convert_string_to_vehicle_status(data["status"])
|
821
|
-
return VehicleStatusUpdate(vehicle_id, status)
|
822
|
-
|
823
|
-
|
824
|
-
class VehicleStopsUpdate(Serializable):
|
825
|
-
vehicle_id: str
|
826
|
-
previous_stops: list[VisualizedStop]
|
827
|
-
current_stop: VisualizedStop | None
|
828
|
-
next_stops: list[VisualizedStop]
|
829
|
-
|
830
|
-
def __init__(
|
831
|
-
self,
|
832
|
-
vehicle_id: str,
|
833
|
-
previous_stops: list[VisualizedStop],
|
834
|
-
current_stop: VisualizedStop | None,
|
835
|
-
next_stops: list[VisualizedStop],
|
836
|
-
) -> None:
|
837
|
-
self.vehicle_id = vehicle_id
|
838
|
-
self.previous_stops = previous_stops
|
839
|
-
self.current_stop = current_stop
|
840
|
-
self.next_stops = next_stops
|
841
|
-
|
842
|
-
@classmethod
|
843
|
-
def from_vehicle_and_route(cls, vehicle: Vehicle, route: Route) -> "VehicleStopsUpdate":
|
844
|
-
previous_stops = [VisualizedStop.from_stop(stop) for stop in route.previous_stops]
|
845
|
-
current_stop = VisualizedStop.from_stop(route.current_stop) if route.current_stop is not None else None
|
846
|
-
next_stops = [VisualizedStop.from_stop(stop) for stop in route.next_stops]
|
847
|
-
return cls(vehicle.id, previous_stops, current_stop, next_stops)
|
848
|
-
|
849
|
-
def serialize(self) -> dict:
|
850
|
-
serialized = {
|
851
|
-
"id": self.vehicle_id,
|
852
|
-
"previousStops": [stop.serialize() for stop in self.previous_stops],
|
853
|
-
"nextStops": [stop.serialize() for stop in self.next_stops],
|
854
|
-
}
|
855
|
-
|
856
|
-
if self.current_stop is not None:
|
857
|
-
serialized["currentStop"] = self.current_stop.serialize()
|
858
|
-
|
859
|
-
return serialized
|
860
|
-
|
861
|
-
@staticmethod
|
862
|
-
def deserialize(data: str) -> "VehicleStopsUpdate":
|
863
|
-
if isinstance(data, str):
|
864
|
-
data = json.loads(data.replace("'", '"'))
|
865
|
-
|
866
|
-
if "id" not in data or "previousStops" not in data or "nextStops" not in data:
|
867
|
-
raise ValueError("Invalid data for VehicleStopsUpdate")
|
868
|
-
|
869
|
-
vehicle_id = str(data["id"])
|
870
|
-
previous_stops = [VisualizedStop.deserialize(stop_data) for stop_data in data["previousStops"]]
|
871
|
-
next_stops = [VisualizedStop.deserialize(stop_data) for stop_data in data["nextStops"]]
|
872
|
-
|
873
|
-
current_stop = data.get("currentStop", None)
|
874
|
-
if current_stop is not None:
|
875
|
-
current_stop = VisualizedStop.deserialize(current_stop)
|
876
|
-
|
877
|
-
return VehicleStopsUpdate(vehicle_id, previous_stops, current_stop, next_stops)
|
878
|
-
|
879
|
-
|
880
|
-
class Update(Serializable):
|
881
|
-
update_type: UpdateType
|
882
|
-
data: Serializable
|
883
|
-
timestamp: float
|
884
|
-
order: int
|
885
|
-
|
886
|
-
def __init__(
|
887
|
-
self,
|
888
|
-
update_type: UpdateType,
|
889
|
-
data: Serializable,
|
890
|
-
timestamp: float,
|
891
|
-
) -> None:
|
892
|
-
self.update_type = update_type
|
893
|
-
self.data = data
|
894
|
-
self.timestamp = timestamp
|
895
|
-
self.order = 0
|
896
|
-
|
897
|
-
def serialize(self) -> dict:
|
898
|
-
return {
|
899
|
-
"type": self.update_type.value,
|
900
|
-
"data": self.data.serialize(),
|
901
|
-
"timestamp": self.timestamp,
|
902
|
-
"order": self.order,
|
903
|
-
}
|
904
|
-
|
905
|
-
@staticmethod
|
906
|
-
def deserialize(data: str) -> "Update":
|
907
|
-
if isinstance(data, str):
|
908
|
-
data = json.loads(data.replace("'", '"'))
|
909
|
-
|
910
|
-
if "type" not in data or "data" not in data or "timestamp" not in data or "order" not in data:
|
911
|
-
raise ValueError("Invalid data for Update")
|
912
|
-
|
913
|
-
update_type = UpdateType(data["type"])
|
914
|
-
update_data = data["data"]
|
915
|
-
timestamp = float(data["timestamp"])
|
916
|
-
|
917
|
-
if update_type == UpdateType.CREATE_PASSENGER:
|
918
|
-
update_data = VisualizedPassenger.deserialize(update_data)
|
919
|
-
elif update_type == UpdateType.CREATE_VEHICLE:
|
920
|
-
update_data = VisualizedVehicle.deserialize(update_data)
|
921
|
-
elif update_type == UpdateType.UPDATE_PASSENGER_STATUS:
|
922
|
-
update_data = PassengerStatusUpdate.deserialize(update_data)
|
923
|
-
elif update_type == UpdateType.UPDATE_PASSENGER_LEGS:
|
924
|
-
update_data = PassengerLegsUpdate.deserialize(update_data)
|
925
|
-
elif update_type == UpdateType.UPDATE_VEHICLE_STATUS:
|
926
|
-
update_data = VehicleStatusUpdate.deserialize(update_data)
|
927
|
-
elif update_type == UpdateType.UPDATE_VEHICLE_STOPS:
|
928
|
-
update_data = VehicleStopsUpdate.deserialize(update_data)
|
929
|
-
elif update_type == UpdateType.UPDATE_STATISTIC:
|
930
|
-
update_data = StatisticUpdate.deserialize(update_data)
|
931
|
-
|
932
|
-
update = Update(update_type, update_data, timestamp)
|
933
|
-
update.order = data["order"]
|
934
|
-
return update
|
935
|
-
|
936
|
-
|
937
|
-
# MARK: State
|
938
|
-
class VisualizedState(VisualizedEnvironment):
|
939
|
-
updates: list[Update]
|
940
|
-
|
941
|
-
def __init__(self) -> None:
|
942
|
-
super().__init__()
|
943
|
-
self.updates = []
|
944
|
-
|
945
|
-
@classmethod
|
946
|
-
def from_environment(cls, environment: VisualizedEnvironment) -> "VisualizedState":
|
947
|
-
state = cls()
|
948
|
-
state.passengers = environment.passengers
|
949
|
-
state.vehicles = environment.vehicles
|
950
|
-
state.timestamp = environment.timestamp
|
951
|
-
state.estimated_end_time = environment.estimated_end_time
|
952
|
-
state.order = environment.order
|
953
|
-
return state
|
954
|
-
|
955
|
-
def serialize(self) -> dict:
|
956
|
-
serialized = super().serialize()
|
957
|
-
serialized["updates"] = [update.serialize() for update in self.updates]
|
958
|
-
return serialized
|
959
|
-
|
960
|
-
@staticmethod
|
961
|
-
def deserialize(data: str) -> "VisualizedState":
|
962
|
-
if isinstance(data, str):
|
963
|
-
data = json.loads(data.replace("'", '"'))
|
964
|
-
|
965
|
-
if "updates" not in data:
|
966
|
-
raise ValueError("Invalid data for VisualizedState")
|
967
|
-
|
968
|
-
environment = VisualizedEnvironment.deserialize(data)
|
969
|
-
|
970
|
-
state = VisualizedState()
|
971
|
-
state.passengers = environment.passengers
|
972
|
-
state.vehicles = environment.vehicles
|
973
|
-
state.timestamp = environment.timestamp
|
974
|
-
state.estimated_end_time = environment.estimated_end_time
|
975
|
-
state.order = environment.order
|
976
|
-
|
977
|
-
for update_data in data["updates"]:
|
978
|
-
update = Update.deserialize(update_data)
|
979
|
-
state.updates.append(update)
|
980
|
-
|
981
|
-
return state
|
982
|
-
|
983
|
-
|
984
|
-
# MARK: Simulation Information
|
985
|
-
class SimulationInformation(Serializable): # pylint: disable=too-many-instance-attributes
|
986
|
-
version: int
|
987
|
-
simulation_id: str
|
988
|
-
name: str
|
989
|
-
start_time: str
|
990
|
-
data: str
|
991
|
-
simulation_start_time: float | None
|
992
|
-
simulation_end_time: float | None
|
993
|
-
last_update_order: int | None
|
994
|
-
|
995
|
-
def __init__( # pylint: disable=too-many-arguments, too-many-positional-arguments
|
996
|
-
self,
|
997
|
-
simulation_id: str,
|
998
|
-
data: str,
|
999
|
-
simulation_start_time: str | None,
|
1000
|
-
simulation_end_time: str | None,
|
1001
|
-
last_update_order: int | None,
|
1002
|
-
version: int | None,
|
1003
|
-
) -> None:
|
1004
|
-
self.version = version
|
1005
|
-
if self.version is None:
|
1006
|
-
self.version = SAVE_VERSION
|
1007
|
-
|
1008
|
-
self.simulation_id = simulation_id
|
1009
|
-
|
1010
|
-
self.name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)[1]
|
1011
|
-
self.start_time = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)[0]
|
1012
|
-
self.data = data
|
1013
|
-
|
1014
|
-
self.simulation_start_time = simulation_start_time
|
1015
|
-
self.simulation_end_time = simulation_end_time
|
1016
|
-
self.last_update_order = last_update_order
|
1017
|
-
|
1018
|
-
def serialize(self) -> dict:
|
1019
|
-
serialized = {
|
1020
|
-
"version": self.version,
|
1021
|
-
"simulationId": self.simulation_id,
|
1022
|
-
"name": self.name,
|
1023
|
-
"startTime": self.start_time,
|
1024
|
-
"data": self.data,
|
1025
|
-
}
|
1026
|
-
if self.simulation_start_time is not None:
|
1027
|
-
serialized["simulationStartTime"] = self.simulation_start_time
|
1028
|
-
if self.simulation_end_time is not None:
|
1029
|
-
serialized["simulationEndTime"] = self.simulation_end_time
|
1030
|
-
if self.last_update_order is not None:
|
1031
|
-
serialized["lastUpdateOrder"] = self.last_update_order
|
1032
|
-
return serialized
|
1033
|
-
|
1034
|
-
@staticmethod
|
1035
|
-
def deserialize(data: str) -> "SimulationInformation":
|
1036
|
-
if isinstance(data, str):
|
1037
|
-
data = json.loads(data.replace("'", '"'))
|
1038
|
-
|
1039
|
-
if "version" not in data or "simulationId" not in data:
|
1040
|
-
raise ValueError("Invalid data for SimulationInformation")
|
1041
|
-
|
1042
|
-
version = int(data["version"])
|
1043
|
-
simulation_id = str(data["simulationId"])
|
1044
|
-
simulation_data = str(data["data"])
|
1045
|
-
|
1046
|
-
simulation_start_time = data.get("simulationStartTime", None)
|
1047
|
-
simulation_end_time = data.get("simulationEndTime", None)
|
1048
|
-
last_update_order = data.get("lastUpdateOrder", None)
|
1049
|
-
|
1050
|
-
return SimulationInformation(
|
1051
|
-
simulation_id,
|
1052
|
-
simulation_data,
|
1053
|
-
simulation_start_time,
|
1054
|
-
simulation_end_time,
|
1055
|
-
last_update_order,
|
1056
|
-
version,
|
1057
|
-
)
|
1058
|
-
|
1059
|
-
|
1060
|
-
# MARK: SVDM
|
1061
|
-
class SimulationVisualizationDataManager: # pylint: disable=too-many-public-methods
|
1062
|
-
"""
|
1063
|
-
This class manage reads and writes of simulation data for visualization.
|
1064
|
-
"""
|
1065
|
-
|
1066
|
-
__CORRUPTED_FILE_NAME = ".corrupted"
|
1067
|
-
__SAVED_SIMULATIONS_DIRECTORY_NAME = "saved_simulations"
|
1068
|
-
__SIMULATION_INFORMATION_FILE_NAME = "simulation_information.json"
|
1069
|
-
__STATES_DIRECTORY_NAME = "states"
|
1070
|
-
__POLYLINES_DIRECTORY_NAME = "polylines"
|
1071
|
-
__POLYLINES_FILE_NAME = "polylines"
|
1072
|
-
__POLYLINES_VERSION_FILE_NAME = "version"
|
1073
|
-
|
1074
|
-
__STATES_ORDER_MINIMUM_LENGTH = 8
|
1075
|
-
__STATES_TIMESTAMP_MINIMUM_LENGTH = 8
|
1076
|
-
|
1077
|
-
# Only send a maximum of __MAX_STATES_AT_ONCE states at once
|
1078
|
-
# This should be at least 2
|
1079
|
-
__MAX_STATES_AT_ONCE = 2
|
1080
|
-
|
1081
|
-
# The client keeps a maximum of __MAX_STATES_IN_CLIENT_BEFORE_NECESSARY + __MAX_STATES_IN_CLIENT_AFTER_NECESSARY + 1
|
1082
|
-
# states in memory
|
1083
|
-
# The current one, the previous __MAX_STATES_IN_CLIENT_BEFORE_NECESSARY and
|
1084
|
-
# the next __MAX_STATES_IN_CLIENT_AFTER_NECESSARY
|
1085
|
-
# __MAX_STATES_IN_CLIENT_BEFORE_NECESSARY = 24
|
1086
|
-
# __MAX_STATES_IN_CLIENT_AFTER_NECESSARY = 50
|
1087
|
-
|
1088
|
-
# MARK: +- Format
|
1089
|
-
@staticmethod
|
1090
|
-
def __format_json_readable(data: dict, file: str) -> str:
|
1091
|
-
return json.dump(data, file, indent=2, separators=(",", ": "), sort_keys=True)
|
1092
|
-
|
1093
|
-
@staticmethod
|
1094
|
-
def __format_json_one_line(data: dict, file: str) -> str:
|
1095
|
-
# Add new line before if not empty
|
1096
|
-
if file.tell() != 0:
|
1097
|
-
file.write("\n")
|
1098
|
-
return json.dump(data, file, separators=(",", ":"))
|
1099
|
-
|
1100
|
-
# MARK: +- File paths
|
1101
|
-
@staticmethod
|
1102
|
-
def get_saved_simulations_directory_path() -> str:
|
1103
|
-
directory_path = os.path.join(
|
1104
|
-
get_data_directory_path(), SimulationVisualizationDataManager.__SAVED_SIMULATIONS_DIRECTORY_NAME
|
1105
|
-
)
|
1106
|
-
|
1107
|
-
if not os.path.exists(directory_path):
|
1108
|
-
os.makedirs(directory_path)
|
1109
|
-
|
1110
|
-
return directory_path
|
1111
|
-
|
1112
|
-
@staticmethod
|
1113
|
-
def get_all_saved_simulation_ids() -> list[str]:
|
1114
|
-
directory_path = SimulationVisualizationDataManager.get_saved_simulations_directory_path()
|
1115
|
-
return os.listdir(directory_path)
|
1116
|
-
|
1117
|
-
@staticmethod
|
1118
|
-
def get_saved_simulation_directory_path(simulation_id: str, should_create=False) -> str:
|
1119
|
-
directory_path = SimulationVisualizationDataManager.get_saved_simulations_directory_path()
|
1120
|
-
simulation_directory_path = f"{directory_path}/{simulation_id}"
|
1121
|
-
|
1122
|
-
if should_create and not os.path.exists(simulation_directory_path):
|
1123
|
-
os.makedirs(simulation_directory_path)
|
1124
|
-
|
1125
|
-
return simulation_directory_path
|
1126
|
-
|
1127
|
-
# MARK: +- Folder size
|
1128
|
-
@staticmethod
|
1129
|
-
def _get_folder_size(start_path: str) -> int:
|
1130
|
-
total_size = 0
|
1131
|
-
for directory_path, _, file_names in os.walk(start_path):
|
1132
|
-
file_names = [name for name in file_names if not name.endswith(".lock")]
|
1133
|
-
for file_name in file_names:
|
1134
|
-
file_path = os.path.join(directory_path, file_name)
|
1135
|
-
lock = FileLock(f"{file_path}.lock")
|
1136
|
-
with lock:
|
1137
|
-
total_size += os.path.getsize(file_path)
|
1138
|
-
return total_size
|
1139
|
-
|
1140
|
-
@staticmethod
|
1141
|
-
def get_saved_simulation_size(simulation_id: str) -> int:
|
1142
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1143
|
-
simulation_id
|
1144
|
-
)
|
1145
|
-
return SimulationVisualizationDataManager._get_folder_size(simulation_directory_path)
|
1146
|
-
|
1147
|
-
# MARK: +- Corrupted
|
1148
|
-
@staticmethod
|
1149
|
-
def is_simulation_corrupted(simulation_id: str) -> bool:
|
1150
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1151
|
-
simulation_id, True
|
1152
|
-
)
|
1153
|
-
|
1154
|
-
return os.path.exists(f"{simulation_directory_path}/{SimulationVisualizationDataManager.__CORRUPTED_FILE_NAME}")
|
1155
|
-
|
1156
|
-
@staticmethod
|
1157
|
-
def mark_simulation_as_corrupted(simulation_id: str) -> None:
|
1158
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1159
|
-
simulation_id, True
|
1160
|
-
)
|
1161
|
-
|
1162
|
-
file_path = f"{simulation_directory_path}/{SimulationVisualizationDataManager.__CORRUPTED_FILE_NAME}"
|
1163
|
-
|
1164
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1165
|
-
file.write("")
|
1166
|
-
|
1167
|
-
# MARK: +- Simulation Information
|
1168
|
-
@staticmethod
|
1169
|
-
def get_saved_simulation_information_file_path(simulation_id: str) -> str:
|
1170
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1171
|
-
simulation_id, True
|
1172
|
-
)
|
1173
|
-
file_path = (
|
1174
|
-
f"{simulation_directory_path}/{SimulationVisualizationDataManager.__SIMULATION_INFORMATION_FILE_NAME}"
|
1175
|
-
)
|
1176
|
-
|
1177
|
-
if not os.path.exists(file_path):
|
1178
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1179
|
-
file.write("")
|
1180
|
-
|
1181
|
-
return file_path
|
1182
|
-
|
1183
|
-
@staticmethod
|
1184
|
-
def set_simulation_information(simulation_id: str, simulation_information: SimulationInformation) -> None:
|
1185
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_information_file_path(simulation_id)
|
1186
|
-
|
1187
|
-
lock = FileLock(f"{file_path}.lock")
|
1188
|
-
|
1189
|
-
with lock:
|
1190
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1191
|
-
SimulationVisualizationDataManager.__format_json_readable(simulation_information.serialize(), file)
|
1192
|
-
|
1193
|
-
@staticmethod
|
1194
|
-
def get_simulation_information(simulation_id: str) -> SimulationInformation:
|
1195
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_information_file_path(simulation_id)
|
1196
|
-
|
1197
|
-
lock = FileLock(f"{file_path}.lock")
|
1198
|
-
|
1199
|
-
simulation_information = None
|
1200
|
-
should_update_simulation_information = False
|
1201
|
-
|
1202
|
-
with lock:
|
1203
|
-
with open(file_path, "r", encoding="utf-8") as file:
|
1204
|
-
data = file.read()
|
1205
|
-
|
1206
|
-
simulation_information = SimulationInformation.deserialize(data)
|
1207
|
-
|
1208
|
-
# Handle mismatched simulation_id, name, or start_time because of uploads
|
1209
|
-
# where the simulation folder has been renamed due to duplicates.
|
1210
|
-
start_time, name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)
|
1211
|
-
|
1212
|
-
if (
|
1213
|
-
simulation_id != simulation_information.simulation_id
|
1214
|
-
or name != simulation_information.name
|
1215
|
-
or start_time != simulation_information.start_time
|
1216
|
-
):
|
1217
|
-
simulation_information.simulation_id = simulation_id
|
1218
|
-
simulation_information.name = name
|
1219
|
-
simulation_information.start_time = start_time
|
1220
|
-
|
1221
|
-
if simulation_information is not None and should_update_simulation_information:
|
1222
|
-
SimulationVisualizationDataManager.set_simulation_information(simulation_id, simulation_information)
|
1223
|
-
|
1224
|
-
return simulation_information
|
1225
|
-
|
1226
|
-
# MARK: +- States and updates
|
1227
|
-
@staticmethod
|
1228
|
-
def get_saved_simulation_states_folder_path(simulation_id: str) -> str:
|
1229
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1230
|
-
simulation_id, True
|
1231
|
-
)
|
1232
|
-
folder_path = f"{simulation_directory_path}/{SimulationVisualizationDataManager.__STATES_DIRECTORY_NAME}"
|
1233
|
-
|
1234
|
-
if not os.path.exists(folder_path):
|
1235
|
-
os.makedirs(folder_path)
|
1236
|
-
|
1237
|
-
return folder_path
|
1238
|
-
|
1239
|
-
@staticmethod
|
1240
|
-
def get_saved_simulation_state_file_path(simulation_id: str, order: int, timestamp: float) -> str:
|
1241
|
-
folder_path = SimulationVisualizationDataManager.get_saved_simulation_states_folder_path(simulation_id)
|
1242
|
-
|
1243
|
-
padded_order = str(order).zfill(SimulationVisualizationDataManager.__STATES_ORDER_MINIMUM_LENGTH)
|
1244
|
-
padded_timestamp = str(int(timestamp)).zfill(
|
1245
|
-
SimulationVisualizationDataManager.__STATES_TIMESTAMP_MINIMUM_LENGTH
|
1246
|
-
)
|
1247
|
-
|
1248
|
-
# States and updates are stored in a .jsonl file to speed up reads and writes
|
1249
|
-
# Each line is a state (the first line) or an update (the following lines)
|
1250
|
-
file_path = f"{folder_path}/{padded_order}-{padded_timestamp}.jsonl"
|
1251
|
-
|
1252
|
-
if not os.path.exists(file_path):
|
1253
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1254
|
-
file.write("")
|
1255
|
-
|
1256
|
-
return file_path
|
1257
|
-
|
1258
|
-
@staticmethod
|
1259
|
-
def get_sorted_states(simulation_id: str) -> list[tuple[int, float]]:
|
1260
|
-
folder_path = SimulationVisualizationDataManager.get_saved_simulation_states_folder_path(simulation_id)
|
1261
|
-
|
1262
|
-
all_states_files = [
|
1263
|
-
path for path in os.listdir(folder_path) if path.endswith(".jsonl")
|
1264
|
-
] # Filter out lock files
|
1265
|
-
|
1266
|
-
states = []
|
1267
|
-
for state_file in all_states_files:
|
1268
|
-
order, timestamp = state_file.split("-")
|
1269
|
-
states.append((int(order), float(timestamp.split(".")[0])))
|
1270
|
-
|
1271
|
-
return sorted(states, key=lambda x: (x[1], x[0]))
|
1272
|
-
|
1273
|
-
@staticmethod
|
1274
|
-
def save_state(simulation_id: str, environment: VisualizedEnvironment) -> str:
|
1275
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_state_file_path(
|
1276
|
-
simulation_id, environment.order, environment.timestamp
|
1277
|
-
)
|
1278
|
-
|
1279
|
-
lock = FileLock(f"{file_path}.lock")
|
1280
|
-
|
1281
|
-
with lock:
|
1282
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1283
|
-
SimulationVisualizationDataManager.__format_json_one_line(environment.serialize(), file)
|
1284
|
-
|
1285
|
-
return file_path
|
1286
|
-
|
1287
|
-
@staticmethod
|
1288
|
-
def save_update(file_path: str, update: Update) -> None:
|
1289
|
-
lock = FileLock(f"{file_path}.lock")
|
1290
|
-
with lock:
|
1291
|
-
with open(file_path, "a", encoding="utf-8") as file:
|
1292
|
-
SimulationVisualizationDataManager.__format_json_one_line(update.serialize(), file)
|
1293
|
-
|
1294
|
-
@staticmethod
|
1295
|
-
def get_missing_states( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
1296
|
-
simulation_id: str,
|
1297
|
-
visualization_time: float,
|
1298
|
-
loaded_state_orders: list[int],
|
1299
|
-
is_simulation_complete: bool,
|
1300
|
-
) -> tuple[list[str], dict[list[str]], list[int], bool, int, int, int]:
|
1301
|
-
sorted_states = SimulationVisualizationDataManager.get_sorted_states(simulation_id)
|
1302
|
-
|
1303
|
-
if len(sorted_states) == 0:
|
1304
|
-
return ([], {}, [], False, 0, 0, 0)
|
1305
|
-
|
1306
|
-
necessary_state_index = None
|
1307
|
-
|
1308
|
-
for index, (order, state_timestamp) in enumerate(sorted_states):
|
1309
|
-
if necessary_state_index is None and state_timestamp > visualization_time:
|
1310
|
-
necessary_state_index = index
|
1311
|
-
break
|
1312
|
-
|
1313
|
-
if necessary_state_index is None:
|
1314
|
-
# If the visualization time is after the last state then
|
1315
|
-
# The last state is necessary
|
1316
|
-
necessary_state_index = len(sorted_states) - 1
|
1317
|
-
else:
|
1318
|
-
# Else we need the state before the first state with greater timestamp
|
1319
|
-
necessary_state_index -= 1
|
1320
|
-
|
1321
|
-
# Handle negative indexes
|
1322
|
-
necessary_state_index = max(0, necessary_state_index)
|
1323
|
-
|
1324
|
-
state_orders_to_keep = []
|
1325
|
-
missing_states = []
|
1326
|
-
missing_updates = {}
|
1327
|
-
|
1328
|
-
last_state_index_in_client = -1
|
1329
|
-
all_state_indexes_in_client = []
|
1330
|
-
|
1331
|
-
# We want to load the necessary state first, followed by
|
1332
|
-
# the __MAX_STATES_IN_CLIENT_AFTER_NECESSARY next states and
|
1333
|
-
# then the __MAX_STATES_IN_CLIENT_BEFORE_NECESSARY previous states
|
1334
|
-
indexes_to_load = (
|
1335
|
-
[necessary_state_index]
|
1336
|
-
# + [
|
1337
|
-
# next_state_index
|
1338
|
-
# for next_state_index in range(
|
1339
|
-
# necessary_state_index + 1,
|
1340
|
-
# min(
|
1341
|
-
# necessary_state_index
|
1342
|
-
# + SimulationVisualizationDataManager.__MAX_STATES_IN_CLIENT_AFTER_NECESSARY
|
1343
|
-
# + 1,
|
1344
|
-
# len(sorted_states),
|
1345
|
-
# ),
|
1346
|
-
# )
|
1347
|
-
# ]
|
1348
|
-
# + [
|
1349
|
-
# previous_state_index
|
1350
|
-
# for previous_state_index in range(
|
1351
|
-
# necessary_state_index - 1,
|
1352
|
-
# max(
|
1353
|
-
# necessary_state_index
|
1354
|
-
# - SimulationVisualizationDataManager.__MAX_STATES_IN_CLIENT_BEFORE_NECESSARY
|
1355
|
-
# - 1,
|
1356
|
-
# -1,
|
1357
|
-
# ),
|
1358
|
-
# -1,
|
1359
|
-
# )
|
1360
|
-
# ]
|
1361
|
-
# All next states
|
1362
|
-
+ list(range(necessary_state_index + 1, len(sorted_states)))
|
1363
|
-
# All previous states
|
1364
|
-
+ list(range(necessary_state_index - 1, -1, -1))
|
1365
|
-
)
|
1366
|
-
|
1367
|
-
for index in indexes_to_load:
|
1368
|
-
order, state_timestamp = sorted_states[index]
|
1369
|
-
|
1370
|
-
# If the client already has the state, skip it
|
1371
|
-
# except the last state that might have changed
|
1372
|
-
if order in loaded_state_orders and not order == max(loaded_state_orders):
|
1373
|
-
state_orders_to_keep.append(order)
|
1374
|
-
|
1375
|
-
all_state_indexes_in_client.append(index)
|
1376
|
-
|
1377
|
-
last_state_index_in_client = max(last_state_index_in_client, index)
|
1378
|
-
|
1379
|
-
continue
|
1380
|
-
|
1381
|
-
# Don't add states if the max number of states is reached
|
1382
|
-
# but continue the loop to know which states need to be kept
|
1383
|
-
if len(missing_states) >= SimulationVisualizationDataManager.__MAX_STATES_AT_ONCE:
|
1384
|
-
continue
|
1385
|
-
|
1386
|
-
state_file_path = SimulationVisualizationDataManager.get_saved_simulation_state_file_path(
|
1387
|
-
simulation_id, order, state_timestamp
|
1388
|
-
)
|
1389
|
-
|
1390
|
-
lock = FileLock(f"{state_file_path}.lock")
|
1391
|
-
|
1392
|
-
with lock:
|
1393
|
-
with open(state_file_path, "r", encoding="utf-8") as file:
|
1394
|
-
environment_data = file.readline()
|
1395
|
-
missing_states.append(environment_data)
|
1396
|
-
|
1397
|
-
updates_data = file.readlines()
|
1398
|
-
current_state_updates = []
|
1399
|
-
for update_data in updates_data:
|
1400
|
-
current_state_updates.append(update_data)
|
1401
|
-
|
1402
|
-
missing_updates[order] = current_state_updates
|
1403
|
-
|
1404
|
-
all_state_indexes_in_client.append(index)
|
1405
|
-
|
1406
|
-
last_state_index_in_client = max(last_state_index_in_client, index)
|
1407
|
-
|
1408
|
-
client_has_last_state = last_state_index_in_client == len(sorted_states) - 1
|
1409
|
-
client_has_max_states = len(missing_states) + len(state_orders_to_keep) >= len(indexes_to_load)
|
1410
|
-
|
1411
|
-
should_request_more_states = (is_simulation_complete and not client_has_max_states) or (
|
1412
|
-
not is_simulation_complete and (client_has_last_state or not client_has_max_states)
|
1413
|
-
)
|
1414
|
-
|
1415
|
-
first_continuous_state_index = necessary_state_index
|
1416
|
-
last_continuous_state_index = necessary_state_index
|
1417
|
-
|
1418
|
-
all_state_indexes_in_client.sort()
|
1419
|
-
|
1420
|
-
necessary_state_index_index = all_state_indexes_in_client.index(necessary_state_index)
|
1421
|
-
|
1422
|
-
for index in range(necessary_state_index_index - 1, -1, -1):
|
1423
|
-
if all_state_indexes_in_client[index] == first_continuous_state_index - 1:
|
1424
|
-
first_continuous_state_index -= 1
|
1425
|
-
else:
|
1426
|
-
break
|
1427
|
-
|
1428
|
-
for index in range(necessary_state_index_index + 1, len(all_state_indexes_in_client)):
|
1429
|
-
if all_state_indexes_in_client[index] == last_continuous_state_index + 1:
|
1430
|
-
last_continuous_state_index += 1
|
1431
|
-
else:
|
1432
|
-
break
|
1433
|
-
|
1434
|
-
first_continuous_state_order = sorted_states[first_continuous_state_index][0]
|
1435
|
-
last_continuous_state_order = sorted_states[last_continuous_state_index][0]
|
1436
|
-
|
1437
|
-
necessary_state_order = sorted_states[necessary_state_index][0]
|
1438
|
-
|
1439
|
-
return (
|
1440
|
-
missing_states,
|
1441
|
-
missing_updates,
|
1442
|
-
state_orders_to_keep,
|
1443
|
-
should_request_more_states,
|
1444
|
-
first_continuous_state_order,
|
1445
|
-
last_continuous_state_order,
|
1446
|
-
necessary_state_order,
|
1447
|
-
)
|
1448
|
-
|
1449
|
-
# MARK: +- Polylines
|
1450
|
-
|
1451
|
-
# The polylines are saved with the following structure :
|
1452
|
-
# polylines/
|
1453
|
-
# version
|
1454
|
-
# polylines.jsonl
|
1455
|
-
# { "coordinatesString": "string", "encodedPolyline": "string", "coefficients": [float] }
|
1456
|
-
|
1457
|
-
@staticmethod
|
1458
|
-
def get_saved_simulation_polylines_lock(simulation_id: str) -> FileLock:
|
1459
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1460
|
-
simulation_id, True
|
1461
|
-
)
|
1462
|
-
return FileLock(f"{simulation_directory_path}/polylines.lock")
|
1463
|
-
|
1464
|
-
@staticmethod
|
1465
|
-
def get_saved_simulation_polylines_directory_path(simulation_id: str) -> str:
|
1466
|
-
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
1467
|
-
simulation_id, True
|
1468
|
-
)
|
1469
|
-
directory_path = f"{simulation_directory_path}/{SimulationVisualizationDataManager.__POLYLINES_DIRECTORY_NAME}"
|
1470
|
-
|
1471
|
-
if not os.path.exists(directory_path):
|
1472
|
-
os.makedirs(directory_path)
|
1473
|
-
|
1474
|
-
return directory_path
|
1475
|
-
|
1476
|
-
@staticmethod
|
1477
|
-
def get_saved_simulation_polylines_version_file_path(simulation_id: str) -> str:
|
1478
|
-
directory_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_directory_path(simulation_id)
|
1479
|
-
file_path = f"{directory_path}/{SimulationVisualizationDataManager.__POLYLINES_VERSION_FILE_NAME}"
|
1480
|
-
|
1481
|
-
if not os.path.exists(file_path):
|
1482
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1483
|
-
file.write(str(0))
|
1484
|
-
|
1485
|
-
return file_path
|
1486
|
-
|
1487
|
-
@staticmethod
|
1488
|
-
def set_polylines_version(simulation_id: str, version: int) -> None:
|
1489
|
-
"""
|
1490
|
-
Should always be called in a lock.
|
1491
|
-
"""
|
1492
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_version_file_path(simulation_id)
|
1493
|
-
|
1494
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1495
|
-
file.write(str(version))
|
1496
|
-
|
1497
|
-
@staticmethod
|
1498
|
-
def get_polylines_version(simulation_id: str) -> int:
|
1499
|
-
"""
|
1500
|
-
Should always be called in a lock.
|
1501
|
-
"""
|
1502
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_version_file_path(simulation_id)
|
1503
|
-
|
1504
|
-
with open(file_path, "r", encoding="utf-8") as file:
|
1505
|
-
return int(file.read())
|
1506
|
-
|
1507
|
-
@staticmethod
|
1508
|
-
def get_polylines_version_with_lock(simulation_id: str) -> int:
|
1509
|
-
lock = SimulationVisualizationDataManager.get_saved_simulation_polylines_lock(simulation_id)
|
1510
|
-
with lock:
|
1511
|
-
return SimulationVisualizationDataManager.get_polylines_version(simulation_id)
|
1512
|
-
|
1513
|
-
@staticmethod
|
1514
|
-
def get_saved_simulation_polylines_file_path(simulation_id: str) -> str:
|
1515
|
-
directory_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_directory_path(simulation_id)
|
1516
|
-
|
1517
|
-
file_path = f"{directory_path}/{SimulationVisualizationDataManager.__POLYLINES_FILE_NAME}.jsonl"
|
1518
|
-
|
1519
|
-
if not os.path.exists(file_path):
|
1520
|
-
with open(file_path, "w", encoding="utf-8") as file:
|
1521
|
-
file.write("")
|
1522
|
-
|
1523
|
-
return file_path
|
1524
|
-
|
1525
|
-
@staticmethod
|
1526
|
-
def set_polylines(simulation_id: str, polylines: dict[str, tuple[str, list[float]]]) -> None:
|
1527
|
-
|
1528
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_file_path(simulation_id)
|
1529
|
-
|
1530
|
-
lock = SimulationVisualizationDataManager.get_saved_simulation_polylines_lock(simulation_id)
|
1531
|
-
|
1532
|
-
with lock:
|
1533
|
-
# Increment the version to notify the client that the polylines have changed
|
1534
|
-
version = SimulationVisualizationDataManager.get_polylines_version(simulation_id)
|
1535
|
-
version += 1
|
1536
|
-
SimulationVisualizationDataManager.set_polylines_version(simulation_id, version)
|
1537
|
-
|
1538
|
-
with open(file_path, "a", encoding="utf-8") as file:
|
1539
|
-
for coordinates_string, (
|
1540
|
-
encoded_polyline,
|
1541
|
-
coefficients,
|
1542
|
-
) in polylines.items():
|
1543
|
-
data = {
|
1544
|
-
"coordinatesString": coordinates_string,
|
1545
|
-
"encodedPolyline": encoded_polyline,
|
1546
|
-
"coefficients": coefficients,
|
1547
|
-
}
|
1548
|
-
SimulationVisualizationDataManager.__format_json_one_line(data, file)
|
1549
|
-
|
1550
|
-
@staticmethod
|
1551
|
-
def get_polylines(
|
1552
|
-
simulation_id: str,
|
1553
|
-
) -> tuple[list[str], int]:
|
1554
|
-
|
1555
|
-
polylines = []
|
1556
|
-
|
1557
|
-
lock = SimulationVisualizationDataManager.get_saved_simulation_polylines_lock(simulation_id)
|
1558
|
-
|
1559
|
-
version = 0
|
1560
|
-
|
1561
|
-
with lock:
|
1562
|
-
version = SimulationVisualizationDataManager.get_polylines_version(simulation_id)
|
1563
|
-
|
1564
|
-
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_file_path(simulation_id)
|
1565
|
-
|
1566
|
-
with open(file_path, "r", encoding="utf-8") as file:
|
1567
|
-
for line in file:
|
1568
|
-
polylines.append(line)
|
1569
|
-
|
1570
|
-
return polylines, version
|