From 0b9cb2e57e1a5d35fd123ee81079557332333ba7 Mon Sep 17 00:00:00 2001 From: Javier Jimenez Shaw Date: Sat, 30 Mar 2024 01:58:20 +0100 Subject: [PATCH 1/3] Add query_geodetic_crs_from_datum --- docs/api/database.rst | 6 +++ docs/history.rst | 1 + pyproj/database.pyi | 7 +++ pyproj/database.pyx | 103 +++++++++++++++++++++++++++++++++++++++++- pyproj/proj.pxi | 2 + test/test_database.py | 41 +++++++++++++++++ 6 files changed, 159 insertions(+), 1 deletion(-) diff --git a/docs/api/database.rst b/docs/api/database.rst index 80cfab7e3..bce40a89b 100644 --- a/docs/api/database.rst +++ b/docs/api/database.rst @@ -49,3 +49,9 @@ pyproj.database.get_database_metadata --------------------------------------- .. autofunction:: pyproj.database.get_database_metadata + + +pyproj.database.query_geodetic_crs_from_datum +--------------------------------------- + +.. autofunction:: pyproj.database.query_geodetic_crs_from_datum diff --git a/docs/history.rst b/docs/history.rst index 5f17bec7d..53b1a045f 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -7,6 +7,7 @@ Latest - DEP: Minimum supported Python version 3.10 (pull #1357) - DEP: Minimum PROJ version 9.2 (pull #1394) - ENH: Add :meth:`CRS.is_deprecated` and :meth:`CRS.get_non_deprecated` (pull #1383) +- ENH: Add :meth:`database.query_geodetic_crs_from_datum` (pull #1390) 3.6.1 ------ diff --git a/pyproj/database.pyi b/pyproj/database.pyi index 61bd7c040..f49024047 100644 --- a/pyproj/database.pyi +++ b/pyproj/database.pyi @@ -1,6 +1,7 @@ from typing import NamedTuple from pyproj.aoi import AreaOfInterest, AreaOfUse +from pyproj.crs import CRS from pyproj.enums import PJType class Unit(NamedTuple): @@ -44,3 +45,9 @@ def query_utm_crs_info( contains: bool = False, ) -> list[CRSInfo]: ... def get_database_metadata(key: str) -> str | None: ... +def query_geodetic_crs_from_datum( + crs_auth_name: str | None, + datum_auth_name: str, + datum_code: str, + pj_type: PJType | None = None, +) -> list[CRS]: ... diff --git a/pyproj/database.pyx b/pyproj/database.pyx index 637b7d5c9..3af1a80e6 100644 --- a/pyproj/database.pyx +++ b/pyproj/database.pyx @@ -6,9 +6,14 @@ from collections import namedtuple from libc.stdlib cimport free, malloc from pyproj._compat cimport cstrdecode, cstrencode -from pyproj._datadir cimport pyproj_context_create, pyproj_context_destroy +from pyproj._datadir cimport ( + _clear_proj_error, + pyproj_context_create, + pyproj_context_destroy, +) from pyproj.aoi import AreaOfUse +from pyproj.crs import CRS from pyproj.enums import PJType @@ -477,3 +482,99 @@ def get_database_metadata(str key not None): return metadata finally: pyproj_context_destroy(context) + + +def query_geodetic_crs_from_datum( + str crs_auth_name, + str datum_auth_name not None, + str datum_code not None, + pj_type=None + ): + """ + .. versionadded:: 3.7.0 + + Return GeodeticCRS that use the specified datum + + See: :c:func:`proj_query_geodetic_crs_from_datum` + + Parameters + ---------- + crs_auth_name: str | None + The authority name to filter by (e.g. EPSG, ESRI). None is all. + datum_auth_name: str + The authority of the datum + datum_code: str + Datum code + pj_type: pyproj.enums.PJType | None, optional + The type of object to get the CRSs. Can be PJType.GEOCENTRIC_CRS, + PJType.GEOGRAPHIC_3D_CRS, PJType.GEOGRAPHIC_2D_CRS or None for all. + + Returns + ------- + list[CRS] + """ + + cdef const char* c_crs_type = NULL + if pj_type is None: + pass + elif pj_type is PJType.GEOCENTRIC_CRS: + c_crs_type = b"geocentric" + elif pj_type is PJType.GEOGRAPHIC_2D_CRS: + c_crs_type = b"geographic 2D" + elif pj_type is PJType.GEOGRAPHIC_3D_CRS: + c_crs_type = b"geographic 3D" + else: + raise ValueError("type must be GEOCENTRIC_CRS, GEOGRAPHIC_2D_CRS, GEOGRAPHIC_3D_CRS or None") + + cdef const char* c_crs_auth_name = NULL + cdef const char* c_datum_auth_name = NULL + cdef const char* c_datum_code = NULL + cdef bytes b_crs_auth_name + cdef bytes b_datum_auth_name + cdef bytes b_datum_code + + if crs_auth_name is not None: + b_crs_auth_name = cstrencode(crs_auth_name) + c_crs_auth_name = b_crs_auth_name + + if datum_auth_name is not None: + b_datum_auth_name = cstrencode(datum_auth_name) + c_datum_auth_name = b_datum_auth_name + + if datum_code is not None: + b_datum_code = cstrencode(datum_code) + c_datum_code = b_datum_code + + ret_list = [] + + cdef PJ_OBJ_LIST *proj_list = NULL + cdef int num_proj_objects = 0 + + cdef PJ_CONTEXT* context = pyproj_context_create() + proj_list = proj_query_geodetic_crs_from_datum( + context, + c_crs_auth_name, + c_datum_auth_name, + c_datum_code, + c_crs_type + ) + + if proj_list != NULL: + num_proj_objects = proj_list_get_count(proj_list) + + cdef PJ* proj = NULL + try: + for iii in range(num_proj_objects): + proj = proj_list_get(context, proj_list, iii) + ret_list.append(CRS(proj_as_wkt(context, proj, PJ_WKT2_2019, NULL))) + proj_destroy(proj) + proj = NULL + finally: + # If there was an error we have to call proj_destroy + # If there was none, calling it on NULL does nothing + proj_destroy(proj) + proj_list_destroy(proj_list) + pyproj_context_destroy(context) + _clear_proj_error() + + return ret_list diff --git a/pyproj/proj.pxi b/pyproj/proj.pxi index cc42ad42b..c00291de4 100644 --- a/pyproj/proj.pxi +++ b/pyproj/proj.pxi @@ -550,3 +550,5 @@ cdef extern from "proj.h" nogil: int proj_is_deprecated(const PJ *obj) PJ_OBJ_LIST *proj_get_non_deprecated(PJ_CONTEXT *ctx, const PJ *obj) + + PJ_OBJ_LIST *proj_query_geodetic_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_auth_name, const char *datum_auth_name, const char *datum_code, const char *crs_type) diff --git a/test/test_database.py b/test/test_database.py index 6b2f1628e..cc47e7678 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -8,6 +8,7 @@ get_database_metadata, get_units_map, query_crs_info, + query_geodetic_crs_from_datum, query_utm_crs_info, ) from pyproj.enums import PJType @@ -268,3 +269,43 @@ def test_get_database_metadata(): def test_get_database_metadata__invalid(): assert get_database_metadata("doesnotexist") is None + + +def test_query_geodetic_crs_from_datum(): + crss = query_geodetic_crs_from_datum("EPSG", "EPSG", "1116", PJType.GEOCENTRIC_CRS) + assert len(crss) == 1 + assert crss[0].to_authority()[1] == "6317" + + crss = query_geodetic_crs_from_datum(None, "EPSG", "1116") + assert len(crss) == 3 + codes = [x.to_authority()[1] for x in crss] + assert "6317" in codes + assert "6318" in codes + assert "6319" in codes + + crss = query_geodetic_crs_from_datum("EPSG", "EPSG", "6269", None) + assert len(crss) == 1 + assert crss[0].to_authority()[1] == "4269" + + crss = query_geodetic_crs_from_datum(None, "EPSG", "6269") + assert len(crss) == 3 # EPSG, ESRI, OGC + + +def test_query_geodetic_crs_from_datum_invalid(): + crss = query_geodetic_crs_from_datum(None, "EPSG", "11") + assert len(crss) == 0 + + crss = query_geodetic_crs_from_datum(None, "EPSG", "32632") + assert len(crss) == 0 + + crss = query_geodetic_crs_from_datum("foo-bar", "EPSG", "6269", None) + assert len(crss) == 0 + + with pytest.raises(ValueError): + query_geodetic_crs_from_datum("EPSG", "EPSG", "1116", PJType.PROJECTED_CRS) + + with pytest.raises(TypeError): + query_geodetic_crs_from_datum("EPSG", "EPSG", None) + + with pytest.raises(TypeError): + query_geodetic_crs_from_datum("EPSG", None, "1116") From 7b5cb5cc91d7a2fd84907b6ae65c93cedd7a0a1e Mon Sep 17 00:00:00 2001 From: Javier Jimenez Shaw Date: Fri, 5 Apr 2024 14:29:19 +0200 Subject: [PATCH 2/3] convert from str to PJType --- pyproj/database.pyx | 3 +++ test/test_database.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/pyproj/database.pyx b/pyproj/database.pyx index 3af1a80e6..f8c0bd07f 100644 --- a/pyproj/database.pyx +++ b/pyproj/database.pyx @@ -514,6 +514,9 @@ def query_geodetic_crs_from_datum( list[CRS] """ + if pj_type is not None and not isinstance(pj_type, PJType): + pj_type = PJType.create(pj_type) + cdef const char* c_crs_type = NULL if pj_type is None: pass diff --git a/test/test_database.py b/test/test_database.py index cc47e7678..3f00f5df0 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -272,6 +272,10 @@ def test_get_database_metadata__invalid(): def test_query_geodetic_crs_from_datum(): + crss = query_geodetic_crs_from_datum("EPSG", "EPSG", "1116", "GEOCENTRIC_CRS") + assert len(crss) == 1 + assert crss[0].to_authority()[1] == "6317" + crss = query_geodetic_crs_from_datum("EPSG", "EPSG", "1116", PJType.GEOCENTRIC_CRS) assert len(crss) == 1 assert crss[0].to_authority()[1] == "6317" @@ -304,6 +308,9 @@ def test_query_geodetic_crs_from_datum_invalid(): with pytest.raises(ValueError): query_geodetic_crs_from_datum("EPSG", "EPSG", "1116", PJType.PROJECTED_CRS) + with pytest.raises(ValueError): + query_geodetic_crs_from_datum("EPSG", "EPSG", "1116", "invalid string") + with pytest.raises(TypeError): query_geodetic_crs_from_datum("EPSG", "EPSG", None) From e283198a288ff848e8cea705cbe8a87a17f79bb4 Mon Sep 17 00:00:00 2001 From: Javier Jimenez Shaw Date: Sat, 20 Apr 2024 09:30:31 +0200 Subject: [PATCH 3/3] trying to fix CI --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index bb4e262f5..51599d1f1 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -124,7 +124,7 @@ jobs: python-version: ['3.10', '3.11', '3.12'] python-implementation: [python] proj-version: ['*'] - include: + # include: # DISABLED UNTIL CONDA-FORGE PYPY SUPPORTS PYTHON 3.10+ # - os: ubuntu-latest # python-version: '*'