@@ -116,7 +116,7 @@ def __init__(self, projection: pyproj.Geod, expedition: Expedition) -> None:
116116 self ._next_ship_underwater_st_time = self ._time
117117
118118 def simulate (self ) -> ScheduleOk | ScheduleProblem :
119- # TODO: instrument config mapping (as introduced in #269) should be helpful for refactoring here...
119+ # TODO: instrument config mapping (as introduced in #269) should be helpful for refactoring here (i.e. #236) ...
120120
121121 for wp_i , waypoint in enumerate (self ._expedition .schedule .waypoints ):
122122 # sail towards waypoint
@@ -140,100 +140,116 @@ def simulate(self) -> ScheduleOk | ScheduleProblem:
140140
141141 # wait while measurements are being done
142142 self ._progress_time_stationary (time_passed )
143+
143144 return ScheduleOk (self ._time , self ._measurements_to_simulate )
144145
145146 def _progress_time_traveling_towards (self , location : Location ) -> None :
147+ """Travel from current location/waypoint to next waypoint, also mark locations and times for underway instrument measurements."""
146148 time_to_reach , azimuth1 , ship_speed_meter_per_second = _calc_sail_time (
147149 self ._location ,
148150 location ,
149151 self ._expedition .ship_config .ship_speed_knots ,
150152 self ._projection ,
151153 )
152154 end_time = self ._time + time_to_reach
155+ distance_to_move = ship_speed_meter_per_second * time_to_reach .total_seconds ()
156+
153157 # note all ADCP measurements
154158 if self ._expedition .instruments_config .adcp_config is not None :
155- location = self ._location
156- time = self ._time
157- while self ._next_adcp_time <= end_time :
158- time_to_sail = self ._next_adcp_time - time
159- distance_to_move = (
160- ship_speed_meter_per_second * time_to_sail .total_seconds ()
161- )
162- geodfwd : tuple [float , float , float ] = self ._projection .fwd (
163- lons = location .lon ,
164- lats = location .lat ,
165- az = azimuth1 ,
166- dist = distance_to_move ,
167- )
168- location = Location (latitude = geodfwd [1 ], longitude = geodfwd [0 ])
169- time = time + time_to_sail
170-
159+ adcp_times , adcp_lons , adcp_lats = self ._get_underway_measurements (
160+ self ._expedition .instruments_config .adcp_config ,
161+ azimuth1 ,
162+ distance_to_move ,
163+ time_to_reach ,
164+ )
165+
166+ for time , lon , lat in zip (adcp_times , adcp_lons , adcp_lats , strict = False ):
167+ location = Location (latitude = lat , longitude = lon )
171168 self ._measurements_to_simulate .adcps .append (
172169 Spacetime (location = location , time = time )
173170 )
174171
175- self ._next_adcp_time = (
176- self ._next_adcp_time
177- + self ._expedition .instruments_config .adcp_config .period
178- )
179-
180172 # note all ship underwater ST measurements
181173 if self ._expedition .instruments_config .ship_underwater_st_config is not None :
182- location = self ._location
183- time = self ._time
184- while self ._next_ship_underwater_st_time <= end_time :
185- time_to_sail = self ._next_ship_underwater_st_time - time
186- distance_to_move = (
187- ship_speed_meter_per_second * time_to_sail .total_seconds ()
188- )
189- geodfwd : tuple [float , float , float ] = self ._projection .fwd (
190- lons = location .lon ,
191- lats = location .lat ,
192- az = azimuth1 ,
193- dist = distance_to_move ,
194- )
195- location = Location (latitude = geodfwd [1 ], longitude = geodfwd [0 ])
196- time = time + time_to_sail
197-
174+ st_times , st_lons , st_lats = self ._get_underway_measurements (
175+ self ._expedition .instruments_config .ship_underwater_st_config ,
176+ azimuth1 ,
177+ distance_to_move ,
178+ time_to_reach ,
179+ )
180+
181+ for time , lon , lat in zip (st_times , st_lons , st_lats , strict = False ):
182+ location = Location (latitude = lat , longitude = lon )
198183 self ._measurements_to_simulate .ship_underwater_sts .append (
199184 Spacetime (location = location , time = time )
200185 )
201186
202- self ._next_ship_underwater_st_time = (
203- self ._next_ship_underwater_st_time
204- + self ._expedition .instruments_config .ship_underwater_st_config .period
205- )
206-
207187 self ._time = end_time
208188 self ._location = location
209189
190+ def _get_underway_measurements (
191+ self ,
192+ underway_instrument_config ,
193+ azimuth : float ,
194+ distance_to_move : float ,
195+ time_to_reach : timedelta ,
196+ ):
197+ """Get the times and locations of measurements between current location/waypoint and the next waypoint, for underway instruments."""
198+ period = underway_instrument_config .period
199+ npts = (time_to_reach .total_seconds () / period .total_seconds ()) + 1
200+ times = [self ._time + i * period for i in range (1 , int (npts ) + 1 )]
201+
202+ geodfwd = self ._projection .fwd_intermediate (
203+ lon1 = self ._location .lon ,
204+ lat1 = self ._location .lat ,
205+ azi1 = azimuth ,
206+ npts = npts ,
207+ del_s = distance_to_move / npts ,
208+ return_back_azimuth = False ,
209+ )
210+
211+ return times , geodfwd .lons , geodfwd .lats
212+
210213 def _progress_time_stationary (self , time_passed : timedelta ) -> None :
214+ """Make ship stay at waypoint whilst instruments are deployed, also set the underway instrument measurements that are taken during this time whilst stationary."""
211215 end_time = self ._time + time_passed
212216
213- # note all ADCP measurements
217+ # note all ADCP measurements (stationary at wp)
214218 if self ._expedition .instruments_config .adcp_config is not None :
215- while self ._next_adcp_time <= end_time :
219+ adcp_times = self ._get_underway_stationary_times (
220+ self ._expedition .instruments_config .adcp_config , time_passed
221+ )
222+
223+ for time in adcp_times :
216224 self ._measurements_to_simulate .adcps .append (
217- Spacetime (self ._location , self ._next_adcp_time )
218- )
219- self ._next_adcp_time = (
220- self ._next_adcp_time
221- + self ._expedition .instruments_config .adcp_config .period
225+ Spacetime (location = self ._location , time = time )
222226 )
223227
224- # note all ship underwater ST measurements
228+ # note all underwater ST measurements (stationary at wp)
225229 if self ._expedition .instruments_config .ship_underwater_st_config is not None :
226- while self ._next_ship_underwater_st_time <= end_time :
230+ st_times = self ._get_underway_stationary_times (
231+ self ._expedition .instruments_config .ship_underwater_st_config ,
232+ time_passed ,
233+ )
234+ for time in st_times :
227235 self ._measurements_to_simulate .ship_underwater_sts .append (
228- Spacetime (self ._location , self ._next_ship_underwater_st_time )
229- )
230- self ._next_ship_underwater_st_time = (
231- self ._next_ship_underwater_st_time
232- + self ._expedition .instruments_config .ship_underwater_st_config .period
236+ Spacetime (location = self ._location , time = time )
233237 )
234238
235239 self ._time = end_time
236240
241+ def _get_underway_stationary_times (
242+ self , underway_instrument_config , time_passed : timedelta
243+ ):
244+ npts = (
245+ time_passed .total_seconds ()
246+ / underway_instrument_config .period .total_seconds ()
247+ ) + 1
248+ return [
249+ self ._time + i * underway_instrument_config .period
250+ for i in range (1 , int (npts ) + 1 )
251+ ]
252+
237253 def _make_measurements (self , waypoint : Waypoint ) -> timedelta :
238254 # if there are no instruments, there is no time cost
239255 if waypoint .instrument is None :
@@ -268,6 +284,12 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta:
268284 drift_days = self ._expedition .instruments_config .argo_float_config .drift_days ,
269285 )
270286 )
287+ # TODO: would be good to avoid having to twice make sure that stationkeeping time is factored in; i.e. in schedule validity checks and here (and for CTDs and Drifters)
288+ # TODO: makes it easy to forget to update both...
289+ # TODO: this is likely to fall under refactoring simulate_schedule.py (i.e. #236)
290+ time_costs .append (
291+ self ._expedition .instruments_config .argo_float_config .stationkeeping_time
292+ )
271293
272294 elif instrument is InstrumentType .CTD :
273295 self ._measurements_to_simulate .ctds .append (
@@ -302,6 +324,10 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta:
302324 lifetime = self ._expedition .instruments_config .drifter_config .lifetime ,
303325 )
304326 )
327+ time_costs .append (
328+ self ._expedition .instruments_config .drifter_config .stationkeeping_time
329+ )
330+
305331 elif instrument is InstrumentType .XBT :
306332 self ._measurements_to_simulate .xbts .append (
307333 XBT (
@@ -315,5 +341,7 @@ def _make_measurements(self, waypoint: Waypoint) -> timedelta:
315341 else :
316342 raise NotImplementedError ("Instrument type not supported." )
317343
318- # measurements are done in parallel, so return time of longest one
344+ # measurements are done simultaneously onboard, so return time of longest one
345+ # TODO: docs suggest that add individual instrument stationkeeping times are cumulative, which is at odds with measurements being done simultaneously onboard here
346+ # TODO: update one or the other?
319347 return max (time_costs )
0 commit comments