11import os
22import logging
3+ from datetime import datetime
34
45import cartopy .crs as ccrs
56import cartopy .feature as cf
@@ -28,7 +29,8 @@ class Constraint:
2829 """
2930 Main class for handling of constraints. Constraints implemented so far:
3031 LandCrossing (prohibit land crossing), WaterDepth (prohibit crossing of areas with too low water depth),
31- StayOnMap (prohibit leaving the area for which the weather data has been obtained)
32+ StayOnMap (prohibit leaving the area for which the weather data has been obtained),
33+ StayInTime (prohibit leaving the timeframe for which the weather data has been obtained)
3234 """
3335
3436 name : str
@@ -52,6 +54,9 @@ def print_constraint_message(self):
5254 def constraint_on_point (self , lat , lon , time ):
5355 pass
5456
57+ def check_crossing (self , lat_start , lon_start , lat_end , lon_end , time_start = None ):
58+ pass
59+
5560 def print_debug (self , message ):
5661 logger .debug (self .name + str (": " ) + str (message ))
5762
@@ -191,6 +196,17 @@ def get_constraints_list(constraints_string_list, **kwargs):
191196 constraints_list .add_neg_constraint (on_map )
192197 is_stay_on_map = True
193198
199+ if 'in_time' in constraints_string_list :
200+ if 'time_frame' not in kwargs :
201+ raise ValueError ('To use the in-time constraint module, you need to provide the timeframe '
202+ 'of the weather data.' )
203+ time = kwargs .get ('time_frame' )
204+ time_start = time [0 ]
205+ time_end = time [1 ]
206+ in_time = StayInTime ()
207+ in_time .set_time (time_start , time_end )
208+ constraints_list .add_neg_constraint (in_time , 'continuous' )
209+
194210 if 'seamarks' in constraints_string_list :
195211 if is_stay_on_map :
196212 seamarks = SeamarkCrossing (is_stay_on_map , map_size )
@@ -339,21 +355,53 @@ def safe_endpoint(self, lat, lon, current_time, is_constrained):
339355 is_constrained += is_constrained_temp
340356 return is_constrained
341357
342- def safe_crossing (self , lat_start , lon_start , lat_end , lon_end , current_time , is_constrained ):
343- is_constrained_discrete = is_constrained
344- is_constrained_continuous = is_constrained
345- is_constrained_discrete = self .safe_crossing_discrete (lat_start , lon_start , lat_end , lon_end , current_time ,
346- is_constrained )
347- is_constrained_continuous = self .safe_crossing_continuous (lat_start , lon_start , lat_end , lon_end ,
348- is_constrained )
358+ def safe_crossing (self , lat_start : np .ndarray , lon_start : np .ndarray , lat_end : np .ndarray , lon_end : np .ndarray ,
359+ start_times : np .ndarray , is_constrained : list [bool ]):
360+ """
361+ Check whether there is a constraint on the way from a starting point (lat_start, lon_start) to the destination
362+ (lat_end, lon_end).
363+
364+ :param lat_start: Latitude(s) of start point(s) of section(s) to check
365+ :type lat_start: numpy.ndarray or float
366+ :param lon_start: Longitude(s) of start point(s) of section(s) to check
367+ :type lon_start: numpy.ndarray or float
368+ :param lat_end: Latitude(s) of end point(s) of section(s) to check
369+ :type lat_end: numpy.ndarray or float
370+ :param lon_end: Longitude(s) of end point(s) of section(s) to check
371+ :type lon_end: numpy.ndarray or float
372+ :param current_time: Time(s) at the start point(s) of the section(s); may be None
373+ :type current_time: numpy.ndarray or None
374+ :param is_constrained: Booleans for every section stating if it is already constrained
375+ :type is_constrained: list[bool]
376+ :return: is_constrained
377+ :rtype: list[bool]
378+ """
379+
380+ is_constrained_discrete = self .safe_crossing_discrete (
381+ lat_start = lat_start ,
382+ lon_start = lon_start ,
383+ lat_end = lat_end ,
384+ lon_end = lon_end ,
385+ current_time = start_times ,
386+ is_constrained = is_constrained
387+ )
388+ is_constrained_continuous = self .safe_crossing_continuous (
389+ lat_start = lat_start ,
390+ lon_start = lon_start ,
391+ lat_end = lat_end ,
392+ lon_end = lon_end ,
393+ time_start = start_times ,
394+ is_constrained = is_constrained
395+ )
349396
350397 # TO BE UPDATED
351398 is_constrained_array = (np .array (is_constrained ) | np .array (is_constrained_discrete )
352399 | np .array (is_constrained_continuous ))
353400 is_constrained = is_constrained_array .tolist ()
354401 return is_constrained
355402
356- def safe_crossing_continuous (self , lat_start , lon_start , lat_end , lon_end , is_constrained ):
403+ def safe_crossing_continuous (self , lat_start : np .ndarray , lon_start : np .ndarray , lat_end : np .ndarray ,
404+ lon_end : np .ndarray , time_start : np .ndarray , is_constrained : list [bool ]):
357405 """TODO: add description
358406 _summary_
359407
@@ -367,6 +415,8 @@ def safe_crossing_continuous(self, lat_start, lon_start, lat_end, lon_end, is_co
367415 :type lon_end: numpy.ndarray or float
368416 :param is_constrained: List of booleans for every constraint stating if the section is constraint by it
369417 :type is_constrained: list[bool]
418+ :param time_start: Time(s) at start point(s) of section(s) to check
419+ :type time_start: numpy.ndarray or None
370420 :return: is_constrained.tolist()
371421 :rtype: list[bool]
372422 """
@@ -377,12 +427,19 @@ def safe_crossing_continuous(self, lat_start, lon_start, lat_end, lon_end, is_co
377427 # logger.debug('Length of latitudes: ' + str(len(lat_start)))
378428
379429 for constr in self .negative_constraints_continuous :
380- is_constrained_temp = constr .check_crossing (lat_start , lon_start , lat_end , lon_end )
430+ is_constrained_temp = constr .check_crossing (
431+ lat_start = lat_start ,
432+ lon_start = lon_start ,
433+ lat_end = lat_end ,
434+ lon_end = lon_end ,
435+ time_start = time_start
436+ )
381437 is_constrained = np .array (is_constrained ) | np .array (is_constrained_temp )
382438
383439 return is_constrained .tolist ()
384440
385- def safe_crossing_discrete (self , lat_start , lon_start , lat_end , lon_end , current_time , is_constrained ):
441+ def safe_crossing_discrete (self , lat_start : np .ndarray , lon_start : np .ndarray , lat_end : np .ndarray ,
442+ lon_end : np .ndarray , current_time : np .ndarray , is_constrained : list [bool ]):
386443 """
387444 Check whether there is a constraint on the way from a starting point (lat_start, lon_start) to the destination
388445 (lat_end, lon_end).
@@ -511,7 +568,7 @@ def load_data_from_file(self, courses_path):
511568 routeData .close ()
512569 return status , lats , lons
513570
514- def check_crossing (self , lat_start = None , lon_start = None , lat_end = None , lon_end = None , current_time = None ):
571+ def check_crossing (self , lat_start = None , lon_start = None , lat_end = None , lon_end = None , time_start = None ):
515572 status , lats_netcdf , lon_netcdf = self .load_data_from_file (self .courses_path )
516573 # Double-check coordinates
517574 assert (lats_netcdf == lat_start ).all ()
@@ -799,6 +856,56 @@ def set_map(self, lat1, lon1, lat2, lon2):
799856 self .lon2 = lon2
800857
801858
859+ class StayInTime (NegativeContraint ):
860+ """
861+ Constraint such that the boat can't leave the timeframe that has weather data available
862+ """
863+
864+ time_start : datetime
865+ time_end : datetime
866+
867+ def __init__ (self ):
868+ NegativeContraint .__init__ (self , "StayInTime" )
869+ self .message += "leaving wheather time frame!" # self.resource_type = 0
870+
871+ def check_crossing (self , lat_start : np .ndarray , lon_start : np .ndarray , lat_end : np .ndarray , lon_end : np .ndarray ,
872+ time_start : np .ndarray = None ):
873+ """
874+ Check whether array of start times from route waypoints is covered by weather data.
875+
876+ time_start can be None. In this case, all waypoints are not constrained.
877+ :param lat_start: Start latitude
878+ :type lat_start: np.ndarray
879+ :param lon_start: Start longitude
880+ :type lon_start: np.ndarray
881+ :param lat_end: End latitude
882+ :type lat_end: np.ndarray
883+ :param lon_end: End longitude
884+ :type lon_end: np.ndarray
885+ :param time_start: Start time
886+ :type time_start: np.ndarray or None
887+ :return is_out_of_time: list constraints
888+ :rtype is_out_of_time: list[bool]
889+
890+ """
891+
892+ if time_start is None :
893+ return np .full (np .shape (lat_start ), False )
894+ is_out_of_time = ((time_start > self .time_end ) + (time_start < self .time_start ))
895+ # if is_out_of_time.any():
896+ # print('checking time: ' + str(time_start))
897+ # print('is_out_of_time: ' + str(is_out_of_time))
898+ # print(f'start_time={self.time_start}, end_time={self.time_end}')
899+ return is_out_of_time
900+
901+ def print_info (self ):
902+ logger .info (form .get_log_step ("stay in wheather time frame" , 1 ))
903+
904+ def set_time (self , time_start , time_end ):
905+ self .time_start = time_start
906+ self .time_end = time_end
907+
908+
802909class ContinuousCheck (NegativeContraint ):
803910 """
804911 Contains various functions to test data connection, gathering and use for obtaining spatial relations
@@ -876,7 +983,7 @@ def print_info(self):
876983 def connect_database (self ):
877984 pass
878985
879- def check_crossing (self , lat_start , lon_start , lat_end , lon_end , time = None ):
986+ def check_crossing (self , lat_start , lon_start , lat_end , lon_end , time_start = None ):
880987 result_length = len (lat_start )
881988 res = []
882989
@@ -1024,7 +1131,7 @@ def concat_nodes_ways(self, db_engine, query):
10241131 else :
10251132 return "false query passed"
10261133
1027- def check_crossing (self , lat_start , lon_start , lat_end , lon_end ):
1134+ def check_crossing (self , lat_start , lon_start , lat_end , lon_end , time_start = None ):
10281135 """
10291136 Check if certain route crosses specified seamark objects
10301137
@@ -1106,7 +1213,7 @@ def query_land_polygons(self, db_engine, query):
11061213 gdf = gdf [gdf ["geom" ] != None ]
11071214 return gdf
11081215
1109- def check_crossing (self , lat_start , lon_start , lat_end , lon_end ):
1216+ def check_crossing (self , lat_start , lon_start , lat_end , lon_end , time_start = None ):
11101217 """
11111218 Check if certain route crosses specified seamark objects
11121219
0 commit comments