From 89da9f6c5f8844e504228a0cf706ef6e64c174a2 Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Tue, 28 Jun 2022 14:40:26 +0100 Subject: [PATCH 1/9] WIP changes for coordinate constraints. --- coast/__init__.py | 1 + coast/_utils/coordinates.py | 26 +++++++++++++++ coast/data/coast.py | 66 +++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 coast/_utils/coordinates.py diff --git a/coast/__init__.py b/coast/__init__.py index c8955bf0..d9184cb7 100644 --- a/coast/__init__.py +++ b/coast/__init__.py @@ -24,6 +24,7 @@ from ._utils.process_data import Process_data from .data.opendap import OpendapInfo from .data.copernicus import Copernicus, Product +from ._utils.coordinates import Coordinates2D, Coordinates3D, Coordinates4D, Coordinates # Set default for logging level when coast is imported import logging diff --git a/coast/_utils/coordinates.py b/coast/_utils/coordinates.py new file mode 100644 index 00000000..cd176003 --- /dev/null +++ b/coast/_utils/coordinates.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from numpy import number +from numbers import Number +from typing import Union, Optional + + +Numeric = Optional[Union[Number, number]] + + +@dataclass +class Coordinates2D: + x: Numeric + y: Numeric + + +@dataclass +class Coordinates3D(Coordinates2D): + z: Numeric + + +@dataclass +class Coordinates4D(Coordinates3D): + t: Numeric + + +Coordinates = Union[Coordinates2D, Coordinates3D, Coordinates4D] diff --git a/coast/data/coast.py b/coast/data/coast.py index 63d81e3a..a9fbaa9a 100644 --- a/coast/data/coast.py +++ b/coast/data/coast.py @@ -5,6 +5,7 @@ from dask.distributed import Client import copy from .._utils.logging_util import get_slug, debug, info, warn, warning +from .._utils.coordinates import Coordinates, Coordinates3D, Coordinates4D, Numeric from .opendap import OpendapInfo @@ -467,5 +468,70 @@ def plot_cartopy(self, var: str, plot_var: array, params, time_counter: int = 0) info("Displaying plot!") plt.show() + def set_constraint(self, start: Coordinates, end: Coordinates, drop: bool = True) -> None: + self.dataset = self.constrain(start, end, drop=drop) + + def constrain(self, start: Coordinates, end: Coordinates, drop: bool = True): + return constrain(self.dataset, start, end, drop=drop) + + @property + def x_dim(self): + return x_dim(self.dataset) + + @property + def y_dim(self): + return y_dim(self.dataset) + + @property + def z_dim(self): + return z_dim(self.dataset) + + @property + def t_dim(self): + return t_dim(self.dataset) + + def get_coord(self, dim: str): + # Really not a fan of this, is there an easier way to get the mapping? + return get_coord(self.dataset, dim) + def plot_movie(self): raise NotImplementedError + + +def create_constraint(start: Numeric, end: Numeric, dim: xr.DataArray, drop: bool = True): + return np.logical_and(dim >= start, dim <= end) + + +def get_coord(dataset: xr.Dataset, dim: str): + return dataset[list(dataset[f"{dim.lower()}_dim"].coords)[0]] + + +def x_dim(dataset: xr.Dataset): + return get_coord(dataset, "x") + + +def y_dim(dataset: xr.Dataset): + return get_coord(dataset, "y") + + +def z_dim(dataset: xr.Dataset): + return get_coord(dataset, "z") + + +def t_dim(dataset: xr.Dataset): + return get_coord(dataset, "t") + + +def constrain(dataset: xr.Dataset, start: Coordinates, end: Coordinates, drop: bool = True): + assert type(start) == type(end), "Coordinates must be of the same dimensionality!" + + constrained = dataset + if start.x is not None and end.x is not None: + constrained = constrained.where(create_constraint(start.x, end.x, x_dim(constrained), drop=drop), drop=drop) + if start.y is not None and end.y is not None: + constrained = constrained.where(create_constraint(start.y, end.y, y_dim(constrained), drop=drop), drop=drop) + if isinstance(start, Coordinates3D) and start.z is not None and end.z is not None: + constrained = constrained.where(create_constraint(start.z, end.y, z_dim(constrained), drop=drop), drop=drop) + if isinstance(start, Coordinates4D) and start.t is not None and end.t is not None: + constrained = constrained.where(create_constraint(start.t, end.t, t_dim(constrained), drop=drop), drop=drop) + return constrained From 21cbbb73a80f82565b85d35214fcff66d27e9b03 Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Wed, 29 Jun 2022 12:46:23 +0100 Subject: [PATCH 2/9] Add missing docstrings and type hints. --- coast/_utils/coordinates.py | 5 +- coast/data/coast.py | 139 ++++++++++++++++++++++++++++++------ 2 files changed, 121 insertions(+), 23 deletions(-) diff --git a/coast/_utils/coordinates.py b/coast/_utils/coordinates.py index cd176003..b393e799 100644 --- a/coast/_utils/coordinates.py +++ b/coast/_utils/coordinates.py @@ -9,18 +9,21 @@ @dataclass class Coordinates2D: + """Represent a point in one-to-two-dimensional space with optional X and Y coordinates.""" x: Numeric y: Numeric @dataclass class Coordinates3D(Coordinates2D): + """Represent a point in one-to-three-dimensional space with optional X, Y, and Z coordinates.""" z: Numeric @dataclass class Coordinates4D(Coordinates3D): - t: Numeric + """Represent a point in one-to-four-dimensional spacetime with optional X, Y, Z, and T coordinates.""" + t: Numeric # TODO Should this be a datetime or is it likely to be something like a Unix timestamp? Coordinates = Union[Coordinates2D, Coordinates3D, Coordinates4D] diff --git a/coast/data/coast.py b/coast/data/coast.py index a9fbaa9a..f37310e1 100644 --- a/coast/data/coast.py +++ b/coast/data/coast.py @@ -469,69 +469,164 @@ def plot_cartopy(self, var: str, plot_var: array, params, time_counter: int = 0) plt.show() def set_constraint(self, start: Coordinates, end: Coordinates, drop: bool = True) -> None: + """Constrain the underlying dataset to values within an arbitrarily sized orthotope. + + Args: + start: The start coordinates of the shape to define. + end: The end coordinates of the shape to define. + drop: Whether values should be dropped from the constrained dataset (if False, they will be NaNed). + """ self.dataset = self.constrain(start, end, drop=drop) - def constrain(self, start: Coordinates, end: Coordinates, drop: bool = True): + def constrain(self, start: Coordinates, end: Coordinates, drop: bool = True) -> xr.Dataset: + """Return the underlying dataset with values constrained to an arbitrarily sized orthotope. + + Args: + start: The start coordinates of the shape to define. + end: The end coordinates of the shape to define. + drop: Whether values should be dropped from the constrained dataset (if False, they will be NaNed). + + Returns: + The underlying dataset with values constrained to within the defined selection. + """ return constrain(self.dataset, start, end, drop=drop) @property - def x_dim(self): + def x_dim(self) -> xr.DataArray: + """Return the X coordinate array of the underlying dataset.""" return x_dim(self.dataset) @property - def y_dim(self): + def y_dim(self) -> xr.DataArray: + """Return the Y coordinate array of the underlying dataset.""" return y_dim(self.dataset) @property - def z_dim(self): + def z_dim(self) -> xr.DataArray: + """Return the Z coordinate array of the underlying dataset.""" return z_dim(self.dataset) @property - def t_dim(self): + def t_dim(self) -> xr.DataArray: + """Return the T[ime] coordinate array of the underlying dataset.""" return t_dim(self.dataset) - def get_coord(self, dim: str): - # Really not a fan of this, is there an easier way to get the mapping? + def get_coord(self, dim: str) -> xr.DataArray: + """Get the coordinate array for a dimension from the underlying dataset. + + Args: + dim: The name of the dimension (i.e. "x", "y", "z", or "t"). + + Returns: + The corresponding coordinate array from the underlying dataset. + """ return get_coord(self.dataset, dim) def plot_movie(self): raise NotImplementedError -def create_constraint(start: Numeric, end: Numeric, dim: xr.DataArray, drop: bool = True): +def create_constraint(start: Numeric, end: Numeric, dim: xr.DataArray) -> np.typing.NDArray[bool]: + """Create a mask to exclude coordinates that do not fall within a range of two arbitrary values. + + Args: + start: The start of the range of values to constrain within. + end: The end of the range of values ot constrain within. + dim: The coordinate array to constrain values from. + + Returns: + A mask that can be applied to dim to exclude unwanted values. + """ return np.logical_and(dim >= start, dim <= end) -def get_coord(dataset: xr.Dataset, dim: str): +def get_coord(dataset: xr.Dataset, dim: str) -> xr.DataArray: + """Get the coordinate array for a dimension in a dataset. + + Args: + dataset: The dataset to interrogate. + dim: The name of the dimension (i.e. "x", "y", "z", or "t"). + + Returns: + The corresponding coordinate array from the provided dataset. + """ + # TODO Really not a fan of this, is there an easier way to get the mapping? return dataset[list(dataset[f"{dim.lower()}_dim"].coords)[0]] -def x_dim(dataset: xr.Dataset): +def x_dim(dataset: xr.Dataset) -> xr.DataArray: + """Get the X coordinate array for a dimension in a dataset. + + Args: + dataset: The dataset to interrogate. + + Returns: + The corresponding coordinate array from the provided dataset. + """ return get_coord(dataset, "x") -def y_dim(dataset: xr.Dataset): +def y_dim(dataset: xr.Dataset) -> xr.DataArray: + """Get the Y coordinate array for a dimension in a dataset. + + Args: + dataset: The dataset to interrogate. + + Returns: + The corresponding coordinate array from the provided dataset. + """ return get_coord(dataset, "y") -def z_dim(dataset: xr.Dataset): +def z_dim(dataset: xr.Dataset) -> xr.DataArray: + """Get the Z coordinate array for a dimension in a dataset. + + Args: + dataset: The dataset to interrogate. + + Returns: + The corresponding coordinate array from the provided dataset. + """ return get_coord(dataset, "z") -def t_dim(dataset: xr.Dataset): +def t_dim(dataset: xr.Dataset) -> xr.DataArray: + """Get the T[ime] coordinate array for a dimension in a dataset. + + Args: + dataset: The dataset to interrogate. + + Returns: + The corresponding coordinate array from the provided dataset. + """ return get_coord(dataset, "t") -def constrain(dataset: xr.Dataset, start: Coordinates, end: Coordinates, drop: bool = True): +def constrain(dataset: xr.Dataset, start: Coordinates, end: Coordinates, drop: bool = True) -> xr.Dataset: + """Constrain values within a dataset to an arbitrarily sized orthotope. + + Args: + dataset: The dataset to constrain values from. + start: The start coordinates of the shape to define. + end: The end coordinates of the shape to define. + drop: Whether values should be dropped from the constrained dataset (if False, they will be NaNed). + + Returns: + The provided dataset with values constrained to within the defined selection. + """ assert type(start) == type(end), "Coordinates must be of the same dimensionality!" constrained = dataset - if start.x is not None and end.x is not None: - constrained = constrained.where(create_constraint(start.x, end.x, x_dim(constrained), drop=drop), drop=drop) - if start.y is not None and end.y is not None: - constrained = constrained.where(create_constraint(start.y, end.y, y_dim(constrained), drop=drop), drop=drop) - if isinstance(start, Coordinates3D) and start.z is not None and end.z is not None: - constrained = constrained.where(create_constraint(start.z, end.y, z_dim(constrained), drop=drop), drop=drop) - if isinstance(start, Coordinates4D) and start.t is not None and end.t is not None: - constrained = constrained.where(create_constraint(start.t, end.t, t_dim(constrained), drop=drop), drop=drop) + if (x_start := start.x is not None) and (x_end := end.x is not None): + assert x_start == x_end, "Tried to constrain on X with a missing paired value!" + constrained = constrained.where(create_constraint(start.x, end.x, x_dim(constrained)), drop=drop) + if (y_start := start.y is not None) and (y_end := end.y is not None): + assert y_start == y_end, "Tried to constrain on Y with a missing paired value!" + constrained = constrained.where(create_constraint(start.y, end.y, y_dim(constrained)), drop=drop) + if isinstance(start, Coordinates3D) and (z_start := start.z is not None) and (z_end := end.z is not None): + assert z_start == z_end, "Tried to constrain on Z with a missing paired value!" + constrained = constrained.where(create_constraint(start.z, end.y, z_dim(constrained)), drop=drop) + if isinstance(start, Coordinates4D) and (t_start := start.t is not None) and (t_end := end.t is not None): + assert t_start == t_end, "Tried to constrain on Z with a missing paired value!" + constrained = constrained.where(create_constraint(start.t, end.t, t_dim(constrained)), drop=drop) return constrained From 54de8b3fa3ab57e232a59d6dbdd135d6b9b102bb Mon Sep 17 00:00:00 2001 From: BlackBot Date: Wed, 29 Jun 2022 12:00:00 +0000 Subject: [PATCH 3/9] Apply Black formatting to Python code. --- coast/_utils/coordinates.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/coast/_utils/coordinates.py b/coast/_utils/coordinates.py index b393e799..4c190386 100644 --- a/coast/_utils/coordinates.py +++ b/coast/_utils/coordinates.py @@ -10,6 +10,7 @@ @dataclass class Coordinates2D: """Represent a point in one-to-two-dimensional space with optional X and Y coordinates.""" + x: Numeric y: Numeric @@ -17,12 +18,14 @@ class Coordinates2D: @dataclass class Coordinates3D(Coordinates2D): """Represent a point in one-to-three-dimensional space with optional X, Y, and Z coordinates.""" + z: Numeric @dataclass class Coordinates4D(Coordinates3D): """Represent a point in one-to-four-dimensional spacetime with optional X, Y, Z, and T coordinates.""" + t: Numeric # TODO Should this be a datetime or is it likely to be something like a Unix timestamp? From e3dab1e9311fe1f3a994da288b8f381b0aeb06f9 Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Wed, 29 Jun 2022 14:22:09 +0100 Subject: [PATCH 4/9] Use datetime64 for time coordinates. --- coast/_utils/coordinates.py | 5 +++-- coast/data/coast.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/coast/_utils/coordinates.py b/coast/_utils/coordinates.py index 4c190386..2e1f04e2 100644 --- a/coast/_utils/coordinates.py +++ b/coast/_utils/coordinates.py @@ -1,10 +1,11 @@ from dataclasses import dataclass -from numpy import number +from numpy import number, datetime64 from numbers import Number from typing import Union, Optional Numeric = Optional[Union[Number, number]] +Coordinate = Union[Numeric, datetime64] @dataclass @@ -26,7 +27,7 @@ class Coordinates3D(Coordinates2D): class Coordinates4D(Coordinates3D): """Represent a point in one-to-four-dimensional spacetime with optional X, Y, Z, and T coordinates.""" - t: Numeric # TODO Should this be a datetime or is it likely to be something like a Unix timestamp? + t: datetime64 Coordinates = Union[Coordinates2D, Coordinates3D, Coordinates4D] diff --git a/coast/data/coast.py b/coast/data/coast.py index f37310e1..dae3bf56 100644 --- a/coast/data/coast.py +++ b/coast/data/coast.py @@ -5,7 +5,7 @@ from dask.distributed import Client import copy from .._utils.logging_util import get_slug, debug, info, warn, warning -from .._utils.coordinates import Coordinates, Coordinates3D, Coordinates4D, Numeric +from .._utils.coordinates import Coordinates, Coordinates3D, Coordinates4D, Coordinate from .opendap import OpendapInfo @@ -526,7 +526,7 @@ def plot_movie(self): raise NotImplementedError -def create_constraint(start: Numeric, end: Numeric, dim: xr.DataArray) -> np.typing.NDArray[bool]: +def create_constraint(start: Coordinate, end: Coordinate, dim: xr.DataArray) -> np.typing.NDArray[bool]: """Create a mask to exclude coordinates that do not fall within a range of two arbitrary values. Args: From ef682f9a4a1bc56a615035df4295a163d8fd16cf Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Wed, 29 Jun 2022 14:25:45 +0100 Subject: [PATCH 5/9] Use "hyperrectangle" instead of "orthotope". --- coast/data/coast.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/coast/data/coast.py b/coast/data/coast.py index dae3bf56..c94f0b4e 100644 --- a/coast/data/coast.py +++ b/coast/data/coast.py @@ -469,7 +469,7 @@ def plot_cartopy(self, var: str, plot_var: array, params, time_counter: int = 0) plt.show() def set_constraint(self, start: Coordinates, end: Coordinates, drop: bool = True) -> None: - """Constrain the underlying dataset to values within an arbitrarily sized orthotope. + """Constrain the underlying dataset to values within an arbitrarily sized hyperrectangle. Args: start: The start coordinates of the shape to define. @@ -479,7 +479,7 @@ def set_constraint(self, start: Coordinates, end: Coordinates, drop: bool = True self.dataset = self.constrain(start, end, drop=drop) def constrain(self, start: Coordinates, end: Coordinates, drop: bool = True) -> xr.Dataset: - """Return the underlying dataset with values constrained to an arbitrarily sized orthotope. + """Return the underlying dataset with values constrained to an arbitrarily sized hyperrectangle. Args: start: The start coordinates of the shape to define. @@ -603,7 +603,7 @@ def t_dim(dataset: xr.Dataset) -> xr.DataArray: def constrain(dataset: xr.Dataset, start: Coordinates, end: Coordinates, drop: bool = True) -> xr.Dataset: - """Constrain values within a dataset to an arbitrarily sized orthotope. + """Constrain values within a dataset to an arbitrarily sized hyperrectangle. Args: dataset: The dataset to constrain values from. From 61af083c1b17ac53ed87096a1886e8f4ae97627a Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Tue, 13 Sep 2022 13:07:44 +0100 Subject: [PATCH 6/9] First pass for wrapping around dataset boundaries. --- coast/data/coast.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/coast/data/coast.py b/coast/data/coast.py index c94f0b4e..1eb2afe2 100644 --- a/coast/data/coast.py +++ b/coast/data/coast.py @@ -1,4 +1,5 @@ """The coast class is the main access point into this package.""" +from typing import Optional from dask import array import xarray as xr import numpy as np @@ -537,7 +538,18 @@ def create_constraint(start: Coordinate, end: Coordinate, dim: xr.DataArray) -> Returns: A mask that can be applied to dim to exclude unwanted values. """ - return np.logical_and(dim >= start, dim <= end) + minimum = dim.min().item() + maximum = dim.max().item() + + mask = np.logical_and(dim >= start, dim <= end) + if start < minimum or start > maximum: + diff = start % minimum + mask = np.logical_or(mask, dim >= maximum + diff) + if end > maximum or end < minimum: + diff = end % maximum + mask = np.logical_or(mask, dim <= minimum + diff) + + return mask def get_coord(dataset: xr.Dataset, dim: str) -> xr.DataArray: From bcd86ba7e234d15a164a38fc08106e9c109cf9b9 Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Wed, 14 Sep 2022 13:56:01 +0100 Subject: [PATCH 7/9] Update some docstrings. --- coast/data/coast.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/coast/data/coast.py b/coast/data/coast.py index 11a301b9..02d61d20 100644 --- a/coast/data/coast.py +++ b/coast/data/coast.py @@ -510,6 +510,9 @@ def plot_cartopy(self, var: str, plot_var: array, params, time_counter: int = 0) def set_constraint(self, start: Coordinates, end: Coordinates, drop: bool = True) -> None: """Constrain the underlying dataset to values within an arbitrarily sized hyperrectangle. + Coordinates that exceed the boundaries of the dataset will wrap around. i.e. a longitude value of 190 applied + to a dataset with a maximum value of 180 would wrap to -10. + Args: start: The start coordinates of the shape to define. end: The end coordinates of the shape to define. @@ -520,6 +523,9 @@ def set_constraint(self, start: Coordinates, end: Coordinates, drop: bool = True def constrain(self, start: Coordinates, end: Coordinates, drop: bool = True) -> xr.Dataset: """Return the underlying dataset with values constrained to an arbitrarily sized hyperrectangle. + Coordinates that exceed the boundaries of the dataset will wrap around. i.e. a longitude value of 190 applied + to a dataset with a maximum value of 180 would wrap to -10. + Args: start: The start coordinates of the shape to define. end: The end coordinates of the shape to define. @@ -569,6 +575,9 @@ def plot_movie(self): def create_constraint(start: Coordinate, end: Coordinate, dim: xr.DataArray) -> np.typing.NDArray[bool]: """Create a mask to exclude coordinates that do not fall within a range of two arbitrary values. + Coordinates that exceed the boundaries of the dataset will wrap around. i.e. a longitude value of 190 applied + to a dataset with a maximum value of 180 would wrap to -10. + Args: start: The start of the range of values to constrain within. end: The end of the range of values ot constrain within. @@ -656,6 +665,9 @@ def t_dim(dataset: xr.Dataset) -> xr.DataArray: def constrain(dataset: xr.Dataset, start: Coordinates, end: Coordinates, drop: bool = True) -> xr.Dataset: """Constrain values within a dataset to an arbitrarily sized hyperrectangle. + Coordinates that exceed the boundaries of the dataset will wrap around. i.e. a longitude value of 190 applied + to a dataset with a maximum value of 180 would wrap to -10. + Args: dataset: The dataset to constrain values from. start: The start coordinates of the shape to define. From 78f5a32660740cc11da7c37a29c5faa0073fcebf Mon Sep 17 00:00:00 2001 From: Matthew Cazaly Date: Wed, 14 Sep 2022 15:04:31 +0100 Subject: [PATCH 8/9] Add some VERY basic tests - these should be enhanced in the future. --- tests/test_subsetting.py | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/test_subsetting.py diff --git a/tests/test_subsetting.py b/tests/test_subsetting.py new file mode 100644 index 00000000..f45bab94 --- /dev/null +++ b/tests/test_subsetting.py @@ -0,0 +1,64 @@ +import logging +from os import environ +from pathlib import Path +import pytest +from coast import Copernicus, Coast, Gridded, Coordinates2D + + +DATABASE = "nrt" +PRODUCT_ID = "global-analysis-forecast-phy-001-024" +CONFIG = (Path(__file__).parent.parent / "config" / "example_cmems_grid_t.json").resolve(strict=True) +USERNAME = environ.get("COPERNICUS_USERNAME") +PASSWORD = environ.get("COPERNICUS_PASSWORD") +CREDENTIALS = USERNAME is not None and PASSWORD is not None + + +@pytest.fixture(name="copernicus") +def copernicus_fixture() -> Copernicus: + """Return a functional Copernicus data accessor.""" + return Copernicus(USERNAME, PASSWORD, DATABASE) + + +@pytest.fixture(name="gridded") +def gridded_fixture(copernicus) -> Gridded: + forecast = copernicus.get_product("global-analysis-forecast-phy-001-024") + return Gridded(fn_data=forecast, config=str(CONFIG)) + + +@pytest.mark.skipif(condition=not CREDENTIALS, reason="Copernicus credentials are not set.") +def test_2d(gridded): + start = Coordinates2D(10, 13) + end = Coordinates2D(20, 50) + # Validate test values + assert gridded.dataset.longitude.min().item() < start.x + assert gridded.dataset.latitude.min().item() < start.y + assert gridded.dataset.longitude.max().item() > end.x + assert gridded.dataset.latitude.max().item() > end.y + + # Constrain dataset + constrained = gridded.constrain(start, end) + + # Check constrained dataset + assert constrained.longitude.min().item() == start.x + assert constrained.latitude.min().item() == start.y + assert constrained.longitude.max().item() == end.x + assert constrained.latitude.max().item() == end.y + + +@pytest.mark.skipif(condition=not CREDENTIALS, reason="Copernicus credentials are not set.") +def test_wrap(gridded): + start = Coordinates2D(175, 50) + end = Coordinates2D(gridded.dataset.longitude.max().item() + 5, 60) + wrapped = gridded.dataset.longitude.min().item() + 5 + + # Constraint dataset + constrained = gridded.constrain(start, end) + + # Check constrained dataset + assert wrapped < start.x + assert wrapped in constrained.longitude + assert constrained.max().item() == 185 % gridded.dataset.max.item() + + +if not CREDENTIALS: + logging.warning("https://marine.copernicus.eu/ credentials not set, integration tests will not be run!") \ No newline at end of file From dab711064d0f025c0212e34808f0fa2d1f5d06c6 Mon Sep 17 00:00:00 2001 From: BlackBot Date: Wed, 14 Sep 2022 14:05:18 +0000 Subject: [PATCH 9/9] Apply Black formatting to Python code. --- tests/test_subsetting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_subsetting.py b/tests/test_subsetting.py index f45bab94..0c9330de 100644 --- a/tests/test_subsetting.py +++ b/tests/test_subsetting.py @@ -61,4 +61,4 @@ def test_wrap(gridded): if not CREDENTIALS: - logging.warning("https://marine.copernicus.eu/ credentials not set, integration tests will not be run!") \ No newline at end of file + logging.warning("https://marine.copernicus.eu/ credentials not set, integration tests will not be run!")