Skip to content

Commit ee67574

Browse files
authored
Merge pull request #19 from KonstantinChri/dev
Merge dev for new release
2 parents 9be74c5 + d23d13d commit ee67574

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+4477
-16811
lines changed

dnora/aux.py dnora/aux_funcs.py

-102
Original file line numberDiff line numberDiff line change
@@ -180,108 +180,6 @@ def month_list(start_time, end_time):
180180
months = pd.date_range(start=start_time[:7], end=end_time[:7], freq='MS')
181181
return months
182182

183-
def add_extension(filename: str, extension: str):
184-
"""Adds a file extension to the file name.
185-
186-
If the file already has an extension, then no extension is added. An
187-
extension is defined as a . in the last five characters of the string.
188-
"""
189-
190-
if ('.' in filename[-5:]) or (not extension):
191-
return filename
192-
else:
193-
if not extension[0] == '.':
194-
extension = f".{extension}"
195-
return f"{filename}{extension}"
196-
197-
198-
def add_prefix(filename: str, prefix: str) -> str:
199-
"""Adds a prefix to a filename, e.g. FileName.txt -> new_FileName.txt"""
200-
if (not prefix == '') and (not prefix[-1] == '_'):
201-
return f"{prefix}_{filename}"
202-
else:
203-
return f"{prefix}{filename}"
204-
205-
def add_suffix(filename: str, suffix: str) -> str:
206-
"""Adds a suffix to a filename, e.g. FileName.txt -> FileName_new.txt"""
207-
if (not suffix == '') and (not suffix[0] == '_'):
208-
suffix = f"_{suffix}"
209-
210-
filename_list = filename.split('.')
211-
212-
if len(filename_list) == 1:
213-
return filename+suffix
214-
else:
215-
return '.'.join(filename_list[0:-1]) + suffix + '.' + filename_list[-1]
216-
217-
218-
def add_folder_to_filename(filename: str, folder: str) -> str:
219-
"""Adds a folder to filename to create a path:
220-
221-
e.g. FileName.txt, Folder -> Folder/FileName.txt
222-
"""
223-
224-
if (not folder == '') and (not folder[-1] == '/'):
225-
return f"{folder}/{filename}"
226-
else:
227-
return f"{folder}{filename}"
228-
229-
def create_filename_time(filestring: str, times: List, datestring: str='%Y%m%d%H%M') -> str:
230-
"""Substitutes the strings #T0, #T1, #T2... etc. in filestring with time
231-
stamps from a list of times, using the format in datestring.
232-
233-
e.g. #T0_file.txt, ['2020-05-04 18:00'], %Y%m%d%H%M -> 202005041800_file.txt
234-
"""
235-
236-
ct = 0
237-
for t in times:
238-
filestring = re.sub(f"#T{ct}", pd.Timestamp(t).strftime(datestring), filestring)
239-
ct = ct + 1
240-
241-
return filestring
242-
243-
def create_filename_lonlat(filestring: str, lon: float, lat: float) -> str:
244-
"""Substitutes the strings #Lon, #Lat in filestring with values of lon and
245-
lat.
246-
247-
e.g. #Lon_#Lat_file.txt, 8.0, 60.05 -> 08.0000000_60.05000000_file.txt
248-
"""
249-
250-
filestring = re.sub("#Lon", f"{lon:010.7f}", filestring)
251-
filestring = re.sub("#Lat", f"{lat:010.7f}", filestring)
252-
253-
return filestring
254-
255-
def create_filename_obj(filestring: str, objects: List[Union[Grid, Forcing, Boundary]]) -> str:
256-
"""Substitutes the strings #{Object} in filestring with the name given to
257-
the object.
258-
259-
e.g. #Grid_#Forcing_file.txt, [Grid(..., name="Sula"), Forcing(..., name='NORA3')]
260-
-> Sula_NORA3_file.txt
261-
"""
262-
263-
for object in objects:
264-
if object is not None:
265-
obj_str = type(object).__name__
266-
obj_name = object.name()
267-
filestring = re.sub(f"#{obj_str}", obj_name, filestring)
268-
269-
return filestring
270-
271-
def clean_filename(filename: str, list_of_placeholders: List[str]) -> str:
272-
""" Cleans out the file name from possible used placeholders, e.g. #Grid
273-
as given in the list.
274-
275-
Also removes multiple underscores '___'
276-
"""
277-
278-
for s in list_of_placeholders:
279-
filename = re.sub(s, '', filename)
280-
281-
filename = re.sub("_{2,10}", '_', filename)
282-
283-
return filename
284-
285183
def create_time_stamps(start_time: str, end_time: str, stride: int, hours_per_file: int=0, last_file: str='', lead_time: int=0) -> Tuple:
286184
"""Create time stamps to read in blocks of wind forcing from files.
287185

dnora/bnd/bnd_mod.py

+62-49
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@
2020
if TYPE_CHECKING:
2121
from .write import BoundaryWriter # Abstract class
2222

23-
# Import default values and auxiliry functions
23+
# Import default values and aux_funcsiliry functions
2424
from .. import msg
25-
from ..aux import day_list, create_filename_obj, create_filename_time, create_filename_lonlat, clean_filename, check_if_folder
26-
from ..defaults import dflt_bnd, list_of_placeholders
25+
from ..aux_funcs import day_list
2726

2827

2928
class Boundary:
@@ -42,18 +41,20 @@ def import_boundary(self, start_time: str, end_time: str, boundary_reader: Bound
4241
are determined by the point_picker.
4342
"""
4443

