Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5c5bf9a
draft script for converting MFP CSV to YAML schedule
iuryt Jan 28, 2025
40c7ff9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 28, 2025
366dede
add openpyxl
iuryt Feb 3, 2025
d046444
add mfp_to_yaml function
iuryt Feb 3, 2025
e3199fa
add new command to init to accept mfp file as input
iuryt Feb 3, 2025
d9fe46a
delete files from scripts/
iuryt Feb 3, 2025
7dc9bd7
deleted scripts files
iuryt Feb 3, 2025
a79433c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 3, 2025
11332f8
export the schedule body instead of saving file
iuryt Feb 13, 2025
ad54992
change name of cli param and adapt for new mfp_to_yaml function
iuryt Feb 13, 2025
66adb18
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 13, 2025
2672afa
add warning message for time entry on yaml
iuryt Feb 13, 2025
a370641
change to pydantic and change name of variables
iuryt Feb 13, 2025
b87d944
add XBT
iuryt Feb 13, 2025
eba08b8
accept nonetype time
iuryt Feb 13, 2025
c0a52ac
change to Waypoint to BaseModel and add field_serializer for instrume…
iuryt Feb 13, 2025
526d2af
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 13, 2025
c51043d
remove restriction for version
iuryt Feb 14, 2025
f3daaa7
add checking for columns from excel file
iuryt Feb 14, 2025
4c59420
add unit tests
iuryt Feb 14, 2025
b67b15d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 14, 2025
6f63cd4
Add update comments and var naming
VeckoTheGecko Feb 14, 2025
222df85
Remove buffering from mfp conversion
VeckoTheGecko Feb 14, 2025
c94567b
update references to Waypoint
VeckoTheGecko Feb 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies:
- pip
- pyyaml
- copernicusmarine >= 2
- openpyxl >= 3.1.5

# linting
- pre-commit
Expand Down
27 changes: 23 additions & 4 deletions src/virtualship/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,26 @@
hash_to_filename,
)
from virtualship.expedition.do_expedition import _get_schedule, do_expedition
from virtualship.utils import SCHEDULE, SHIP_CONFIG
from virtualship.utils import SCHEDULE, SHIP_CONFIG, mfp_to_yaml


@click.command()
@click.argument(
"path",
type=click.Path(exists=False, file_okay=False, dir_okay=True),
)
def init(path):
"""Initialize a directory for a new expedition, with an example schedule and ship config files."""
@click.option(
"--mfp_file",
type=str,
default=None,
help='Partially initialise a project from an exported xlsx or csv file from NIOZ\' Marine Facilities Planning tool (specifically the "Export Coordinates > DD" option). User edits are required after initialisation.',
)
def init(path, mfp_file):
"""
Initialize a directory for a new expedition, with an example schedule and ship config files.

If --mfp_file is provided, it will generate the schedule from the MPF file instead.
"""
path = Path(path)
path.mkdir(exist_ok=True)

Expand All @@ -43,7 +53,16 @@ def init(path):
)

config.write_text(utils.get_example_config())
schedule.write_text(utils.get_example_schedule())

if mfp_file:
# Generate schedule.yaml from the MPF file
click.echo(f"Generating schedule from {mfp_file}...")
mfp_to_yaml(
mfp_file, str(path)
) # Pass the path to save in the correct directory
else:
# Create a default example schedule
schedule.write_text(utils.get_example_schedule())

click.echo(f"Created '{config.name}' and '{schedule.name}' at {path}.")

Expand Down
99 changes: 99 additions & 0 deletions src/virtualship/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import os
from functools import lru_cache
from importlib.resources import files
from typing import TextIO

import numpy as np
import pandas as pd
import yaml
from pydantic import BaseModel

Expand Down Expand Up @@ -37,3 +40,99 @@ def _dump_yaml(model: BaseModel, stream: TextIO) -> str | None:
def _generic_load_yaml(data: str, model: BaseModel) -> BaseModel:
"""Load a yaml string into a pydantic model."""
return model.model_validate(yaml.safe_load(data))


def mfp_to_yaml(excel_file_path: str, save_directory: str):
"""
Generates a YAML file (`schedule.yaml`) with spatial and temporal information based on instrument data from MFP excel file.

Parameters
----------
- excel_file_path (str): Path to the Excel file containing coordinate and instrument data.
- save_directory (str): Directory where `schedule.yaml` will be saved.

The function:
1. Reads instrument and location data from the Excel file.
2. Determines the maximum depth and buffer based on the instruments present.
3. Ensures longitude and latitude values remain valid after applying buffer adjustments.
4. Saves `schedule.yaml` in the specified directory.

"""
# Read data from Excel
coordinates_data = pd.read_excel(
excel_file_path,
usecols=["Station Type", "Name", "Latitude", "Longitude", "Instrument"],
)
coordinates_data = coordinates_data.dropna()

# Define maximum depth and buffer for each instrument
instrument_properties = {
"CTD": {"depth": 5000, "buffer": 1},
"DRIFTER": {"depth": 1, "buffer": 5},
"ARGO_FLOAT": {"depth": 2000, "buffer": 5},
}

# Extract unique instruments from dataset
unique_instruments = np.unique(
np.hstack(coordinates_data["Instrument"].apply(lambda a: a.split(", ")).values)
)

# Determine the maximum depth based on the unique instruments
maximum_depth = max(
instrument_properties.get(inst, {"depth": 0})["depth"]
for inst in unique_instruments
)
minimum_depth = 0

# Determine the buffer based on the maximum buffer of the instruments present
buffer = max(
instrument_properties.get(inst, {"buffer": 0})["buffer"]
for inst in unique_instruments
)

# Adjusted spatial range
min_longitude = coordinates_data["Longitude"].min() - buffer
max_longitude = coordinates_data["Longitude"].max() + buffer
min_latitude = coordinates_data["Latitude"].min() - buffer
max_latitude = coordinates_data["Latitude"].max() + buffer

# Template for the YAML output
yaml_output = {
"space_time_region": {
"spatial_range": {
"minimum_longitude": min_longitude,
"maximum_longitude": max_longitude,
"minimum_latitude": min_latitude,
"maximum_latitude": max_latitude,
"minimum_depth": minimum_depth,
"maximum_depth": maximum_depth,
},
"time_range": {
"start_time": "", # Blank start time
"end_time": "", # Blank end time
},
},
"waypoints": [],
}

# Populate waypoints
for _, row in coordinates_data.iterrows():
instruments = row["Instrument"].split(", ")
for instrument in instruments:
waypoint = {
"instrument": instrument,
"location": {
"latitude": row["Latitude"],
"longitude": row["Longitude"],
},
"time": "", # Blank time
}
yaml_output["waypoints"].append(waypoint)

# Ensure save directory exists
os.makedirs(save_directory, exist_ok=True)

# Save the YAML file
yaml_file_path = os.path.join(save_directory, SCHEDULE)
with open(yaml_file_path, "w") as file:
yaml.dump(yaml_output, file, default_flow_style=False)