diff --git a/opendrift/models/basemodel/__init__.py b/opendrift/models/basemodel/__init__.py index 344937e8c..21aa8082e 100644 --- a/opendrift/models/basemodel/__init__.py +++ b/opendrift/models/basemodel/__init__.py @@ -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) diff --git a/opendrift/readers/interpolation/structured.py b/opendrift/readers/interpolation/structured.py index ed1cc51d8..587cc7ffd 100644 --- a/opendrift/readers/interpolation/structured.py +++ b/opendrift/readers/interpolation/structured.py @@ -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() @@ -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 @@ -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: diff --git a/opendrift/readers/reader_global_landmask.py b/opendrift/readers/reader_global_landmask.py index fcfe4df68..342339211 100644 --- a/opendrift/readers/reader_global_landmask.py +++ b/opendrift/readers/reader_global_landmask.py @@ -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) diff --git a/opendrift/readers/reader_netCDF_CF_generic.py b/opendrift/readers/reader_netCDF_CF_generic.py index 97184b522..480c88522 100644 --- a/opendrift/readers/reader_netCDF_CF_generic.py +++ b/opendrift/readers/reader_netCDF_CF_generic.py @@ -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) @@ -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] @@ -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 diff --git a/tests/readers/test_interpolation.py b/tests/readers/test_interpolation.py index 2361627ad..ca097bf5a 100644 --- a/tests/readers/test_interpolation.py +++ b/tests/readers/test_interpolation.py @@ -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)