Skip to content

Commit

Permalink
Merge pull request #1480 from knutfrode/dev
Browse files Browse the repository at this point in the history
Fix and unittest for dateline/0-meridian problem for structured reade…
  • Loading branch information
knutfrode authored Jan 16, 2025
2 parents 4faa301 + 99e2dc9 commit 9a7f3cb
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 29 deletions.
2 changes: 2 additions & 0 deletions opendrift/models/basemodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2441,6 +2441,8 @@ def set_up_map(self,
fig = plt.figure(figsize=(figsize, figsize * aspect_ratio))

ax = fig.add_subplot(111, projection=self.crs_plot)
if lonmin == -180 and lonmax == 180:
lonmax -= .1 # To avoid problem with Cartopy
ax.set_extent([lonmin, lonmax, latmin, latmax], crs=self.crs_lonlat)

gl = ax.gridlines(self.crs_lonlat, draw_labels=True, xlocs=xlocs, ylocs=ylocs)
Expand Down
41 changes: 27 additions & 14 deletions opendrift/readers/interpolation/structured.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,22 @@ def __init__(self, data_dict,
del self.data_dict['z']
except:
self.z = None
self.wrap_x = wrap_x # For global readers where longitude wraps at 360
if wrap_x is True:
if self.x.min() < 180:
logger.debug('Shifting reader block longitudes to -180 to 180')
self.x = np.mod(self.x+180, 360) - 180
elif self.x.max() > 360:

# For global readers where longitude wraps at 360
# data_dict[x] should be provided as monotonously incereasing
self.wrap_x = wrap_x
if self.wrap_x:
if not np.all(np.diff(self.x)>0):
raise ValueError('X-coordinate (congitude) is not monotonuously increasing: ', self.x)
if self.x.max() - self.x.min() > 360:
raise ValueError('Longitude spans more than 360 degrees: ', self.x)

# We shift longitude/x to be in interval -180 to 360
if self.x.min() < -180 or self.x.max() > 360:
logger.debug('Shifting reader block longitudes to 0 to 360')
self.x = np.mod(self.x, 360)
if not np.all(np.diff(x)>0): # not increasing, shift again to -180-180
self.x = np.mod(self.x + 180, 360) - 180

# Mask any extremely large values, e.g. if missing netCDF _Fill_value
filled_variables = set()
Expand Down Expand Up @@ -88,7 +96,7 @@ def __init__(self, data_dict,
def _initialize_interpolator(self, x, y, z=None):
logger.debug('Initialising interpolator.')
if self.wrap_x is True:
if self.x.min() > 0:
if self.x.min() >= 0:
x = np.mod(x, 360) # Shift x/lons to 0-360
else:
x = np.mod(x + 180, 360) - 180 # Shift x/lons to -180-180
Expand Down Expand Up @@ -154,18 +162,23 @@ def _interpolate_horizontal_layers(self, data, nearest=False):
result[layer, :] = self.interpolator2d(data[layer, :, :])
return result

def _wrap_longitude(self, x):
# For global readers/blocks, we shift longitude/x to same quadrant as reader
if self.wrap_x is False:
return x
else:
if self.x.min() < 0:
return np.mod(x+180, 360) - 180
else:
return np.mod(x, 360)

def covers_positions(self, x, y, z=None):
'''Check if given positions are covered by this reader block.'''

if self.wrap_x is True:
if self.x.min() < 180:
logger.debug('Shifting longitudes to -180 to 180')
x = np.mod(x+180, 360) - 180
elif self.x.max() > 360:
logger.debug('Shifting longitudes to 0 to 360')
x = np.mod(x, 360)
x = self._wrap_longitude(x)
indices = np.where((x >= self.x.min()) & (x <= self.x.max()) &
(y >= self.y.min()) & (y <= self.y.max()))[0]

if len(indices) == len(x):
return True
else:
Expand Down
2 changes: 1 addition & 1 deletion opendrift/readers/reader_global_landmask.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def intersecting_geometries(self, extent):
else:
logger.debug('Getting fullres shapes from roaring landmask cache')
else:
logger.debug('Loading shapes with Cartopy shapereader...')
logger.debug(f'Loading shapes (\'{scale}\' level {level}) with Cartopy shapereader...')
# Load GSHHS geometries from appropriate shape file.
# TODO selective load based on bbox of each geom in file.
path = shapereader.gshhs(scale, level)
Expand Down
26 changes: 12 additions & 14 deletions opendrift/readers/reader_netCDF_CF_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def get_variables(self, requested_variables, time=None,
uniqx = np.unique(indx)
diff_xind = np.diff(uniqx)
# We split if >800 pixels between left/west and right/east blocks
if len(diff_xind)>1 and diff_xind.max() > np.minimum(800, 0.6*self.numx):
if len(diff_xind)>=1 and diff_xind.max() > np.minimum(800, 0.6*self.numx):
logger.debug('Requested data block crosses lon-border, reading and concatinating two parts')
split = True
splitind = np.argmax(diff_xind)
Expand Down Expand Up @@ -473,17 +473,17 @@ def get_variables(self, requested_variables, time=None,
dimorder[ynum] = self.dimensions['x']
var = var.permute_dims(*dimorder)

# Remove any unknown dimensions
for dim in var.dims:
if dim not in self.dimensions.values() and dim != self.ensemble_dimension:
logger.debug(f'Removing unknown dimension: {dim}')
var = var.squeeze(dim=dim)
if self.ensemble_dimension is not None and self.ensemble_dimension in var.dims:
ensemble_dim = 0 # hardcoded, may not work for MEPS
if split is False:
if True: # new dynamic way
subset = {vdim:dimindices[dim] for dim,vdim in self.dimensions.items() if vdim in var.dims}
variables[par] = var.isel(subset)
# Remove any unknown dimensions
for dim in variables[par].dims:
if dim not in self.dimensions.values() and dim != self.ensemble_dimension:
logger.debug(f'Removing unknown dimension: {dim}')
variables[par] = variables[par].squeeze(dim=dim)
if self.ensemble_dimension is not None and self.ensemble_dimension in variables[par].dims:
ensemble_dim = 0 # hardcoded, may not work for MEPS
#else: # old hardcoded way, to be removed
# if var.ndim == 2:
# variables[par] = var[indy, indx]
Expand Down Expand Up @@ -560,12 +560,10 @@ def get_variables(self, requested_variables, time=None,
if self.projected is True:
variables['x'] = self.x[indx]
variables['y'] = self.y[indy]
if split is True and variables['x'][0] > variables['x'][-1]:
# We need to shift so that x-coordinate (longitude) is continous
if self.lon_range() == '-180to180':
variables['x'][variables['x']>0] -= 360
elif self.lon_range() == '0to360':
variables['x'][variables['x']<0] += 360
if split is True and not np.all(np.diff(variables['x'])>0): # not monotonously increasing
variables['x'] = np.mod(variables['x'], 360) # Shift to 0-360
if not np.all(np.diff(variables['x'])>0): # not increasing, shift again to -180-180
variables['x'] = np.mod(variables['x'] + 180, 360) - 180
else:
variables['x'] = indx
variables['y'] = indy
Expand Down
22 changes: 22 additions & 0 deletions tests/readers/test_interpolation.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,28 @@ def test_dateline(self):
np.testing.assert_array_almost_equal(o.elements.lon, [-175.129, 175.129], decimal=3)
np.testing.assert_array_almost_equal(o.elements.lat, [60.006, 59.994], decimal=3)

# With elements spread globally (both around dateline and 0-meredian)
o = OceanDrift(loglevel=30)
o.add_readers_from_list([fc, fw])
lons = np.arange(-180, 181, 20)
lats = np.arange(-80, 81, 20)
lons, lats = np.meshgrid(lons, lats)
o.set_config('seed:ocean_only', False)
o.seed_elements(lon=lons, lat=lats, time=start_time, wind_drift_factor=.1)
o.run(steps=2, time_step=3600*12)
# Check expected drift direction for each quadrant
lon,lat = o.get_lonlats()
# North-westwards
np.testing.assert_array_almost_equal(lon[25,:], [-60, -60.77, -61.55], decimal=2)
np.testing.assert_array_almost_equal(lat[25,:], [-60, -59.96, -59.92], decimal=2)
np.testing.assert_array_almost_equal(lon[100,:], [-80, -80.41, -80.83], decimal=2)
np.testing.assert_array_almost_equal(lat[100,:], [20, 20.04, 20.08], decimal=2)
# South-eastwards
np.testing.assert_array_almost_equal(lon[35,:], [140, 140.77, 141.55], decimal=2)
np.testing.assert_array_almost_equal(lat[35,:], [-60, -60.04, -60.07], decimal=2)
np.testing.assert_array_almost_equal(lon[112,:], [160, 160.41, 160.83], decimal=2)
np.testing.assert_array_almost_equal(lat[112,:], [20, 19.96, 19.92], decimal=2)

# Cleaning up
os.remove(fw)
os.remove(fw2)
Expand Down

0 comments on commit 9a7f3cb

Please sign in to comment.