|
1 | 1 | """Load trajectories to the internal trajectory data format.""" |
| 2 | +import math |
2 | 3 | import pathlib |
3 | 4 | import sqlite3 |
4 | 5 | from enum import Enum |
@@ -449,3 +450,134 @@ def load_walkable_area_from_ped_data_archive_hdf5( |
449 | 450 | walkable_area = WalkableArea(hdf5_file.attrs["wkt_geometry"]) |
450 | 451 |
|
451 | 452 | return walkable_area |
| 453 | + |
| 454 | + |
| 455 | +def load_trajectory_from_viswalk( |
| 456 | + *, |
| 457 | + trajectory_file: pathlib.Path, |
| 458 | +) -> TrajectoryData: |
| 459 | + """Loads data from Viswalk-csv file as :class:`~trajectory_data.TrajectoryData`. |
| 460 | +
|
| 461 | + This function reads a CSV file containing trajectory data from Viswalk simulations and |
| 462 | + converts it into a :class:`~trajectory_data.TrajectoryData` object which can be used for |
| 463 | + further analysis and processing in the *PedPy* framework. |
| 464 | +
|
| 465 | + .. note:: |
| 466 | +
|
| 467 | + Viswalk data have a time column, that is going to be converted to a frame column for use |
| 468 | + with *PedPy*. |
| 469 | +
|
| 470 | + .. warning:: |
| 471 | +
|
| 472 | + Currently only Viswalk files with a time column can be loaded. |
| 473 | +
|
| 474 | + Args: |
| 475 | + trajectory_file: The full path of the CSV file containing the Viswalk |
| 476 | + trajectory data. The expected format is a CSV file with :code:`;` as delimiter, and it |
| 477 | + should contain at least the following columns: NO, SIMSEC, COORDCENTX, COORDCENTY. |
| 478 | + Comment lines may start with a :code:`*` and will be ignored. |
| 479 | +
|
| 480 | + Returns: |
| 481 | + :class:`~trajectory_data.TrajectoryData` representation of the file data |
| 482 | +
|
| 483 | + Raises: |
| 484 | + LoadTrajectoryError: If the provided path does not exist or is not a file. |
| 485 | + """ |
| 486 | + _validate_is_file(trajectory_file) |
| 487 | + |
| 488 | + traj_dataframe = _load_trajectory_data_from_viswalk( |
| 489 | + trajectory_file=trajectory_file |
| 490 | + ) |
| 491 | + traj_dataframe["frame"], traj_frame_rate = _calculate_frames_and_fps( |
| 492 | + traj_dataframe |
| 493 | + ) |
| 494 | + |
| 495 | + return TrajectoryData( |
| 496 | + data=traj_dataframe[[ID_COL, FRAME_COL, X_COL, Y_COL]], |
| 497 | + frame_rate=traj_frame_rate, |
| 498 | + ) |
| 499 | + |
| 500 | + |
| 501 | +def _calculate_frames_and_fps( |
| 502 | + traj_dataframe: pd.DataFrame, |
| 503 | +) -> Tuple[pd.Series, int]: |
| 504 | + """Calculates fps and frames based on the time column of the dataframe.""" |
| 505 | + mean_diff = traj_dataframe.groupby(ID_COL)["time"].diff().dropna().mean() |
| 506 | + if math.isnan(mean_diff): |
| 507 | + raise LoadTrajectoryError( |
| 508 | + "Can not determine the frame rate used to write the trajectory file. " |
| 509 | + "This may happen, if the file only contains data for a single frame." |
| 510 | + ) |
| 511 | + |
| 512 | + fps = int(round(1 / mean_diff)) |
| 513 | + frames = traj_dataframe["time"] * fps |
| 514 | + frames = frames.round().astype("int64") |
| 515 | + return frames, fps |
| 516 | + |
| 517 | + |
| 518 | +def _load_trajectory_data_from_viswalk( |
| 519 | + *, trajectory_file: pathlib.Path |
| 520 | +) -> pd.DataFrame: |
| 521 | + """Parse the trajectory file for trajectory data. |
| 522 | +
|
| 523 | + Args: |
| 524 | + trajectory_file (pathlib.Path): The file containing the trajectory data. |
| 525 | + The expected format is a CSV file with ';' as delimiter, and it should |
| 526 | + contain at least the following columns: NO, SIMSEC, COORDCENTX, COORDCENTY. |
| 527 | + Comment lines may start with a '*' and will be ignored. |
| 528 | +
|
| 529 | + Returns: |
| 530 | + The trajectory data as :class:`DataFrame`, the coordinates are |
| 531 | + in meter (m). |
| 532 | + """ |
| 533 | + columns_to_keep = ["NO", "SIMSEC", "COORDCENTX", "COORDCENTY"] |
| 534 | + rename_mapping = { |
| 535 | + "NO": ID_COL, |
| 536 | + "SIMSEC": "time", |
| 537 | + "COORDCENTX": X_COL, |
| 538 | + "COORDCENTY": Y_COL, |
| 539 | + } |
| 540 | + common_error_message = ( |
| 541 | + "The given trajectory file seems to be incorrect or empty. " |
| 542 | + "It should contain at least the following columns: " |
| 543 | + "NO, SIMSEC, COORDCENTX, COORDCENTY, separated by ';'. " |
| 544 | + "Comment lines may start with a '*' and will be ignored. " |
| 545 | + f"Please check your trajectory file: {trajectory_file}." |
| 546 | + ) |
| 547 | + try: |
| 548 | + data = pd.read_csv( |
| 549 | + trajectory_file, |
| 550 | + delimiter=";", |
| 551 | + skiprows=1, # skip first row containing '$VISION' |
| 552 | + comment="*", |
| 553 | + dtype={ |
| 554 | + ID_COL: "int64", |
| 555 | + "time": "float64", |
| 556 | + X_COL: "float64", |
| 557 | + Y_COL: "float64", |
| 558 | + }, |
| 559 | + encoding="utf-8-sig", |
| 560 | + ) |
| 561 | + got_columns = data.columns |
| 562 | + cleaned_columns = got_columns.map( |
| 563 | + lambda x: x.replace("$PEDESTRIAN:", "") |
| 564 | + ) |
| 565 | + set_columns_to_keep = set(columns_to_keep) |
| 566 | + set_cleaned_columns = set(cleaned_columns) |
| 567 | + missing_columns = set_columns_to_keep - set_cleaned_columns |
| 568 | + if missing_columns: |
| 569 | + raise LoadTrajectoryError( |
| 570 | + f"{common_error_message}" |
| 571 | + f"Missing columns: {', '.join(missing_columns)}." |
| 572 | + ) |
| 573 | + |
| 574 | + data.columns = cleaned_columns |
| 575 | + data = data[columns_to_keep] |
| 576 | + data.rename(columns=rename_mapping, inplace=True) |
| 577 | + |
| 578 | + if data.empty: |
| 579 | + raise LoadTrajectoryError(common_error_message) |
| 580 | + |
| 581 | + return data |
| 582 | + except pd.errors.ParserError as exc: |
| 583 | + raise LoadTrajectoryError(common_error_message) from exc |
0 commit comments