45-
self.start_time = copy(start_time)
46-
self.end_time = copy(end_time)
4744
self._history.append(copy(boundary_reader))
4845

4946
msg.header(boundary_reader, "Reading coordinates of spectra...")
50-
lon_all, lat_all = boundary_reader.get_coordinates(self.start_time)
47+
lon_all, lat_all = boundary_reader.get_coordinates(start_time)
5148

5249
msg.header(point_picker, "Choosing spectra...")
5350
inds = point_picker(self.grid, lon_all, lat_all)
5451

52+
if len(inds) < 1:
53+
msg.warning("PointPicker didn't find any points. Aborting import of boundary.")
54+
return
55+
5556
msg.header(boundary_reader, "Loading boundary spectra...")
56-
time, freq, dirs, spec, lon, lat, source = boundary_reader(self.start_time, end_time, inds)
57+
time, freq, dirs, spec, lon, lat, source = boundary_reader(start_time, end_time, inds)
5758

5859
self.data = self.compile_to_xr(time, freq, dirs, spec, lon, lat, source)
5960
self.mask = [True]*len(self.x())
@@ -130,71 +131,84 @@ def compile_to_xr(self, time, freq, dirs, spec, lon, lat, source):
130131
)
131132
return data
132133

133-
def slice_data(self, start_time: str='', end_time: str='', x: List[int]=None):
134-
"""Slice data in space (x) and time. Returns an xarray dataset."""
134+
def slice_data(self, start_time: str='', end_time: str='', x: List[int]=None) -> xr.Dataset:
135+
"""Slice data in space (x) and time."""
136+
if not hasattr(self, 'data'):
137+
return None
135138

136139
if x is None:
137140
x=self.x()
138-
139141
if not start_time:
140142
# This is not a string, but slicing works also with this input
141143
start_time = self.time()[0]
142-
143144
if not end_time:
144145
# This is not a string, but slicing works also with this input
145146
end_time = self.time()[-1]
146-
147147
sliced_data = self.data.sel(time=slice(start_time, end_time), x = x)
148-
149148
return sliced_data
150149

151-
def spec(self, start_time: str='', end_time: str='', x: List[int]=None):
152-
"""Slice spectra in space (x) and time. Returns an numpy array."""
153-
154-
spec = self.slice_data(start_time, end_time, x).spec.values
155-
return spec
150+
def spec(self, start_time: str='', end_time: str='', x: List[int]=None) -> np.ndarray:
151+
"""Slice spectra in space (x) and time."""
152+
spec = self.slice_data(start_time, end_time, x)
153+
if spec is None:
154+
return None
155+
return spec.spec.values
156156

157157
def time(self):
158+
if not hasattr(self, 'data'):
159+
return pd.to_datetime([])
158160
return pd.to_datetime(self.data.time.values)
159161

162+
def start_time(self) -> str:
163+
if len(self.time()) == 0:
164+
return ''
165+
return self.time()[0].strftime('%Y-%m-%d %H:%M')
166+
167+
def end_time(self) -> str:
168+
if len(self.time()) == 0:
169+
return ''
170+
return self.time()[-1].strftime('%Y-%m-%d %H:%M')
171+
160172
def dt(self) -> float:
161173
""" Returns time step of boundary spectra in hours."""
174+
if len(self.time()) == 0:
175+
return 0
162176
return self.time().to_series().diff().dt.total_seconds().values[-1]/3600
163177

164-
def freq(self):
165-
if hasattr(self, 'data'):
166-
return copy(self.data.freq.values)
167-
else:
178+
def freq(self) -> np.ndarray:
179+
if not hasattr(self, 'data'):
168180
return np.array([])
181+
return copy(self.data.freq.values)
169182

170-
def dirs(self):
171-
if hasattr(self, 'data'):
172-
return copy(self.data.dirs.values)
173-
else:
183+
def dirs(self) -> np.ndarray:
184+
if not hasattr(self, 'data'):
174185
return np.array([])
186+
return copy(self.data.dirs.values)
175187

176-
def lon(self):
177-
if hasattr(self, 'data'):
178-
return copy(self.data.lon.values)
179-
else:
188+
def lon(self) -> np.ndarray:
189+
if not hasattr(self, 'data'):
180190
return np.array([])
191+
return copy(self.data.lon.values)
181192

182-
def lat(self):
183-
if hasattr(self, 'data'):
184-
return copy(self.data.lat.values)
185-
else:
193+
def lat(self) -> np.ndarray:
194+
if not hasattr(self, 'data'):
186195
return np.array([])
196+
return copy(self.data.lat.values)
187197

188-
def x(self):
189-
if hasattr(self, 'data'):
190-
return copy(self.data.x.values)
191-
else:
198+
def x(self) -> np.ndarray:
199+
if not hasattr(self, 'data'):
192200
return np.array([])
201+
return copy(self.data.x.values)
193202

