Skip to content

#259 normalize "EPSG:123"-style CRS to integer #754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- In bounding box `spatial_extent`s of `load_collection`, `load_stac`, ...: normalize non-standard "EPSG:123"-style CRS strings to proper integer value ([#259](https://github.com/Open-EO/openeo-python-client/issues/259), [Open-EO/openeo-processes#391](https://github.com/Open-EO/openeo-processes/issues/391))

### Removed

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -3043,6 +3043,8 @@ def _get_geometry_argument(
and isinstance(argument, dict)
and all(k in argument for k in ["west", "south", "east", "north"])
):
if "crs" in argument:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just use to_bbox_dict (or related) here instead of reinventing that wheel?

argument = dict(argument, crs=normalize_crs(argument["crs"], warn_on_change=True))
return argument

# Support URL based geometry references (with `load_url` and best-effort format guess)
Expand Down
14 changes: 10 additions & 4 deletions openeo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from __future__ import annotations

import copy
import datetime as dt
import functools
import json
Expand Down Expand Up @@ -626,7 +627,7 @@ def get(self, fraction: float) -> str:
return f"{self.left}{bar:{self.fill}<{width}s}{self.right}"


def normalize_crs(crs: Any, *, use_pyproj: bool = True) -> Union[None, int, str]:
def normalize_crs(crs: Any, *, use_pyproj: bool = True, warn_on_change: bool = False) -> Union[None, int, str]:
"""
Normalize the given value (describing a CRS or Coordinate Reference System)
to an openEO compatible EPSG code (int) or WKT2 CRS string.
Expand Down Expand Up @@ -654,16 +655,18 @@ def normalize_crs(crs: Any, *, use_pyproj: bool = True) -> Union[None, int, str]
:param use_pyproj: whether ``pyproj`` should be leveraged at all
(mainly useful for testing the "no pyproj available" code path)

:param warn_on_change: whether to emit a warning when the normalization involves a change in value.

:return: EPSG code as int, or WKT2 string. Or None if input was empty.

:raises ValueError:
When the given CRS data can not be parsed/converted/normalized.

"""
orig_crs = copy.deepcopy(crs)
if crs in (None, "", {}):
return None

if pyproj and use_pyproj:
crs = None
elif pyproj and use_pyproj:
try:
# (if available:) let pyproj do the validation/parsing
crs_obj = pyproj.CRS.from_user_input(crs)
Expand All @@ -689,4 +692,7 @@ def normalize_crs(crs: Any, *, use_pyproj: bool = True) -> Union[None, int, str]
else:
raise ValueError(f"Can not normalize CRS data {type(crs)}")

if warn_on_change and orig_crs != crs:
logger.warning(f"Normalized CRS {orig_crs!r} to {crs!r}")

return crs
43 changes: 43 additions & 0 deletions tests/rest/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from contextlib import nullcontext
from pathlib import Path

import dirty_equals
import pytest
import requests
import requests_mock
Expand Down Expand Up @@ -2703,6 +2704,31 @@ def test_load_collection_spatial_extent_vector_cube(self, dummy_backend):
},
}

@pytest.mark.parametrize("crs", ["EPSG:4326", "epsg:4326", 4326, "4326"])
def test_load_collection_normalize_epsg_crs(self, dummy_backend, crs, caplog):
caplog.set_level(level=logging.WARNING)
spatial_extent = {"west": 1, "south": 2, "east": 3, "north": 4, "crs": crs}
temporal_extent = ["2019-01-01", "2019-01-22"]
cube = dummy_backend.connection.load_collection(
"S2", spatial_extent=spatial_extent, temporal_extent=temporal_extent, bands=["B2", "B3"]
)
cube.execute()
assert dummy_backend.get_sync_pg() == {
"loadcollection1": {
"process_id": "load_collection",
"arguments": {
"id": "S2",
"spatial_extent": {"east": 3, "north": 4, "south": 2, "west": 1, "crs": 4326},
"temporal_extent": ["2019-01-01", "2019-01-22"],
"bands": ["B2", "B3"],
},
"result": True,
}
}
if crs != 4326:
assert f"Normalized CRS {crs!r} to 4326" in caplog.text
else:
assert "Normalized CRS" not in caplog.text

def test_load_result(requests_mock):
requests_mock.get(API_URL, json={"api_version": "1.0.0"})
Expand Down Expand Up @@ -3026,6 +3052,23 @@ def test_load_stac_spatial_extent_bbox(self, dummy_backend):
"spatial_extent": {"west": 1, "south": 2, "east": 3, "north": 4},
}

@pytest.mark.parametrize("crs", ["EPSG:32632", "epsg:32632", 32632, "32632"])
def test_load_stac_spatial_extent_bbox_normalize_epsg_crs(self, dummy_backend, crs, caplog):
caplog.set_level(level=logging.WARNING)
spatial_extent = {"west": 1, "south": 2, "east": 3, "north": 4, "crs": crs}
# TODO #694 how to avoid request to dummy STAC URL (without mocking, which is overkill for this test)
cube = dummy_backend.connection.load_stac("https://stac.test/data", spatial_extent=spatial_extent)
cube.execute()
assert dummy_backend.get_sync_pg()["loadstac1"]["arguments"] == {
"url": "https://stac.test/data",
"spatial_extent": {"west": 1, "south": 2, "east": 3, "north": 4, "crs": 32632},
}

if crs != 32632:
assert f"Normalized CRS {crs!r} to 32632" in caplog.text
else:
assert "Normalized CRS" not in caplog.text

@pytest.mark.parametrize(
"spatial_extent",
[
Expand Down