diff --git a/pyproj/_crs.pyx b/pyproj/_crs.pyx index 73d75f0a1..c42394c30 100644 --- a/pyproj/_crs.pyx +++ b/pyproj/_crs.pyx @@ -5,9 +5,18 @@ from collections import OrderedDict from pyproj._datadir cimport pyproj_context_initialize from pyproj.compat import cstrencode, pystrdecode +from pyproj.crs.ellipsoid import CustomEllipsoid from pyproj.crs.enums import CoordinateOperationType, DatumType from pyproj.enums import ProjVersion, WktVersion from pyproj.exceptions import CRSError +from pyproj.geod import pj_ellps + + +# This is for looking up the ellipsoid parameters +# based on the long name +_PJ_ELLPS_NAME_MAP = { + ellps["description"]: ellps_id for ellps_id, ellps in pj_ellps.items() +} cdef cstrdecode(const char *instring): @@ -911,25 +920,22 @@ cdef class Ellipsoid(_CRSParts): return Ellipsoid.from_json_dict(_load_proj_json(ellipsoid_json_str)) @staticmethod - def from_name( + def _from_name( ellipsoid_name, - auth_name=None, + auth_name, ): """ .. versionadded:: 2.5.0 Create a Ellipsoid from a name. - Examples: - - WGS 84 - Parameters ---------- ellipsoid_name: str Ellipsoid name. - auth_name: str, optional + auth_name: str The authority name to refine search (e.g. 'EPSG'). - If None, will search all authorities. Default is None. + If None, will search all authorities. Returns ------- @@ -954,6 +960,56 @@ cdef class Ellipsoid(_CRSParts): return Ellipsoid.create(context, ellipsoid_pj) + @staticmethod + def from_name( + ellipsoid_name, + auth_name=None, + ): + """ + .. versionadded:: 2.5.0 + + Create a Ellipsoid from a name. + + Examples: + - WGS 84 + + Parameters + ---------- + ellipsoid_name: str + Ellipsoid name. + auth_name: str, optional + The authority name to refine search (e.g. 'EPSG'). + If None, will search all authorities. Default is None. + + Returns + ------- + Ellipsoid + """ + try: + return Ellipsoid._from_name( + ellipsoid_name=ellipsoid_name, + auth_name=auth_name, + ) + except CRSError: + if auth_name not in ("PROJ", None): + raise + pass + + # add support for past names for PROJ ellipsoids + try: + ellipsoid_params = pj_ellps[ + _PJ_ELLPS_NAME_MAP.get(ellipsoid_name, ellipsoid_name) + ] + except KeyError: + raise CRSError("Invalid ellipsoid name: {}".format(ellipsoid_name)) + return CustomEllipsoid( + name=ellipsoid_params["description"], + semi_major_axis=ellipsoid_params["a"], + semi_minor_axis=ellipsoid_params.get("b"), + inverse_flattening=ellipsoid_params.get("rf"), + ) + + cdef class PrimeMeridian(_CRSParts): """ .. versionadded:: 2.0.0 @@ -1386,43 +1442,33 @@ cdef class Datum(_CRSParts): return Datum.from_name(datum_string) @staticmethod - def from_name( + def _from_name( datum_name, - auth_name=None, - datum_type=DatumType.GEODETIC_REFERENCE_FRAME, + auth_name, + datum_type, ): """ .. versionadded:: 2.5.0 Create a Datum from a name. - Examples: - - WGS 84 - - World Geodetic System 1984 - Parameters ---------- datum_name: str Datum name. - auth_name: str, optional + auth_name: str The authority name to refine search (e.g. 'EPSG'). - If None, will search all authorities. Default is None. + If None, will search all authorities. datum_type: DatumType, optional - The datum type to create. Default is DatumType.GEODETIC_REFERENCE_FRAME. + The datum type to create. Returns ------- Datum """ - pj_datum_type = _PJ_DATUM_TYPE_MAP[DatumType.create(datum_type)] + pj_datum_type = _PJ_DATUM_TYPE_MAP[datum_type] cdef PJ_CONTEXT* context = proj_context_create() pyproj_context_initialize(context, True) - # ensure the PROJ string name matches properly - # https://github.com/OSGeo/PROJ/issues/1823 - datum_name = _DATUM_NAME_MAP.get( - datum_name.upper().replace(" ", ""), - datum_name, - ) cdef PJ* datum_pj = _from_name( context, datum_name, @@ -1439,6 +1485,63 @@ cdef class Datum(_CRSParts): CRSError.clear() return Datum.create(context, datum_pj) + @staticmethod + def from_name( + datum_name, + auth_name=None, + datum_type=DatumType.GEODETIC_REFERENCE_FRAME, + ): + """ + .. versionadded:: 2.5.0 + + Create a Datum from a name. + + Examples: + - WGS 84 + - World Geodetic System 1984 + + Parameters + ---------- + datum_name: str + Datum name. + auth_name: str, optional + The authority name to refine search (e.g. 'EPSG'). + If None, will search all authorities. Default is None. + datum_type: DatumType, optional + The datum type to create. Default is DatumType.GEODETIC_REFERENCE_FRAME. + + Returns + ------- + Datum + """ + datum_type = DatumType.create(datum_type) + try: + return Datum._from_name( + datum_name=datum_name, + auth_name=auth_name, + datum_type=datum_type, + ) + except CRSError: + if ( + auth_name not in ("PROJ", None) + or datum_type!=DatumType.GEODETIC_REFERENCE_FRAME + ): + raise + pass + # add support for PROJ datum aliases + # https://github.com/OSGeo/PROJ/issues/1823 + try: + datum_name=_DATUM_NAME_MAP[datum_name.upper().replace(" ", "")] + except KeyError: + raise CRSError( + "Invalid datum name: {}".format(datum_name) + ) + return Datum.from_name( + datum_name=datum_name, + auth_name=auth_name, + datum_type=datum_type, + ) + @staticmethod def from_json_dict(datum_dict): """ diff --git a/pyproj/crs/ellipsoid.py b/pyproj/crs/ellipsoid.py index d020f25ea..4e25e0b76 100644 --- a/pyproj/crs/ellipsoid.py +++ b/pyproj/crs/ellipsoid.py @@ -10,6 +10,7 @@ class CustomEllipsoid(Ellipsoid): def __new__( cls, + name="undefined", semi_major_axis=None, inverse_flattening=None, semi_minor_axis=None, @@ -18,6 +19,8 @@ def __new__( """ Parameters ---------- + name: str, optional + Name of the ellipsoid. Default is 'undefined'. semi_major_axis: float, optional The semi major axis in meters. Required if missing radius. inverse_flattening: float, optional @@ -33,7 +36,7 @@ def __new__( ellipsoid_json = { "$schema": "https://proj.org/schemas/v0.2/projjson.schema.json", "type": "Ellipsoid", - "name": "undefined", + "name": name, } if semi_major_axis is not None: ellipsoid_json["semi_major_axis"] = semi_major_axis diff --git a/test/crs/test_crs.py b/test/crs/test_crs.py index b68342cc6..6fc5bc238 100644 --- a/test/crs/test_crs.py +++ b/test/crs/test_crs.py @@ -848,10 +848,15 @@ def test_datum_from_name__auth_type(auth_name): "invalid_str", ["3-598y5-98y", "urn:ogc:def:ellipsoid:EPSG::7001"] ) def test_datum__from_name__invalid(invalid_str): - with pytest.raises(CRSError, match="Invalid datum"): + with pytest.raises(CRSError, match="Invalid datum name:"): Datum.from_name(invalid_str) +def test_datum__from_name__invalid_type(): + with pytest.raises(CRSError, match="Invalid datum name: WGS84"): + Datum.from_name("WGS84", datum_type="VERTICAL_REFERENCE_FRAME") + + @pytest.mark.parametrize( "invalid_str", ["3-598y5-98y", "urn:ogc:def:ellipsoid:EPSG::7001"] ) @@ -866,9 +871,17 @@ def test_ellipsoid__from_string(input_str): assert ee.name == "Airy 1830" -def test_ellipsoid__from_name(): - ee = Ellipsoid.from_name("Airy 1830") - assert ee.name == "Airy 1830" +@pytest.mark.parametrize( + "input_str,long_name", + [ + ("Airy 1830", "Airy 1830"), + ("intl", "International 1909 (Hayford)"), + ("International 1909 (Hayford)", "International 1909 (Hayford)"), + ], +) +def test_ellipsoid__from_name(input_str, long_name): + ee = Ellipsoid.from_name(input_str) + assert ee.name == long_name @pytest.mark.parametrize("invalid_str", ["3-598y5-98y", "urn:ogc:def:datum:EPSG::6326"]) @@ -877,6 +890,11 @@ def test_ellipsoid__from_name__invalid(invalid_str): Ellipsoid.from_name(invalid_str) +def test_ellipsoid__from_name__invalid__auth(): + with pytest.raises(CRSError, match="Invalid ellipsoid"): + Ellipsoid.from_name("intl", auth_name="ESRI") + + @pytest.mark.parametrize("invalid_str", ["3-598y5-98y", "urn:ogc:def:datum:EPSG::6326"]) def test_ellipsoid__from_string__invalid(invalid_str): with pytest.raises(CRSError, match="Invalid ellipsoid"): diff --git a/test/crs/test_crs_ellipsoid.py b/test/crs/test_crs_ellipsoid.py index 91d125c0b..24cdc7311 100644 --- a/test/crs/test_crs_ellipsoid.py +++ b/test/crs/test_crs_ellipsoid.py @@ -19,8 +19,10 @@ def test_custom_ellipsoid(): def test_custom_ellipsoid__minor(): - ce = CustomEllipsoid(semi_major_axis=6378137, semi_minor_axis=6356752.314) - assert ce.name == "undefined" + ce = CustomEllipsoid( + name="test", semi_major_axis=6378137, semi_minor_axis=6356752.314 + ) + assert ce.name == "test" assert ce.semi_major_metre == 6378137 assert ce.semi_minor_metre == 6356752.314 assert_almost_equal(ce.inverse_flattening, 298.25722014)