194203
def days(self):
195204
"""Determins a Pandas data range of all the days in the time span."""
196-
days = day_list(start_time = self.start_time, end_time = self.end_time)
197-
return days
205+
if len(self.time()) == 0:
206+
return []
207+
return pd.date_range(start=str(self.time()[0]).split(' ')[0],
208+
end=str(self.time()[-1]).split(' ')[0], freq='D')
209+
210+
#days = day_list(start_time = self.start_time, end_time = self.end_time)
211+
#return days
198212

199213
def size(self):
200214
return (len(self.time()), len(self.x()))
@@ -206,33 +220,32 @@ def name(self) -> str:
206220

207221
def convention(self) -> str:
208222
"""Returns the convention (WW3/Ocean/Met/Math) of the spectra"""
209-
if hasattr(self, '_convention'):
210-
return copy(self._convention)
211-
else:
223+
if not hasattr(self, '_convention'):
212224
return None
225+
return copy(self._convention)
213226

214227
def times_in_day(self, day):
215228
"""Determines time stamps of one given day."""
216229
t0 = day.strftime('%Y-%m-%d') + "T00:00:00"
217230
t1 = day.strftime('%Y-%m-%d') + "T23:59:59"
218231

219-
times = self.slice_data(start_time=t0, end_time=t1, x=[0]).time.values
220-
return times
232+
times = self.slice_data(start_time=t0, end_time=t1, x=[0])
233+
if times is None:
234+
return []
235+
236+
return times.time.values
221237

222238
def __str__(self) -> str:
223239
"""Prints status of boundary."""
224240

225241
msg.header(self, f"Status of boundary {self.name()}")
226-
msg.plain(f"Contains data ({len(self.x())} points) for {self.start_time} - {self.end_time}")
242+
msg.plain(f"Contains data ({len(self.x())} points) for {self.start_time()} - {self.end_time()}")
227243
msg.plain(f"Data covers: lon: {min(self.lon())} - {max(self.lon())}, lat: {min(self.lat())} - {max(self.lat())}")
228244
if len(self._history) > 0:
229245
msg.blank()
230246
msg.plain("Object has the following history:")
231247
for obj in self._history:
232248
msg.process(f"{obj.__class__.__bases__[0].__name__}: {type(obj).__name__}")
233-
#msg.print_line()
234-
#msg.plain("The Boundary is for the following Grid:")
235-
#print(self.grid)
236249

237250
msg.print_line()
238251

dnora/bnd/pick.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
# Import objects
55
from ..grd.grd_mod import Grid
66

7-
# Import auxiliry functions
7+
# Import aux_funcsiliry functions
88
from .. import msg
9-
from ..aux import min_distance, expand_area
9+
from ..aux_funcs import min_distance, expand_area
1010

1111
class PointPicker(ABC):
1212
"""PointPickers take in longitude and latitude values, and returns indeces
@@ -28,8 +28,11 @@ def __call__(self, grid: Grid, bnd_lon, bnd_lat):
2828
return inds
2929

3030
class NearestGridPoint(PointPicker):
31-
"""Choose the nearest grid point to each boundary point in the grid."""
32-
def __init__(self):
31+
"""Choose the nearest grid point to each boundary point in the grid.
32+
Set a maximum allowed distance using `max_dist` (in km) at instantiation time.
33+
"""
34+
def __init__(self, max_dist=None):
35+
self.max_dist = max_dist
3336
pass
3437

3538
def __call__(self, grid, bnd_lon, bnd_lat):
@@ -41,8 +44,12 @@ def __call__(self, grid, bnd_lon, bnd_lat):
4144
inds = []
4245
for n in range(len(lat)):
4346
dx, ind = min_distance(lon[n], lat[n], bnd_lon, bnd_lat)
44-
msg.plain(f"Point {n}: lat: {lat[n]:10.7f}, lon: {lon[n]:10.7f} <<< ({bnd_lat[ind]: .7f}, {bnd_lon[ind]: .7f}). Distance: {dx:.1f} km")
45-
inds.append(ind)
47+
ms = f"Point {n}: lat: {lat[n]:10.7f}, lon: {lon[n]:10.7f} <<< ({bnd_lat[ind]: .7f}, {bnd_lon[ind]: .7f}). Distance: {dx:.1f} km"
48+
if self.max_dist is None or dx <= self.max_dist:
49+
msg.plain(ms)
50+
inds.append(ind)
51+
else:
52+
msg.plain('DISCARDED, too far: '+ms)
4653

4754
inds = np.array(inds)
4855
return inds

dnora/bnd/process.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
from abc import ABC, abstractmethod
44
from typing import Tuple
55

6-
# Import auxiliry functions
6+
# Import aux_funcsiliry functions
77
from .. import msg
8-
from ..aux import interp_spec, shift_spec, flip_spec
8+
from ..aux_funcs import interp_spec, shift_spec, flip_spec
99

1010
class BoundaryProcessor(ABC):
1111
def __init__(self):

0 commit comments

Comments
 (0)