1616# Tanker: implements interface to mariPower package which is used for power estimation.
1717
1818class Boat :
19+ """
20+ Base class representing a vessel used by the Weather Routing Tool.
21+
22+ - Sub-classes (for example `MariPowerTanker`, `DirectPowerBoat` or `SailingBoat`) implement
23+ vessel-specific power and fuel estimation methods. This class provides shared
24+ functionality used by all boat types such as keeping basic ship parameters
25+ (draught, under-keel clearance), handling weather data access and helper
26+ methods for extracting weather fields from a NetCDF file.
27+ - Sub-classes should implement vessel-specific behaviour by overriding
28+ ``get_ship_parameters``. Individual methods are documented on the method
29+ itself.
30+
31+ :param weather_path: Path to the NetCDF file that contains
32+ weather/oceanographic variables.
33+ :type weather_path: str
34+ :param under_keel_clearance: Minimum clearance below the hull as defined in
35+ the ship configuration.
36+ :type under_keel_clearance: astropy.units.Quantity
37+ :param draught_aft: Aft draught read from the ship configuration.
38+ :type draught_aft: astropy.units.Quantity
39+ :param draught_fore: Fore draught read from the ship configuration.
40+ :type draught_fore: astropy.units.Quantity
41+ :param time_min_max: Cached min/max time values available in the weather file.
42+ :type time_min_max: list
43+ :param lat_min_max: Cached min/max latitude values available in the weather file.
44+ :type lat_min_max: list
45+ :param lon_min_max: Cached min/max longitude values available in the weather file.
46+ :type lon_min_max: list
47+ """
1948 weather_path : str # path to netCDF containing weather data
2049
2150 def __init__ (self , ship_config : ShipConfig ):
@@ -28,16 +57,70 @@ def __init__(self, ship_config: ShipConfig):
2857 self .lon_min_max = [None , None ]
2958
3059 def get_required_water_depth (self ):
60+ """Return required water depth in metres.
61+
62+ The required water depth is computed as the maximum of fore and aft
63+ draught plus the under-keel clearance.
64+
65+ :return: required water depth in metres (float)
66+ :rtype: float
67+ """
3168 needs_water_depth = max (self .draught_aft , self .draught_fore ) + self .under_keel_clearance
3269 return needs_water_depth .value
3370
3471 def get_ship_parameters (self , courses , lats , lons , time , speed , unique_coords = False ):
35- pass
72+ """Return `ShipParams` for the requested positions and times.
73+
74+ Sub-classes must override this method to provide vessel-specific power,
75+ RPM and fuel estimations.
76+
77+ :param courses: course angles for each routing segment (radians or degrees
78+ depending on caller conventions).
79+ :type courses: array-like
80+ :param lats: latitudes of start points for each routing segment.
81+ :type lats: array-like
82+ :param lons: longitudes of start points for each routing segment.
83+ :type lons: array-like
84+ :param time: start times for each routing segment (array of datetimes).
85+ :type time: array-like
86+ :param speed: speeds to evaluate at (one per segment).
87+ :type speed: array-like
88+ :param unique_coords: if True, the implementation may assume coordinates
89+ are unique and optimise lookups accordingly.
90+ :type unique_coords: bool
91+ :return: a `ShipParams` instance populated for each requested segment.
92+ :rtype: ShipParams
93+ """
94+ raise NotImplementedError ()
3695
3796 def print_init (self ):
38- pass
97+ """Log basic boat initialisation information.
98+
99+ Implementations should use the project's logging/formatting helpers to
100+ print relevant initialisation values (e.g. fuel rate or geometry).
101+ """
102+ return None
39103
40104 def evaluate_weather (self , ship_params , lats , lons , time ):
105+ """Populate weather-related fields of a `ShipParams` object.
106+
107+ This method reads the NetCDF file referenced by ``self.weather_path`` and
108+ interpolates (nearest) the required variables to the provided
109+ coordinates and times. The populated fields on ``ship_params`` include
110+ wave height, wave period, wave direction, wind and current components,
111+ pressure, temperatures and salinity.
112+
113+ :param ship_params: `ShipParams` instance to populate.
114+ :type ship_params: ShipParams
115+ :param lats: latitudes for the lookups.
116+ :type lats: array-like
117+ :param lons: longitudes for the lookups.
118+ :type lons: array-like
119+ :param time: times for the lookups (array-like, datetime-like objects).
120+ :type time: array-like
121+ :return: the same ``ship_params`` instance populated with weather fields.
122+ :rtype: ShipParams
123+ """
41124 weather_data = xr .open_dataset (self .weather_path )
42125 n_coords = len (lats )
43126
@@ -108,6 +191,22 @@ def evaluate_weather(self, ship_params, lats, lons, time):
108191 return ship_params
109192
110193 def check_value_in_range (self , lats , lons , time ):
194+ """Raise `ValueError` if any requested coordinate or time is outside
195+ the cached weather data ranges.
196+
197+ The method uses the cached ``lat_min_max``, ``lon_min_max`` and
198+ ``time_min_max`` values populated when ``evaluate_weather`` was first
199+ called. If a value is out of range, the corresponding available range is
200+ printed and a ``ValueError`` is raised.
201+
202+ :param lats: latitudes to check.
203+ :type lats: array-like
204+ :param lons: longitudes to check.
205+ :type lons: array-like
206+ :param time: times to check.
207+ :type time: array-like
208+ :raises ValueError: if any coordinate/time lies outside the available data.
209+ """
111210 if (lats > self .lat_min_max [1 ] or lats < self .lat_min_max [0 ]).any ():
112211 weather_data = xr .open_dataset (self .weather_path )
113212 print (f'lat: { weather_data ["latitude" ].min ().to_numpy ()} - { weather_data ["latitude" ].max ().to_numpy ()} ' )
@@ -122,6 +221,27 @@ def check_value_in_range(self, lats, lons, time):
122221 raise ValueError (f'Time { time } is out of weather range.' )
123222
124223 def approx_weather (self , var , lats , lons , time , height = None , depth = None ):
224+ """Select nearest values from an xarray Variable and return as NumPy.
225+
226+ Uses ``xarray.DataArray.sel`` with ``method='nearest'`` and fills
227+ missing values with zero. Optionally selects by ``height_above_ground``
228+ or ``depth`` where available.
229+
230+ :param var: xarray variable (DataArray) to sample from.
231+ :type var: xarray.DataArray
232+ :param lats: latitude values for the lookup.
233+ :type lats: array-like
234+ :param lons: longitude values for the lookup.
235+ :type lons: array-like
236+ :param time: time values for the lookup.
237+ :type time: array-like
238+ :param height: optional height above ground to select (e.g. wind levels).
239+ :type height: float or None
240+ :param depth: optional depth to select (e.g. ocean fields).
241+ :type depth: float or None
242+ :return: sampled values as a NumPy array with NaNs replaced by 0.
243+ :rtype: numpy.ndarray
244+ """
125245
126246 # self.check_value_in_range(lats, lons, time)
127247
@@ -135,7 +255,12 @@ def approx_weather(self, var, lats, lons, time, height=None, depth=None):
135255 return ship_var
136256
137257 def load_data (self ):
138- pass
258+ """Optional hook to (re)load vessel-specific data.
259+
260+ Child classes may implement this to load auxiliary data (e.g. lookup
261+ tables) required for power/fuel computations.
262+ """
263+ return None
139264
140265 def check_data_meaningful (self ):
141266 """
0 commit comments