Skip to content
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

Issue #437 Better documentation of CRS code usage, and slightly more … #462

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
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: 1 addition & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ openeo.util
-------------

.. automodule:: openeo.util
:members: to_bbox_dict, BBoxDict, load_json_resource, string_to_temporal_extent
:members: to_bbox_dict, BBoxDict, load_json_resource, normalize_crs, string_to_temporal_extent


openeo.processes
Expand Down
21 changes: 19 additions & 2 deletions openeo/rest/datacube.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def filter_bbox(
>>> cube.filter_bbox(west=3, south=51, east=4, north=52, crs=4326)

- With a (west, south, east, north) list or tuple
(note that EPSG:4326 is the default CRS, so it's not nececarry to specify it explicitly)::
(note that EPSG:4326 is the default CRS, so it's not necessary to specify it explicitly)::

>>> cube.filter_bbox([3, 51, 4, 52])
>>> cube.filter_bbox(bbox=[3, 51, 4, 52])
Expand All @@ -335,14 +335,27 @@ def filter_bbox(

- With a CRS other than EPSG 4326::

>>> cube.filter_bbox(west=652000, east=672000, north=5161000, south=5181000, crs=32632)
>>> cube.filter_bbox(
... west=652000, east=672000, north=5161000, south=5181000,
... crs=32632
... )

- Deprecated: positional arguments are also supported,
but follow a non-standard order for legacy reasons::

>>> west, east, north, south = 3, 4, 52, 51
>>> cube.filter_bbox(west, east, north, south)

:param crs: value that encodes a coordinate reference system, typically just an int (EPSG code) or string (authority string).

Integers always refer to the corresponding EPSG code.
A string that contains only an integer is interpreted as that same integer EPSG code.

You can also specify EPSG codes as an authority string, such as "EPSG:4326".
For example, the following string and int values all specify to the same CRS:
``"EPSG:4326"``, ``"4326"``, and the integer ``4326``.

.. seealso:: openEO Python Client documentation on openeo.util.normalize_crs :py:func:`openeo.util.normalize_crs` for the most up to date information.
"""
if args and any(k is not None for k in (west, south, east, north, bbox)):
raise ValueError("Don't mix positional arguments with keyword arguments.")
Expand Down Expand Up @@ -833,6 +846,10 @@ def _get_geometry_argument(
) -> Union[dict, Parameter, PGNode]:
"""
Convert input to a geometry as "geojson" subtype object.

:param crs: value that encodes a coordinate reference system, typically just an int (EPSG code) or string (authority string).
.. seealso:: openEO Python Client documentation on openeo.util.normalize_crs :py:func:`openeo.util.normalize_crs` for the most up to date information.

"""
if isinstance(geometry, (str, pathlib.Path)):
# Assumption: `geometry` is path to polygon is a path to vector file at backend.
Expand Down
28 changes: 24 additions & 4 deletions openeo/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,18 @@ class BBoxDict(dict):
Dictionary based helper to easily create/work with bounding box dictionaries
(having keys "west", "south", "east", "north", and optionally "crs").

crs:
value that encodes a coordinate reference system, typically just an int (EPSG code) or string (authority string).

Integers always refer to the corresponding EPSG code.
A string that contains only an integer is interpreted as that same integer EPSG code.

You can also specify EPSG codes as an authority string, such as "EPSG:4326".
For example, the following string and int values all specify to the same CRS:
``"EPSG:4326"``, ``"4326"``, and the integer ``4326``.

.. seealso:: openEO Python Client documentation on openeo.util.normalize_crs :py:func:`openeo.util.normalize_crs` for the most up to date information.

.. versionadded:: 0.10.1
"""

Expand Down Expand Up @@ -804,18 +816,26 @@ def normalize_crs(crs: Any, *, use_pyproj: bool = True) -> Union[None, int, str]
Behavior and data structure support depends on the availability of the ``pyproj`` library:

- If the ``pyproj`` library is available: use that to do parsing and conversion.
This means that anything that is supported by ``pyproj.CRS.from_user_input`` is allowed.
This means that everything supported by `pyproj.CRS.from_user_input <https://pyproj4.github.io/pyproj/dev/api/crs/crs.html#pyproj.crs.CRS.from_user_input>`_ is allowed.
See the ``pyproj`` docs for more details.
- Otherwise, some best effort validation is done:
EPSG looking int/str values will be parsed as such, other strings will be assumed to be WKT2 already.
Other data structures will not be accepted.

:param crs: data structure that encodes a CRS, typically just an int or string value.
If the ``pyproj`` library is available, everything supported by it is allowed
Integers always refer to the corresponding EPSG code.
A string that contains only an integer is interpreted as that same integer EPSG code.

You can also specify EPSG codes as an authority string, such as "EPSG:4326".
For example, the following string and int values all specify to the same CRS:
``"EPSG:4326"``, ``"4326"``, and the integer ``4326``.

:param crs: value that encodes a coordinate reference system, typically just an int (EPSG code) or string (authority string).
If the ``pyproj`` library is available, everything supported by it is allowed.

:param use_pyproj: whether ``pyproj`` should be leveraged at all
(mainly useful for testing the "no pyproj available" code path)

:return: EPSG code as int, or WKT2 string. Or None if input was empty .
: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.
Expand Down
52 changes: 52 additions & 0 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,21 @@ def test_init(self):
"crs": 4326,
}

@pytest.mark.skipif(
# TODO #460 this skip is only necessary for python 3.6 and lower
pyproj.__version__ < ComparableVersion("3.3.1"),
reason="pyproj below 3.3.1 does not support int-like strings",
)
def test_init_python_for_pyprojv331(self):
"""Extra test case that does not work with old pyproj versions that we get on python version 3.7 and below."""
assert BBoxDict(west=1, south=2, east=3, north=4, crs="4326") == {
"west": 1,
"south": 2,
"east": 3,
"north": 4,
"crs": 4326,
}

def test_repr(self):
d = BBoxDict(west=1, south=2, east=3, north=4)
assert repr(d) == "{'west': 1, 'south': 2, 'east': 3, 'north': 4}"
Expand All @@ -732,6 +747,21 @@ def test_to_bbox_dict_from_sequence(self):
"crs": 4326,
}

@pytest.mark.skipif(
# TODO #460 this skip is only necessary for python 3.6 and lower
pyproj.__version__ < ComparableVersion("3.3.1"),
reason="pyproj below 3.3.1 does not support int-like strings",
)
def test_to_bbox_dict_from_sequence_pyprojv331(self):
"""Extra test cases that do not work with old pyproj versions that we get on python version 3.7 and below."""
assert to_bbox_dict([1, 2, 3, 4], crs="4326") == {
"west": 1,
"south": 2,
"east": 3,
"north": 4,
"crs": 4326,
}

def test_to_bbox_dict_from_sequence_mismatch(self):
with pytest.raises(InvalidBBoxException, match="Expected sequence with 4 items, but got 3."):
to_bbox_dict([1, 2, 3])
Expand Down Expand Up @@ -775,6 +805,28 @@ def test_to_bbox_dict_from_dict(self):
}
) == {"west": 1, "south": 2, "east": 3, "north": 4, "crs": 4326}

@pytest.mark.skipif(
# TODO #460 this skip is only necessary for python 3.6 and lower
pyproj.__version__ < ComparableVersion("3.3.1"),
reason="pyproj below 3.3.1 does not support int-like strings",
)
def test_to_bbox_dict_from_dict_for_pyprojv331(self):
"""Extra test cases that do not work with old pyproj versions that we get on python version 3.7 and below."""
assert to_bbox_dict({"west": 1, "south": 2, "east": 3, "north": 4, "crs": "4326"}) == {
"west": 1,
"south": 2,
"east": 3,
"north": 4,
"crs": 4326,
}
assert to_bbox_dict({"west": 1, "south": 2, "east": 3, "north": 4}, crs="4326") == {
"west": 1,
"south": 2,
"east": 3,
"north": 4,
"crs": 4326,
}

def test_to_bbox_dict_from_dict_missing_field(self):
with pytest.raises(InvalidBBoxException, match=re.escape("Missing bbox fields ['north', 'south', 'west']")):
to_bbox_dict({"east": 3})
Expand Down