From 6df2f2771e3f61cf813ebcabdb2e3ef68e5dae6b Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Fri, 20 Dec 2024 12:05:42 +0100 Subject: [PATCH 1/7] Cleanup --- pyproject.toml.rej | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 pyproject.toml.rej diff --git a/pyproject.toml.rej b/pyproject.toml.rej deleted file mode 100644 index b5d7518..0000000 --- a/pyproject.toml.rej +++ /dev/null @@ -1,28 +0,0 @@ -diff a/pyproject.toml b/pyproject.toml (rejected hunks) -@@ -1,5 +1,6 @@ - [build-system] --requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"] -+build-backend = "setuptools.build_meta" -+requires = ["setuptools>=64", "setuptools_scm>=8"] - - [project] - classifiers = [ -@@ -25,13 +26,15 @@ branch = true - strict = true - - [tool.ruff] -+# Same as Black. -+indent-width = 4 -+line-length = 88 -+ -+[tool.ruff.lint] - ignore = [ - # pydocstyle: Missing Docstrings - "D1" - ] --# Same as Black. --indent-width = 4 --line-length = 88 - select = [ - # pyflakes - "F", From 7464ac637c1cc6e8b9d0ab3f22ea1e72c74f1dc3 Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Fri, 20 Dec 2024 12:06:23 +0100 Subject: [PATCH 2/7] Add a simple client for the dataspace service --- xarray_sentinel/dataspace_client.py | 93 +++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 xarray_sentinel/dataspace_client.py diff --git a/xarray_sentinel/dataspace_client.py b/xarray_sentinel/dataspace_client.py new file mode 100644 index 0000000..2439de7 --- /dev/null +++ b/xarray_sentinel/dataspace_client.py @@ -0,0 +1,93 @@ +import datetime +from typing import Any + +import requests +import shapely +import structlog +import typer + +DEFAULT_ODATA_URL: str = "https://catalogue.dataspace.copernicus.eu/odata/v1" +DEFAULT_PRODUCTS_ODATA_FILTER_TEMPLATE = ( + "((Collection/Name eq 'SENTINEL-1' " + "and (Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'instrumentShortName' and att/OData.CSC.StringAttribute/Value eq 'SAR') " + "and (contains(Name,'GRD') and contains(Name,'_COG') " + "and OData.CSC.Intersects(area=geography'{geometry_wkt}'))) " + "and (Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'operationalMode' and att/OData.CSC.StringAttribute/Value eq 'IW') " + "and Attributes/OData.CSC.IntegerAttribute/any(att:att/Name eq 'relativeOrbitNumber' and att/OData.CSC.IntegerAttribute/Value eq {relative_orbit}))) " + "and ContentDate/Start ge {start_date_iso}Z and ContentDate/Start lt {stop_date_iso}Z)" +) +LOGGER = structlog.getLogger(__name__) + + +class DataSpaceClient: + + def __init__( + self, + odata_url: str = DEFAULT_ODATA_URL, + products_odata_filter_template: str = DEFAULT_PRODUCTS_ODATA_FILTER_TEMPLATE, + ) -> None: + self.odata_url = odata_url + self.products_odata_filter_template = products_odata_filter_template + + def build_sentinel1_products_odata_filter( + self, + start_date: datetime.datetime, + stop_date: datetime.datetime, + relative_orbit: int, + bbox: tuple[float, float, float, float] = (-180, -90, 180, 90), + ) -> str: + geometry = shapely.box(*bbox) + geometry_wkt = f"SRID=4326;{geometry.wkt}" + sentinel1_products_odata_filter = self.products_odata_filter_template.format( + geometry_wkt=geometry_wkt, + start_date_iso=start_date.isoformat(), + stop_date_iso=stop_date.isoformat(), + relative_orbit=relative_orbit, + ) + return sentinel1_products_odata_filter + + def search_sentinel1_products( + self, + start_date: datetime.datetime, + stop_date: datetime.datetime, + relative_orbit: int, + bbox: tuple[float, float, float, float] = (-180, -90, 180, 90), + limit: int = 100, + log: structlog.BoundLogger = LOGGER, + ) -> dict[str, Any]: + product_url = f"{self.odata_url}/Products" + odata_filter = self.build_sentinel1_products_odata_filter( + start_date, stop_date, relative_orbit, bbox + ) + params = { + "$filter": odata_filter, + "$top": limit, + } + resp = requests.get(product_url, params=params) + resp.raise_for_status() + return resp.json() + + +def get_s3paths(results: list[Any]) -> list[str]: + return sorted([result.get("S3Path") for result in results]) + + +def search_sentinel1_products( + start_date: str = "2024-01-01", + stop_date: str = "2024-12-31", + relative_orbit: int = 22, + bbox: tuple[float, float, float, float] = (6.75, 36.62, 18.48, 47.11), + limit: int = 100, + odata_url: str = DEFAULT_ODATA_URL, +) -> None: + dataspace_client = DataSpaceClient(odata_url) + resutls = dataspace_client.search_sentinel1_products( + start_date, stop_date, relative_orbit, bbox, limit + ) + results = get_s3paths(resutls["value"]) + for res in results: + print(res) + + +if __name__ == "__main__": + typer.run(search_sentinel1_products) From 27ce9dc839b6ed5d0a191eefd5c31bf80c924e58 Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Fri, 20 Dec 2024 12:19:34 +0100 Subject: [PATCH 3/7] Code style and typing (amazing typer!) --- xarray_sentinel/dataspace_client.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/xarray_sentinel/dataspace_client.py b/xarray_sentinel/dataspace_client.py index 2439de7..16d5d30 100644 --- a/xarray_sentinel/dataspace_client.py +++ b/xarray_sentinel/dataspace_client.py @@ -9,18 +9,20 @@ DEFAULT_ODATA_URL: str = "https://catalogue.dataspace.copernicus.eu/odata/v1" DEFAULT_PRODUCTS_ODATA_FILTER_TEMPLATE = ( "((Collection/Name eq 'SENTINEL-1' " - "and (Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'instrumentShortName' and att/OData.CSC.StringAttribute/Value eq 'SAR') " + "and (Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'instrumentShortName' " + "and att/OData.CSC.StringAttribute/Value eq 'SAR') " "and (contains(Name,'GRD') and contains(Name,'_COG') " "and OData.CSC.Intersects(area=geography'{geometry_wkt}'))) " - "and (Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'operationalMode' and att/OData.CSC.StringAttribute/Value eq 'IW') " - "and Attributes/OData.CSC.IntegerAttribute/any(att:att/Name eq 'relativeOrbitNumber' and att/OData.CSC.IntegerAttribute/Value eq {relative_orbit}))) " + "and (Attributes/OData.CSC.StringAttribute/any(att:att/Name eq 'operationalMode' " + "and att/OData.CSC.StringAttribute/Value eq 'IW') " + "and Attributes/OData.CSC.IntegerAttribute/any(att:att/Name eq 'relativeOrbitNumber' " + "and att/OData.CSC.IntegerAttribute/Value eq {relative_orbit}))) " "and ContentDate/Start ge {start_date_iso}Z and ContentDate/Start lt {stop_date_iso}Z)" ) LOGGER = structlog.getLogger(__name__) class DataSpaceClient: - def __init__( self, odata_url: str = DEFAULT_ODATA_URL, @@ -54,11 +56,12 @@ def search_sentinel1_products( bbox: tuple[float, float, float, float] = (-180, -90, 180, 90), limit: int = 100, log: structlog.BoundLogger = LOGGER, - ) -> dict[str, Any]: + ) -> Any: product_url = f"{self.odata_url}/Products" odata_filter = self.build_sentinel1_products_odata_filter( start_date, stop_date, relative_orbit, bbox ) + params: dict[str, str | int] params = { "$filter": odata_filter, "$top": limit, @@ -73,8 +76,8 @@ def get_s3paths(results: list[Any]) -> list[str]: def search_sentinel1_products( - start_date: str = "2024-01-01", - stop_date: str = "2024-12-31", + start_date: datetime.datetime = datetime.datetime(2024, 1, 1), + stop_date: datetime.datetime = datetime.datetime(2024, 12, 31), relative_orbit: int = 22, bbox: tuple[float, float, float, float] = (6.75, 36.62, 18.48, 47.11), limit: int = 100, From 0ad165f5fdcaae0760700247fdb503a146fbb8dc Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Fri, 20 Dec 2024 12:54:39 +0100 Subject: [PATCH 4/7] Log not used yet --- xarray_sentinel/dataspace_client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/xarray_sentinel/dataspace_client.py b/xarray_sentinel/dataspace_client.py index 16d5d30..b0761cb 100644 --- a/xarray_sentinel/dataspace_client.py +++ b/xarray_sentinel/dataspace_client.py @@ -3,7 +3,6 @@ import requests import shapely -import structlog import typer DEFAULT_ODATA_URL: str = "https://catalogue.dataspace.copernicus.eu/odata/v1" @@ -19,7 +18,6 @@ "and att/OData.CSC.IntegerAttribute/Value eq {relative_orbit}))) " "and ContentDate/Start ge {start_date_iso}Z and ContentDate/Start lt {stop_date_iso}Z)" ) -LOGGER = structlog.getLogger(__name__) class DataSpaceClient: @@ -55,7 +53,6 @@ def search_sentinel1_products( relative_orbit: int, bbox: tuple[float, float, float, float] = (-180, -90, 180, 90), limit: int = 100, - log: structlog.BoundLogger = LOGGER, ) -> Any: product_url = f"{self.odata_url}/Products" odata_filter = self.build_sentinel1_products_odata_filter( From 436214a5f62b103e9ad4954dc29ee4fca7af6df5 Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Fri, 20 Dec 2024 12:55:00 +0100 Subject: [PATCH 5/7] Add minimal testing (the rest needs integration tests) --- tests/test_10_dataspace_client.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tests/test_10_dataspace_client.py diff --git a/tests/test_10_dataspace_client.py b/tests/test_10_dataspace_client.py new file mode 100644 index 0000000..b005706 --- /dev/null +++ b/tests/test_10_dataspace_client.py @@ -0,0 +1,16 @@ +import datetime +from xarray_sentinel import dataspace_client + + +def test_DataSpaceClient() -> None: + filter_template = "{start_date_iso} {stop_date_iso} {relative_orbit} {geometry_wkt}" + client = dataspace_client.DataSpaceClient( + products_odata_filter_template=filter_template + ) + date = datetime.datetime(2020, 2, 3) + bbox = (0, 1, 2, 3) + expected = "2020-02-03T00:00:00 2020-02-03T00:00:00 22 SRID=4326;POLYGON ((2 1, 2 3, 0 3, 0 1, 2 1))" + + res = client.build_sentinel1_products_odata_filter(date, date, 22, bbox) + + assert res == expected From 528089a1a744b4c0f28f0d1efe8a809e4b606139 Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Tue, 31 Dec 2024 12:17:42 +0100 Subject: [PATCH 6/7] Try avoid a warning. --- xarray_sentinel/sentinel1.py | 1 + 1 file changed, 1 insertion(+) diff --git a/xarray_sentinel/sentinel1.py b/xarray_sentinel/sentinel1.py index 2716c02..ed42154 100644 --- a/xarray_sentinel/sentinel1.py +++ b/xarray_sentinel/sentinel1.py @@ -544,6 +544,7 @@ def make_azimuth_time( start=product_first_line_utc_time, end=product_last_line_utc_time, periods=number_of_lines, + unit="ns", ) return azimuth_time.values From f8ee49b8fd124b0ecb3d04d704170093437d7dd9 Mon Sep 17 00:00:00 2001 From: Alessandro Amici Date: Tue, 31 Dec 2024 13:38:53 +0100 Subject: [PATCH 7/7] Use ns everywhere --- xarray_sentinel/sentinel1.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray_sentinel/sentinel1.py b/xarray_sentinel/sentinel1.py index ed42154..cad1930 100644 --- a/xarray_sentinel/sentinel1.py +++ b/xarray_sentinel/sentinel1.py @@ -26,7 +26,7 @@ from . import conventions, esa_safe SPEED_OF_LIGHT = 299_792_458 # m / s -ONE_SECOND = np.timedelta64(1, "s") +ONE_SECOND = np.timedelta64(10**9, "ns") DataArrayOrDataset = TypeVar("DataArrayOrDataset", xr.DataArray, xr.Dataset)