Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 0 additions & 40 deletions tests/sailship_config.json

This file was deleted.

37 changes: 33 additions & 4 deletions tests/test_sailship.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
import numpy as np
from parcels import Field, FieldSet

from virtual_ship import Location
from virtual_ship.sailship import sailship
from virtual_ship.virtual_ship_configuration import VirtualShipConfiguration
from virtual_ship.virtual_ship_configuration import (
ADCPConfig,
ArgoFloatConfig,
VirtualShipConfig,
)


def _make_ctd_fieldset(base_time: datetime) -> FieldSet:
Expand Down Expand Up @@ -77,13 +82,37 @@ def test_sailship() -> None:
},
)

config = VirtualShipConfiguration(
"sailship_config.json",
argo_float_config = ArgoFloatConfig(
fieldset=argo_float_fieldset,
max_depth=-2000,
drift_depth=-1000,
vertical_speed=-0.10,
cycle_days=10,
drift_days=9,
)

adcp_config = ADCPConfig(max_depth=-1000, bin_size_m=24)

config = VirtualShipConfig(
start_time=datetime.datetime.strptime(
"2022-01-01T00:00:00", "%Y-%m-%dT%H:%M:%S"
),
route_coordinates=[
Location(latitude=-23.071289, longitude=63.743631),
Location(latitude=-23.081289, longitude=63.743631),
Location(latitude=-23.191289, longitude=63.743631),
],
adcp_fieldset=adcp_fieldset,
ship_underwater_st_fieldset=ship_underwater_st_fieldset,
ctd_fieldset=ctd_fieldset,
drifter_fieldset=drifter_fieldset,
argo_float_fieldset=argo_float_fieldset,
argo_float_deploy_locations=[
Location(latitude=-23.081289, longitude=63.743631)
],
drifter_deploy_locations=[Location(latitude=-23.081289, longitude=63.743631)],
ctd_deploy_locations=[Location(latitude=-23.081289, longitude=63.743631)],
argo_float_config=argo_float_config,
adcp_config=adcp_config,
)

sailship(config)
8 changes: 4 additions & 4 deletions virtual_ship/costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from datetime import timedelta

from .virtual_ship_configuration import VirtualShipConfiguration
from .virtual_ship_configuration import VirtualShipConfig


def costs(config: VirtualShipConfiguration, total_time: timedelta):
def costs(config: VirtualShipConfig, total_time: timedelta):
"""
Calculate the cost of the virtual ship (in US$).

Expand All @@ -18,8 +18,8 @@ def costs(config: VirtualShipConfiguration, total_time: timedelta):
argo_deploy_cost = 15000

ship_cost = ship_cost_per_day / 24 * total_time.total_seconds() // 3600
argo_cost = len(config.argo_deploylocations) * argo_deploy_cost
drifter_cost = len(config.drifter_deploylocations) * drifter_deploy_cost
argo_cost = len(config.argo_float_deploy_locations) * argo_deploy_cost
drifter_cost = len(config.drifter_deploy_locations) * drifter_deploy_cost

cost = ship_cost + argo_cost + drifter_cost
return cost
157 changes: 63 additions & 94 deletions virtual_ship/sailship.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"""sailship function."""

import datetime
import os
from datetime import timedelta

import numpy as np
import pyproj
from shapely.geometry import Point, Polygon

from .costs import costs
from .instruments.adcp import simulate_adcp
Expand All @@ -17,49 +15,34 @@
from .location import Location
from .postprocess import postprocess
from .spacetime import Spacetime
from .virtual_ship_configuration import VirtualShipConfiguration
from .virtual_ship_configuration import VirtualShipConfig


def sailship(config: VirtualShipConfiguration):
def sailship(config: VirtualShipConfig):
"""
Use parcels to simulate the ship, take ctd_instruments and measure ADCP and underwaydata.

:param config: The cruise configuration.
"""
# TODO this will be in the config later, but for now we don't change the config structure
# from here -----
argo_locations_list = [
Location(latitude=argo[1], longitude=argo[0])
for argo in config.argo_deploylocations
]
argo_locations = set(argo_locations_list)
if len(argo_locations) != len(argo_locations_list):
config.verify()

# combine identical instrument deploy location
argo_locations = set(config.argo_float_deploy_locations)
if len(argo_locations) != len(config.argo_float_deploy_locations):
print(
"WARN: Some argo float deployment locations are identical and have been combined."
)

drifter_locations_list = [
Location(latitude=drifter[1], longitude=drifter[0])
for drifter in config.drifter_deploylocations
]
drifter_locations = set(drifter_locations_list)
if len(drifter_locations) != len(drifter_locations_list):
drifter_locations = set(config.drifter_deploy_locations)
if len(drifter_locations) != len(config.drifter_deploy_locations):
print(
"WARN: Some drifter deployment locations are identical and have been combined."
)

ctd_locations_list = [
Location(latitude=ctd[1], longitude=ctd[0]) for ctd in config.CTD_locations
]
ctd_locations = set(ctd_locations_list)
if len(ctd_locations) != len(ctd_locations_list):
ctd_locations = set(config.ctd_deploy_locations)
if len(drifter_locations) != len(config.ctd_deploy_locations):
print("WARN: Some CTD locations are identical and have been combined.")

start_time = datetime.datetime.strptime(
config.requested_ship_time["start"], "%Y-%m-%dT%H:%M:%S"
)
# until here ----

# get discrete points along the ships route were sampling and deployments will be performed
route_dt = timedelta(minutes=5)
route_points = shiproute(config=config, dt=route_dt)
Expand Down Expand Up @@ -139,12 +122,12 @@ def sailship(config: VirtualShipConfiguration):
spacetime=Spacetime(
location=route_point, time=time_past.total_seconds()
),
min_depth=-config.argo_float_fieldset.U.depth[0],
max_depth=config.argo_characteristics["maxdepth"],
drift_depth=config.argo_characteristics["driftdepth"],
vertical_speed=config.argo_characteristics["vertical_speed"],
cycle_days=config.argo_characteristics["cycle_days"],
drift_days=config.argo_characteristics["drift_days"],
min_depth=-config.argo_float_config.fieldset.U.depth[0],
max_depth=config.argo_float_config.max_depth,
drift_depth=config.argo_float_config.drift_depth,
vertical_speed=config.argo_float_config.vertical_speed,
cycle_days=config.argo_float_config.cycle_days,
drift_days=config.argo_float_config.drift_days,
)
)
argo_locations_visited = argo_locations_visited.union(argos_here)
Expand All @@ -168,7 +151,7 @@ def sailship(config: VirtualShipConfiguration):
CTD(
spacetime=Spacetime(
location=route_point,
time=start_time + time_past,
time=config.start_time + time_past,
),
min_depth=config.ctd_fieldset.U.depth[0],
max_depth=config.ctd_fieldset.U.depth[-1],
Expand Down Expand Up @@ -212,10 +195,9 @@ def sailship(config: VirtualShipConfiguration):
simulate_adcp(
fieldset=config.adcp_fieldset,
out_path=os.path.join("results", "adcp.zarr"),
max_depth=config.ADCP_settings["max_depth"],
max_depth=config.adcp_config.max_depth,
min_depth=-5,
num_bins=(-5 - config.ADCP_settings["max_depth"])
// config.ADCP_settings["bin_size_m"],
num_bins=(-5 - config.adcp_config.max_depth) // config.adcp_config.bin_size_m,
sample_points=adcps,
)

Expand All @@ -241,7 +223,7 @@ def sailship(config: VirtualShipConfiguration):
simulate_argo_floats(
out_path=os.path.join("results", "argo_floats.zarr"),
argo_floats=argo_floats,
fieldset=config.argo_float_fieldset,
fieldset=config.argo_float_config.fieldset,
outputdt=timedelta(minutes=5),
endtime=None,
)
Expand All @@ -256,65 +238,52 @@ def sailship(config: VirtualShipConfiguration):
print(f"This cruise took {time_past} and would have cost {cost:,.0f} euros.")


def shiproute(config: VirtualShipConfiguration, dt: timedelta) -> list[Location]:
def shiproute(config: VirtualShipConfig, dt: timedelta) -> list[Location]:
"""
Take in route coordinates and return lat and lon points within region of interest to sample.

:param config: The cruise configuration.
:param dt: Sailing time between each discrete route point.
:returns: lat and lon points within region of interest to sample.
"""
# Initialize lists to store intermediate points
lons = []
lats = []

# Loop over station coordinates and calculate intermediate points along great circle path
for i in range(len(config.route_coordinates) - 1):
startlong = config.route_coordinates[i][0]
startlat = config.route_coordinates[i][1]
endlong = config.route_coordinates[i + 1][0]
endlat = config.route_coordinates[i + 1][1]

# calculate line string along path with segments every 5 min for ADCP measurements
# current cruise speed is 10knots = 5.14 m/s * 60*5 = 1542 m every 5 min
# Realistic time between measurements is 2 min on Pelagia according to Floran
cruise_speed = 5.14
geod = pyproj.Geod(ellps="WGS84")
azimuth1, azimuth2, distance = geod.inv(startlong, startlat, endlong, endlat)
if distance > (cruise_speed * dt.total_seconds()):
r = geod.inv_intermediate(
startlong,
startlat,
endlong,
endlat,
del_s=1545,
initial_idx=0,
return_back_azimuth=False,
)
lons = np.append(lons, r.lons) # stored as a list of arrays
lats = np.append(lats, r.lats)
else:
lons = np.append(lons, endlong)
lats = np.append(lats, endlat)

# initial_idx will add begin point to each list (but not end point to avoid doubling) so add final endpoint manually
lons = np.append(np.hstack(lons), endlong)
lats = np.append(np.hstack(lats), endlat)

# check if input sample locations are within data availability area, only save if so
north = config.region_of_interest["North"]
east = config.region_of_interest["East"]
south = config.region_of_interest["South"]
west = config.region_of_interest["West"]
poly = Polygon([(west, north), (west, south), (east, south), (east, north)])
sample_lons = []
sample_lats = []
for i in range(len(lons)):
if poly.contains(Point(lons[i], lats[i])):
sample_lons.append(lons[i])
sample_lats.append(lats[i])
points = [
Location(latitude=lat, longitude=lon)
for lat, lon in zip(sample_lats, sample_lons, strict=True)
]
return points
CRUISE_SPEED = 5.14

# discrete points the ship will pass
sample_points: list[Location] = []

# projection used to get discrete locations
geod = pyproj.Geod(ellps="WGS84")

# loop over station coordinates and calculate intermediate points along great circle path
for startloc, endloc in zip(config.route_coordinates, config.route_coordinates[1:]):
# iterate over each coordinate and the next coordinate
# last coordinate has no next coordinate and is skipped

# get locations between start and end, seperate by 5 minutes of cruising
# excludes final point, but this is added explicitly after this loop
int_points = geod.inv_intermediate(
startloc.lon,
startloc.lat,
endloc.lon,
endloc.lat,
del_s=CRUISE_SPEED * dt.total_seconds(),
initial_idx=0,
return_back_azimuth=False,
)

sample_points.extend(
[
Location(latitude=lat, longitude=lon)
for lat, lon in zip(int_points.lats, int_points.lons, strict=True)
]
)

# explitly include final point which is not added by the previous loop
sample_points.append(
Location(
latitude=config.route_coordinates[-1].lat,
longitude=config.route_coordinates[-1].lon,
)
)

return sample_points
Loading