multimodalsim-viewer 0.0.2__py3-none-any.whl → 0.1.0.0__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 +567 -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/chunk-BQ2VC5TN.js +7 -0
- multimodalsim_viewer/ui/static/{chunk-IGIP6IOU.js → chunk-RHGMGEGM.js} +1 -1
- multimodalsim_viewer/ui/static/environment.json +2 -0
- multimodalsim_viewer/ui/static/images/undefined-texture.png +0 -0
- multimodalsim_viewer/ui/static/images/zoomed-out-stop.png +0 -0
- multimodalsim_viewer/ui/static/index.html +2 -2
- multimodalsim_viewer/ui/static/main-EAYQBWLP.js +3648 -0
- multimodalsim_viewer/ui/static/scripts/load-environment.script.js +1 -1
- multimodalsim_viewer/ui/static/styles-257KETL3.css +1 -0
- {multimodalsim_viewer-0.0.2.dist-info → multimodalsim_viewer-0.1.0.0.dist-info}/METADATA +6 -12
- multimodalsim_viewer-0.1.0.0.dist-info/RECORD +53 -0
- multimodalsim_viewer/server/simulation_visualization_data_model.py +0 -1552
- multimodalsim_viewer/ui/static/chunk-6VAXIXEZ.js +0 -7
- multimodalsim_viewer/ui/static/main-FGMGJ32M.js +0 -3648
- multimodalsim_viewer/ui/static/styles-KU7LTPET.css +0 -1
- multimodalsim_viewer-0.0.2.dist-info/RECORD +0 -41
- /multimodalsim_viewer/ui/static/images/{sample-wait.png → passenger.png} +0 -0
- /multimodalsim_viewer/ui/static/images/{sample-stop.png → stop.png} +0 -0
- /multimodalsim_viewer/ui/static/images/{sample-bus.png → vehicle.png} +0 -0
- /multimodalsim_viewer/ui/static/images/{zoom-out-passenger.png → zoomed-out-passenger.png} +0 -0
- /multimodalsim_viewer/ui/static/images/{zoom-out-vehicle.png → zoomed-out-vehicle.png} +0 -0
- {multimodalsim_viewer-0.0.2.dist-info → multimodalsim_viewer-0.1.0.0.dist-info}/WHEEL +0 -0
- {multimodalsim_viewer-0.0.2.dist-info → multimodalsim_viewer-0.1.0.0.dist-info}/entry_points.txt +0 -0
- {multimodalsim_viewer-0.0.2.dist-info → multimodalsim_viewer-0.1.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,567 @@
|
|
1
|
+
import os
|
2
|
+
from io import TextIOWrapper
|
3
|
+
from json import dump, loads
|
4
|
+
|
5
|
+
from filelock import FileLock
|
6
|
+
|
7
|
+
from multimodalsim_viewer.common.utils import (
|
8
|
+
INPUT_DATA_DIRECTORY_PATH,
|
9
|
+
NUMBER_OF_STATES_TO_SEND_AT_ONCE,
|
10
|
+
SIMULATION_SAVE_FILE_SEPARATOR,
|
11
|
+
)
|
12
|
+
from multimodalsim_viewer.models.environment import VisualizedEnvironment
|
13
|
+
from multimodalsim_viewer.models.simulation_information import SimulationInformation
|
14
|
+
from multimodalsim_viewer.models.update import Update
|
15
|
+
|
16
|
+
|
17
|
+
# MARK: Data Manager
|
18
|
+
class SimulationVisualizationDataManager: # pylint: disable=too-many-public-methods
|
19
|
+
"""
|
20
|
+
This class manage reads and writes of simulation data for visualization.
|
21
|
+
"""
|
22
|
+
|
23
|
+
__CORRUPTED_FILE_NAME = ".corrupted"
|
24
|
+
__SAVED_SIMULATIONS_DIRECTORY_NAME = "saved_simulations"
|
25
|
+
__SIMULATION_INFORMATION_FILE_NAME = "simulation_information.json"
|
26
|
+
__STATES_DIRECTORY_NAME = "states"
|
27
|
+
__POLYLINES_DIRECTORY_NAME = "polylines"
|
28
|
+
__POLYLINES_FILE_NAME = "polylines"
|
29
|
+
__POLYLINES_VERSION_FILE_NAME = "version"
|
30
|
+
|
31
|
+
__STATES_UPDATE_INDEX_MINIMUM_LENGTH = 8
|
32
|
+
__STATES_TIMESTAMP_MINIMUM_LENGTH = 8
|
33
|
+
|
34
|
+
# MARK: +- Format
|
35
|
+
@staticmethod
|
36
|
+
def __format_json_readable(data: dict, file: str) -> str:
|
37
|
+
return dump(data, file, indent=2, separators=(",", ": "), sort_keys=True)
|
38
|
+
|
39
|
+
@staticmethod
|
40
|
+
def __format_json_one_line(data: dict | str | int | float | bool, file: str) -> str:
|
41
|
+
# Add new line before if not empty
|
42
|
+
if file.tell() != 0:
|
43
|
+
file.write("\n")
|
44
|
+
return dump(data, file, separators=(",", ":"))
|
45
|
+
|
46
|
+
# MARK: +- File paths
|
47
|
+
@staticmethod
|
48
|
+
def get_saved_simulations_directory_path() -> str:
|
49
|
+
directory_path = os.path.join(
|
50
|
+
SimulationVisualizationDataManager.get_data_directory_path(),
|
51
|
+
SimulationVisualizationDataManager.__SAVED_SIMULATIONS_DIRECTORY_NAME,
|
52
|
+
)
|
53
|
+
|
54
|
+
if not os.path.exists(directory_path):
|
55
|
+
os.makedirs(directory_path)
|
56
|
+
|
57
|
+
return directory_path
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def get_all_saved_simulation_ids() -> list[str]:
|
61
|
+
directory_path = SimulationVisualizationDataManager.get_saved_simulations_directory_path()
|
62
|
+
return os.listdir(directory_path)
|
63
|
+
|
64
|
+
@staticmethod
|
65
|
+
def get_saved_simulation_directory_path(simulation_id: str, should_create=False) -> str:
|
66
|
+
directory_path = SimulationVisualizationDataManager.get_saved_simulations_directory_path()
|
67
|
+
simulation_directory_path = f"{directory_path}/{simulation_id}"
|
68
|
+
|
69
|
+
if should_create and not os.path.exists(simulation_directory_path):
|
70
|
+
os.makedirs(simulation_directory_path)
|
71
|
+
|
72
|
+
return simulation_directory_path
|
73
|
+
|
74
|
+
# MARK: +- Folder size
|
75
|
+
@staticmethod
|
76
|
+
def _get_folder_size(start_path: str) -> int:
|
77
|
+
total_size = 0
|
78
|
+
for directory_path, _, file_names in os.walk(start_path):
|
79
|
+
file_names = [name for name in file_names if not name.endswith(".lock")]
|
80
|
+
for file_name in file_names:
|
81
|
+
file_path = os.path.join(directory_path, file_name)
|
82
|
+
lock = FileLock(f"{file_path}.lock")
|
83
|
+
with lock:
|
84
|
+
total_size += os.path.getsize(file_path)
|
85
|
+
return total_size
|
86
|
+
|
87
|
+
@staticmethod
|
88
|
+
def get_saved_simulation_size(simulation_id: str) -> int:
|
89
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
90
|
+
simulation_id
|
91
|
+
)
|
92
|
+
return SimulationVisualizationDataManager._get_folder_size(simulation_directory_path)
|
93
|
+
|
94
|
+
# MARK: +- Corrupted
|
95
|
+
@staticmethod
|
96
|
+
def is_simulation_corrupted(simulation_id: str) -> bool:
|
97
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
98
|
+
simulation_id, True
|
99
|
+
)
|
100
|
+
|
101
|
+
return os.path.exists(f"{simulation_directory_path}/{SimulationVisualizationDataManager.__CORRUPTED_FILE_NAME}")
|
102
|
+
|
103
|
+
@staticmethod
|
104
|
+
def mark_simulation_as_corrupted(simulation_id: str) -> None:
|
105
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
106
|
+
simulation_id, True
|
107
|
+
)
|
108
|
+
|
109
|
+
file_path = f"{simulation_directory_path}/{SimulationVisualizationDataManager.__CORRUPTED_FILE_NAME}"
|
110
|
+
|
111
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
112
|
+
file.write("")
|
113
|
+
|
114
|
+
# MARK: +- Simulation Information
|
115
|
+
@staticmethod
|
116
|
+
def get_saved_simulation_information_file_path(simulation_id: str) -> str:
|
117
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
118
|
+
simulation_id, True
|
119
|
+
)
|
120
|
+
file_path = (
|
121
|
+
f"{simulation_directory_path}/{SimulationVisualizationDataManager.__SIMULATION_INFORMATION_FILE_NAME}"
|
122
|
+
)
|
123
|
+
|
124
|
+
if not os.path.exists(file_path):
|
125
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
126
|
+
file.write("")
|
127
|
+
|
128
|
+
return file_path
|
129
|
+
|
130
|
+
@staticmethod
|
131
|
+
def set_simulation_information(simulation_id: str, simulation_information: SimulationInformation) -> None:
|
132
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_information_file_path(simulation_id)
|
133
|
+
|
134
|
+
lock = FileLock(f"{file_path}.lock")
|
135
|
+
|
136
|
+
with lock:
|
137
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
138
|
+
SimulationVisualizationDataManager.__format_json_readable(simulation_information.serialize(), file)
|
139
|
+
|
140
|
+
@staticmethod
|
141
|
+
def get_simulation_information(simulation_id: str) -> SimulationInformation:
|
142
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_information_file_path(simulation_id)
|
143
|
+
|
144
|
+
lock = FileLock(f"{file_path}.lock")
|
145
|
+
|
146
|
+
simulation_information = None
|
147
|
+
should_update_simulation_information = False
|
148
|
+
|
149
|
+
with lock:
|
150
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
151
|
+
data = file.read()
|
152
|
+
|
153
|
+
simulation_information = SimulationInformation.deserialize(data)
|
154
|
+
|
155
|
+
# Handle mismatched simulation_id, name, or start_time because of uploads
|
156
|
+
# where the simulation folder has been renamed due to duplicates.
|
157
|
+
start_time, name = simulation_id.split(SIMULATION_SAVE_FILE_SEPARATOR)
|
158
|
+
|
159
|
+
if (
|
160
|
+
simulation_id != simulation_information.simulation_id
|
161
|
+
or name != simulation_information.name
|
162
|
+
or start_time != simulation_information.start_time
|
163
|
+
):
|
164
|
+
simulation_information.simulation_id = simulation_id
|
165
|
+
simulation_information.name = name
|
166
|
+
simulation_information.start_time = start_time
|
167
|
+
|
168
|
+
if simulation_information is not None and should_update_simulation_information:
|
169
|
+
SimulationVisualizationDataManager.set_simulation_information(simulation_id, simulation_information)
|
170
|
+
|
171
|
+
return simulation_information
|
172
|
+
|
173
|
+
# MARK: +- States and updates
|
174
|
+
@staticmethod
|
175
|
+
def get_saved_simulation_states_folder_path(simulation_id: str) -> str:
|
176
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
177
|
+
simulation_id, True
|
178
|
+
)
|
179
|
+
folder_path = f"{simulation_directory_path}/{SimulationVisualizationDataManager.__STATES_DIRECTORY_NAME}"
|
180
|
+
|
181
|
+
if not os.path.exists(folder_path):
|
182
|
+
os.makedirs(folder_path)
|
183
|
+
|
184
|
+
return folder_path
|
185
|
+
|
186
|
+
@staticmethod
|
187
|
+
def get_saved_simulation_state_file_path(simulation_id: str, update_index: int, timestamp: float) -> str:
|
188
|
+
folder_path = SimulationVisualizationDataManager.get_saved_simulation_states_folder_path(simulation_id)
|
189
|
+
|
190
|
+
padded_update_index = str(update_index).zfill(
|
191
|
+
SimulationVisualizationDataManager.__STATES_UPDATE_INDEX_MINIMUM_LENGTH
|
192
|
+
)
|
193
|
+
padded_timestamp = str(int(timestamp)).zfill(
|
194
|
+
SimulationVisualizationDataManager.__STATES_TIMESTAMP_MINIMUM_LENGTH
|
195
|
+
)
|
196
|
+
|
197
|
+
# States and updates are stored in a .jsonl file to speed up reads and writes
|
198
|
+
# Each line is a state (the first line) or an update (the following lines)
|
199
|
+
file_path = f"{folder_path}/{padded_update_index}-{padded_timestamp}.jsonl"
|
200
|
+
|
201
|
+
if not os.path.exists(file_path):
|
202
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
203
|
+
file.write("")
|
204
|
+
|
205
|
+
return file_path
|
206
|
+
|
207
|
+
@staticmethod
|
208
|
+
def get_sorted_states(simulation_id: str) -> list[tuple[int, float]]:
|
209
|
+
folder_path = SimulationVisualizationDataManager.get_saved_simulation_states_folder_path(simulation_id)
|
210
|
+
|
211
|
+
all_states_files = [
|
212
|
+
path for path in os.listdir(folder_path) if path.endswith(".jsonl")
|
213
|
+
] # Filter out lock files
|
214
|
+
|
215
|
+
states = []
|
216
|
+
for state_file in all_states_files:
|
217
|
+
update_index, timestamp = state_file.split("-")
|
218
|
+
states.append((int(update_index), float(timestamp.split(".")[0])))
|
219
|
+
|
220
|
+
return sorted(states, key=lambda x: (x[1], x[0]))
|
221
|
+
|
222
|
+
@staticmethod
|
223
|
+
def save_state(simulation_id: str, environment: VisualizedEnvironment) -> str:
|
224
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_state_file_path(
|
225
|
+
simulation_id, environment.update_index, environment.timestamp
|
226
|
+
)
|
227
|
+
|
228
|
+
lock = FileLock(f"{file_path}.lock")
|
229
|
+
|
230
|
+
with lock:
|
231
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
232
|
+
# Store timestamp
|
233
|
+
SimulationVisualizationDataManager.__format_json_one_line(environment.timestamp, file)
|
234
|
+
|
235
|
+
# Store update index
|
236
|
+
SimulationVisualizationDataManager.__format_json_one_line(environment.update_index, file)
|
237
|
+
|
238
|
+
# Store statistics
|
239
|
+
SimulationVisualizationDataManager.__format_json_one_line(
|
240
|
+
environment.statistics if environment.statistics else {}, file
|
241
|
+
)
|
242
|
+
|
243
|
+
# Store total number of passengers
|
244
|
+
SimulationVisualizationDataManager.__format_json_one_line(len(environment.passengers), file)
|
245
|
+
|
246
|
+
# Store each passenger
|
247
|
+
for passenger in environment.passengers.values():
|
248
|
+
SimulationVisualizationDataManager.__format_json_one_line(passenger.serialize(), file)
|
249
|
+
|
250
|
+
# Store total number of vehicles
|
251
|
+
SimulationVisualizationDataManager.__format_json_one_line(len(environment.vehicles), file)
|
252
|
+
|
253
|
+
# Store each vehicle
|
254
|
+
for vehicle in environment.vehicles.values():
|
255
|
+
SimulationVisualizationDataManager.__format_json_one_line(vehicle.serialize(), file)
|
256
|
+
|
257
|
+
return file_path
|
258
|
+
|
259
|
+
@staticmethod
|
260
|
+
def get_state_to_send(file: TextIOWrapper) -> dict:
|
261
|
+
state = {}
|
262
|
+
|
263
|
+
state["timestamp"] = loads(file.readline().strip())
|
264
|
+
state["updateIndex"] = loads(file.readline().strip())
|
265
|
+
state["statistics"] = file.readline().strip()
|
266
|
+
|
267
|
+
state["passengers"] = []
|
268
|
+
|
269
|
+
num_passengers = loads(file.readline().strip())
|
270
|
+
for _ in range(num_passengers):
|
271
|
+
state["passengers"].append(file.readline().strip())
|
272
|
+
|
273
|
+
state["vehicles"] = []
|
274
|
+
|
275
|
+
num_vehicles = loads(file.readline().strip())
|
276
|
+
for _ in range(num_vehicles):
|
277
|
+
state["vehicles"].append(file.readline().strip())
|
278
|
+
|
279
|
+
return state
|
280
|
+
|
281
|
+
@staticmethod
|
282
|
+
def save_update(file_path: str, update: Update) -> None:
|
283
|
+
lock = FileLock(f"{file_path}.lock")
|
284
|
+
with lock:
|
285
|
+
with open(file_path, "a", encoding="utf-8") as file:
|
286
|
+
SimulationVisualizationDataManager.__format_json_one_line(update.serialize(), file)
|
287
|
+
|
288
|
+
@staticmethod
|
289
|
+
def get_missing_states( # pylint: disable=too-many-locals, too-many-branches, too-many-statements
|
290
|
+
simulation_id: str,
|
291
|
+
visualization_time: float,
|
292
|
+
complete_state_update_indexes: list[int],
|
293
|
+
is_simulation_complete: bool,
|
294
|
+
) -> tuple[list[dict], dict[list[str]], bool]:
|
295
|
+
sorted_states = SimulationVisualizationDataManager.get_sorted_states(simulation_id)
|
296
|
+
|
297
|
+
if len(sorted_states) == 0:
|
298
|
+
return [], {}, False
|
299
|
+
|
300
|
+
if len(complete_state_update_indexes) == len(sorted_states):
|
301
|
+
# If the client has all states, no need to request more
|
302
|
+
return [], {}, True
|
303
|
+
|
304
|
+
necessary_state_index = None
|
305
|
+
|
306
|
+
for index, (update_index, state_timestamp) in enumerate(sorted_states):
|
307
|
+
if necessary_state_index is None and state_timestamp > visualization_time:
|
308
|
+
necessary_state_index = index
|
309
|
+
break
|
310
|
+
|
311
|
+
if necessary_state_index is None:
|
312
|
+
# If the visualization time is after the last state then
|
313
|
+
# The last state is necessary
|
314
|
+
necessary_state_index = len(sorted_states) - 1
|
315
|
+
else:
|
316
|
+
# Else we need the state before the first state with greater timestamp
|
317
|
+
necessary_state_index -= 1
|
318
|
+
|
319
|
+
# Handle negative indexes
|
320
|
+
necessary_state_index = max(0, necessary_state_index)
|
321
|
+
|
322
|
+
missing_states = []
|
323
|
+
missing_updates = {}
|
324
|
+
has_incomplete_states = False
|
325
|
+
|
326
|
+
# We want to load the necessary state first, followed by
|
327
|
+
# the next states and then the previous states in reverse order.
|
328
|
+
indexes_to_load = (
|
329
|
+
[necessary_state_index]
|
330
|
+
# All next states
|
331
|
+
+ list(range(necessary_state_index + 1, len(sorted_states)))
|
332
|
+
# All previous states
|
333
|
+
+ list(range(necessary_state_index - 1, -1, -1))
|
334
|
+
)
|
335
|
+
|
336
|
+
for index in indexes_to_load:
|
337
|
+
update_index, state_timestamp = sorted_states[index]
|
338
|
+
|
339
|
+
# If the client already has the state, skip it.
|
340
|
+
if update_index in complete_state_update_indexes:
|
341
|
+
continue
|
342
|
+
|
343
|
+
# Don't add states if the max number of states is reached
|
344
|
+
# but continue the loop to know which states need to be kept
|
345
|
+
if len(missing_states) >= NUMBER_OF_STATES_TO_SEND_AT_ONCE:
|
346
|
+
continue
|
347
|
+
|
348
|
+
state_file_path = SimulationVisualizationDataManager.get_saved_simulation_state_file_path(
|
349
|
+
simulation_id, update_index, state_timestamp
|
350
|
+
)
|
351
|
+
|
352
|
+
lock = FileLock(f"{state_file_path}.lock")
|
353
|
+
|
354
|
+
with lock:
|
355
|
+
with open(state_file_path, "r", encoding="utf-8") as file:
|
356
|
+
state = SimulationVisualizationDataManager.get_state_to_send(file)
|
357
|
+
state["isComplete"] = is_simulation_complete or (index < len(sorted_states) - 1)
|
358
|
+
missing_states.append(state)
|
359
|
+
|
360
|
+
updates_data = file.readlines()
|
361
|
+
current_state_updates = []
|
362
|
+
for update_data in updates_data:
|
363
|
+
current_state_updates.append(update_data)
|
364
|
+
|
365
|
+
missing_updates[update_index] = current_state_updates
|
366
|
+
|
367
|
+
has_all_states = (
|
368
|
+
len(missing_states) + len(complete_state_update_indexes) == len(sorted_states) and not has_incomplete_states
|
369
|
+
)
|
370
|
+
|
371
|
+
return (missing_states, missing_updates, has_all_states)
|
372
|
+
|
373
|
+
# MARK: +- Polylines
|
374
|
+
|
375
|
+
# The polylines are saved with the following structure :
|
376
|
+
# polylines/
|
377
|
+
# version
|
378
|
+
# polylines.jsonl
|
379
|
+
# { "coordinatesString": "string", "encodedPolyline": "string", "coefficients": [float] }
|
380
|
+
|
381
|
+
@staticmethod
|
382
|
+
def get_saved_simulation_polylines_lock(simulation_id: str) -> FileLock:
|
383
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
384
|
+
simulation_id, True
|
385
|
+
)
|
386
|
+
return FileLock(f"{simulation_directory_path}/polylines.lock")
|
387
|
+
|
388
|
+
@staticmethod
|
389
|
+
def get_saved_simulation_polylines_directory_path(simulation_id: str) -> str:
|
390
|
+
simulation_directory_path = SimulationVisualizationDataManager.get_saved_simulation_directory_path(
|
391
|
+
simulation_id, True
|
392
|
+
)
|
393
|
+
directory_path = f"{simulation_directory_path}/{SimulationVisualizationDataManager.__POLYLINES_DIRECTORY_NAME}"
|
394
|
+
|
395
|
+
if not os.path.exists(directory_path):
|
396
|
+
os.makedirs(directory_path)
|
397
|
+
|
398
|
+
return directory_path
|
399
|
+
|
400
|
+
@staticmethod
|
401
|
+
def get_saved_simulation_polylines_version_file_path(simulation_id: str) -> str:
|
402
|
+
directory_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_directory_path(simulation_id)
|
403
|
+
file_path = f"{directory_path}/{SimulationVisualizationDataManager.__POLYLINES_VERSION_FILE_NAME}"
|
404
|
+
|
405
|
+
if not os.path.exists(file_path):
|
406
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
407
|
+
file.write(str(0))
|
408
|
+
|
409
|
+
return file_path
|
410
|
+
|
411
|
+
@staticmethod
|
412
|
+
def set_polylines_version(simulation_id: str, version: int) -> None:
|
413
|
+
"""
|
414
|
+
Should always be called in a lock.
|
415
|
+
"""
|
416
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_version_file_path(simulation_id)
|
417
|
+
|
418
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
419
|
+
file.write(str(version))
|
420
|
+
|
421
|
+
@staticmethod
|
422
|
+
def get_polylines_version(simulation_id: str) -> int:
|
423
|
+
"""
|
424
|
+
Should always be called in a lock.
|
425
|
+
"""
|
426
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_version_file_path(simulation_id)
|
427
|
+
|
428
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
429
|
+
return int(file.read())
|
430
|
+
|
431
|
+
@staticmethod
|
432
|
+
def get_polylines_version_with_lock(simulation_id: str) -> int:
|
433
|
+
lock = SimulationVisualizationDataManager.get_saved_simulation_polylines_lock(simulation_id)
|
434
|
+
with lock:
|
435
|
+
return SimulationVisualizationDataManager.get_polylines_version(simulation_id)
|
436
|
+
|
437
|
+
@staticmethod
|
438
|
+
def get_saved_simulation_polylines_file_path(simulation_id: str) -> str:
|
439
|
+
directory_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_directory_path(simulation_id)
|
440
|
+
|
441
|
+
file_path = f"{directory_path}/{SimulationVisualizationDataManager.__POLYLINES_FILE_NAME}.jsonl"
|
442
|
+
|
443
|
+
if not os.path.exists(file_path):
|
444
|
+
with open(file_path, "w", encoding="utf-8") as file:
|
445
|
+
file.write("")
|
446
|
+
|
447
|
+
return file_path
|
448
|
+
|
449
|
+
@staticmethod
|
450
|
+
def set_polylines(simulation_id: str, polylines: dict[str, tuple[str, list[float]]]) -> None:
|
451
|
+
|
452
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_file_path(simulation_id)
|
453
|
+
|
454
|
+
lock = SimulationVisualizationDataManager.get_saved_simulation_polylines_lock(simulation_id)
|
455
|
+
|
456
|
+
with lock:
|
457
|
+
# Increment the version to notify the client that the polylines have changed
|
458
|
+
version = SimulationVisualizationDataManager.get_polylines_version(simulation_id)
|
459
|
+
version += 1
|
460
|
+
SimulationVisualizationDataManager.set_polylines_version(simulation_id, version)
|
461
|
+
|
462
|
+
with open(file_path, "a", encoding="utf-8") as file:
|
463
|
+
for coordinates_string, (
|
464
|
+
encoded_polyline,
|
465
|
+
coefficients,
|
466
|
+
) in polylines.items():
|
467
|
+
data = {
|
468
|
+
"coordinatesString": coordinates_string,
|
469
|
+
"encodedPolyline": encoded_polyline,
|
470
|
+
"coefficients": coefficients,
|
471
|
+
}
|
472
|
+
SimulationVisualizationDataManager.__format_json_one_line(data, file)
|
473
|
+
|
474
|
+
@staticmethod
|
475
|
+
def get_polylines(
|
476
|
+
simulation_id: str,
|
477
|
+
) -> tuple[list[str], int]:
|
478
|
+
|
479
|
+
polylines = []
|
480
|
+
|
481
|
+
lock = SimulationVisualizationDataManager.get_saved_simulation_polylines_lock(simulation_id)
|
482
|
+
|
483
|
+
version = 0
|
484
|
+
|
485
|
+
with lock:
|
486
|
+
version = SimulationVisualizationDataManager.get_polylines_version(simulation_id)
|
487
|
+
|
488
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_polylines_file_path(simulation_id)
|
489
|
+
|
490
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
491
|
+
for line in file:
|
492
|
+
polylines.append(line)
|
493
|
+
|
494
|
+
return polylines, version
|
495
|
+
|
496
|
+
@staticmethod
|
497
|
+
def get_all_simulation_states(simulation_id: str) -> tuple[list[str], dict[list[str]]]:
|
498
|
+
states = []
|
499
|
+
updates = {}
|
500
|
+
|
501
|
+
sorted_states = SimulationVisualizationDataManager.get_sorted_states(simulation_id)
|
502
|
+
|
503
|
+
for update_index, timestamp in sorted_states:
|
504
|
+
file_path = SimulationVisualizationDataManager.get_saved_simulation_state_file_path(
|
505
|
+
simulation_id, update_index, timestamp
|
506
|
+
)
|
507
|
+
|
508
|
+
lock = FileLock(f"{file_path}.lock")
|
509
|
+
|
510
|
+
with lock:
|
511
|
+
with open(file_path, "r", encoding="utf-8") as file:
|
512
|
+
state_data = file.readline()
|
513
|
+
states.append(state_data)
|
514
|
+
|
515
|
+
updates_data = file.readlines()
|
516
|
+
current_state_updates = []
|
517
|
+
for update_data in updates_data:
|
518
|
+
current_state_updates.append(update_data)
|
519
|
+
|
520
|
+
updates[update_index] = current_state_updates
|
521
|
+
|
522
|
+
return states, updates
|
523
|
+
|
524
|
+
# MARK: +- Simulation Data
|
525
|
+
@staticmethod
|
526
|
+
def get_data_directory_path() -> str:
|
527
|
+
current_file_path = os.path.abspath(__file__)
|
528
|
+
current_file_dir = os.path.dirname(current_file_path)
|
529
|
+
data_directory_path = os.path.join(current_file_dir, "..", "data")
|
530
|
+
|
531
|
+
if not os.path.exists(data_directory_path):
|
532
|
+
os.makedirs(data_directory_path)
|
533
|
+
|
534
|
+
return data_directory_path
|
535
|
+
|
536
|
+
@staticmethod
|
537
|
+
def get_saved_logs_directory_path() -> str:
|
538
|
+
data_directory_path = SimulationVisualizationDataManager.get_data_directory_path()
|
539
|
+
saved_logs_directory_path = os.path.join(data_directory_path, "saved_logs")
|
540
|
+
|
541
|
+
if not os.path.exists(saved_logs_directory_path):
|
542
|
+
os.makedirs(saved_logs_directory_path)
|
543
|
+
|
544
|
+
return saved_logs_directory_path
|
545
|
+
|
546
|
+
@staticmethod
|
547
|
+
def get_input_data_directory_path(data: str | None = None) -> str:
|
548
|
+
input_data_directory = INPUT_DATA_DIRECTORY_PATH
|
549
|
+
|
550
|
+
if data is not None:
|
551
|
+
input_data_directory = os.path.join(input_data_directory, data)
|
552
|
+
|
553
|
+
return input_data_directory
|
554
|
+
|
555
|
+
@staticmethod
|
556
|
+
def get_available_data():
|
557
|
+
input_data_directory = SimulationVisualizationDataManager.get_input_data_directory_path()
|
558
|
+
|
559
|
+
if not os.path.exists(input_data_directory):
|
560
|
+
return []
|
561
|
+
|
562
|
+
# List all directories in the input data directory
|
563
|
+
return [
|
564
|
+
name
|
565
|
+
for name in os.listdir(input_data_directory)
|
566
|
+
if os.path.isdir(os.path.join(input_data_directory, name)) and not name.startswith(".")
|
567
|
+
]
|
@@ -6,10 +6,7 @@ import zipfile
|
|
6
6
|
|
7
7
|
from flask import Blueprint, jsonify, request, send_file
|
8
8
|
|
9
|
-
from multimodalsim_viewer.
|
10
|
-
from multimodalsim_viewer.server.simulation_visualization_data_model import (
|
11
|
-
SimulationVisualizationDataManager,
|
12
|
-
)
|
9
|
+
from multimodalsim_viewer.server.data_manager import SimulationVisualizationDataManager
|
13
10
|
|
14
11
|
http_routes = Blueprint("http_routes", __name__)
|
15
12
|
|
@@ -80,7 +77,7 @@ def handle_zip_upload(folder_path):
|
|
80
77
|
# MARK: Input Data Routes
|
81
78
|
@http_routes.route("/api/input_data/<folder_name>", methods=["GET"])
|
82
79
|
def export_input_data(folder_name):
|
83
|
-
folder_path = get_input_data_directory_path(folder_name)
|
80
|
+
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
84
81
|
logging.info("Requested folder: %s", folder_path)
|
85
82
|
|
86
83
|
zip_path = zip_folder(folder_path, folder_name)
|
@@ -92,13 +89,13 @@ def export_input_data(folder_name):
|
|
92
89
|
|
93
90
|
@http_routes.route("/api/input_data/<folder_name>", methods=["POST"])
|
94
91
|
def import_input_data(folder_name):
|
95
|
-
folder_path = get_input_data_directory_path(folder_name)
|
92
|
+
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
96
93
|
return handle_zip_upload(folder_path)
|
97
94
|
|
98
95
|
|
99
96
|
@http_routes.route("/api/input_data/<folder_name>", methods=["DELETE"])
|
100
97
|
def delete_input_data(folder_name):
|
101
|
-
folder_path = get_input_data_directory_path(folder_name)
|
98
|
+
folder_path = SimulationVisualizationDataManager.get_input_data_directory_path(folder_name)
|
102
99
|
if not os.path.isdir(folder_path):
|
103
100
|
return jsonify({"error": "Folder not found"}), 404
|
104
101
|
|
@@ -1,8 +1,8 @@
|
|
1
|
-
from multimodalsim_viewer.
|
1
|
+
from multimodalsim_viewer.server.data_manager import SimulationVisualizationDataManager
|
2
2
|
|
3
3
|
|
4
4
|
def register_log(simulation_id, message):
|
5
|
-
saved_logs_directory_path = get_saved_logs_directory_path()
|
5
|
+
saved_logs_directory_path = SimulationVisualizationDataManager.get_saved_logs_directory_path()
|
6
6
|
file_name = f"{simulation_id}.txt"
|
7
7
|
file_path = f"{saved_logs_directory_path}/{file_name}"
|
8
8
|
|
@@ -5,12 +5,8 @@ from flask import Flask
|
|
5
5
|
from flask_cors import CORS
|
6
6
|
from flask_socketio import SocketIO, emit, join_room, leave_room
|
7
7
|
|
8
|
-
from multimodalsim_viewer.common.utils import
|
9
|
-
|
10
|
-
get_available_data,
|
11
|
-
get_session_id,
|
12
|
-
log,
|
13
|
-
)
|
8
|
+
from multimodalsim_viewer.common.utils import CLIENT_ROOM, get_session_id, log
|
9
|
+
from multimodalsim_viewer.server.data_manager import SimulationVisualizationDataManager
|
14
10
|
from multimodalsim_viewer.server.http_routes import http_routes
|
15
11
|
from multimodalsim_viewer.server.simulation_manager import SimulationManager
|
16
12
|
|
@@ -80,17 +76,19 @@ def configure_server() -> tuple[Flask, SocketIO]: # pylint: disable=too-many-st
|
|
80
76
|
@socketio.on("get-available-data")
|
81
77
|
def on_client_get_data():
|
82
78
|
log("getting available data", "client")
|
83
|
-
emit("available-data", get_available_data(), to=CLIENT_ROOM)
|
79
|
+
emit("available-data", SimulationVisualizationDataManager.get_available_data(), to=CLIENT_ROOM)
|
84
80
|
|
85
81
|
@socketio.on("get-missing-simulation-states")
|
86
|
-
def on_client_get_missing_simulation_states(simulation_id, visualization_time,
|
82
|
+
def on_client_get_missing_simulation_states(simulation_id, visualization_time, complete_state_update_indexes):
|
87
83
|
log(
|
88
84
|
f"getting missing simulation states for {simulation_id} "
|
89
85
|
f"with visualization time {visualization_time} "
|
90
|
-
f"and {len(
|
86
|
+
f"and {len(complete_state_update_indexes)} complete state update indexes",
|
91
87
|
"client",
|
92
88
|
)
|
93
|
-
simulation_manager.emit_missing_simulation_states(
|
89
|
+
simulation_manager.emit_missing_simulation_states(
|
90
|
+
simulation_id, visualization_time, complete_state_update_indexes
|
91
|
+
)
|
94
92
|
|
95
93
|
@socketio.on("get-polylines")
|
96
94
|
def on_client_get_polylines(simulation_id):
|
@@ -12,14 +12,13 @@ from multimodalsim.statistics.data_analyzer import FixedLineDataAnalyzer
|
|
12
12
|
|
13
13
|
from multimodalsim_viewer.common.utils import (
|
14
14
|
build_simulation_id,
|
15
|
-
get_available_data,
|
16
|
-
get_input_data_directory_path,
|
17
15
|
set_event_on_input,
|
18
16
|
verify_simulation_name,
|
19
17
|
)
|
20
|
-
from multimodalsim_viewer.server.
|
18
|
+
from multimodalsim_viewer.server.data_collector import (
|
21
19
|
SimulationVisualizationDataCollector,
|
22
20
|
)
|
21
|
+
from multimodalsim_viewer.server.data_manager import SimulationVisualizationDataManager
|
23
22
|
|
24
23
|
|
25
24
|
def run_simulation(
|
@@ -44,7 +43,7 @@ def run_simulation(
|
|
44
43
|
[StandardDataCollector(data_container), data_collector],
|
45
44
|
)
|
46
45
|
|
47
|
-
simulation_data_directory = get_input_data_directory_path(data) + "/"
|
46
|
+
simulation_data_directory = SimulationVisualizationDataManager.get_input_data_directory_path(data) + "/"
|
48
47
|
|
49
48
|
if not os.path.exists(simulation_data_directory):
|
50
49
|
print(f"Simulation data directory {simulation_data_directory} does not exist")
|
@@ -110,7 +109,7 @@ def start_simulation_cli(parsed_arguments: Namespace) -> None:
|
|
110
109
|
|
111
110
|
name = name.replace(" ", "_")
|
112
111
|
|
113
|
-
available_data = get_available_data()
|
112
|
+
available_data = SimulationVisualizationDataManager.get_available_data()
|
114
113
|
|
115
114
|
if len(available_data) == 0:
|
116
115
|
print("No input data is available, please provide some in the data folder")
|