Skip to content

Commit

Permalink
BUG: Fix Derived Projected CRS support (#1222)
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 authored Dec 27, 2022
1 parent e9b5a60 commit 52cba95
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 8 deletions.
2 changes: 1 addition & 1 deletion docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Latest
- PERF: Optimize point transformations (pull #1204)
- REF: Raise error when :meth:`.CRS.to_wkt`, :meth:`.CRS.to_json`, or :meth:`.CRS.to_proj4` returns None (issue #1036)
- CLN: Remove `AzumuthalEquidistantConversion` & :class:`LambertAzumuthalEqualAreaConversion`. :class:`AzimuthalEquidistantConversion` & :class:`LambertAzimuthalEqualAreaConversion` should be used instead (pull #1219)

- BUG: Fix Derived Projected CRS support (issue #1182)

3.4.1
-----
Expand Down
20 changes: 18 additions & 2 deletions pyproj/_crs.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2332,6 +2332,8 @@ cdef dict _CRS_TYPE_MAP = {
PJ_TYPE_OTHER_CRS: "Other CRS",
}

IF (CTE_PROJ_VERSION_MAJOR, CTE_PROJ_VERSION_MINOR) >= (9, 2):
_CRS_TYPE_MAP[PJ_TYPE_DERIVED_PROJECTED_CRS] = "Derived Projected CRS"

cdef class _CRS(Base):
"""
Expand Down Expand Up @@ -2382,8 +2384,22 @@ cdef class _CRS(Base):
if self._type_name is not None:
return self._type_name
self._type_name = _CRS_TYPE_MAP[self._type]
if self.is_derived:
self._type_name = f"Derived {self._type_name}"
if not self.is_derived or self._type == PJ_TYPE_PROJECTED_CRS:
# Projected CRS are derived by definition
# https://github.com/OSGeo/PROJ/issues/3525#issuecomment-1365790999
return self._type_name

# Handle Derived Projected CRS
# https://github.com/OSGeo/PROJ/issues/3525#issuecomment-1366002289
IF (CTE_PROJ_VERSION_MAJOR, CTE_PROJ_VERSION_MINOR) < (9, 2):
if self._type == PJ_TYPE_OTHER_CRS:
self._type_name = "Derived Projected CRS"
return self._type_name
ELSE:
if self._type == PJ_TYPE_DERIVED_PROJECTED_CRS:
return self._type_name

self._type_name = f"Derived {self._type_name}"
return self._type_name

@property
Expand Down
18 changes: 16 additions & 2 deletions pyproj/database.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,23 @@ cdef dict _PJ_TYPE_MAP = {
PJType.CONCATENATED_OPERATION: PJ_TYPE_CONCATENATED_OPERATION,
PJType.OTHER_COORDINATE_OPERATION: PJ_TYPE_OTHER_COORDINATE_OPERATION,
}
IF (CTE_PROJ_VERSION_MAJOR, CTE_PROJ_VERSION_MINOR) >= (9, 2):
_PJ_TYPE_MAP[PJType.DERIVED_PROJECTED_CRS] = PJ_TYPE_DERIVED_PROJECTED_CRS

cdef dict _INV_PJ_TYPE_MAP = {value: key for key, value in _PJ_TYPE_MAP.items()}


cdef PJ_TYPE get_pj_type(pj_type) except *:
if not isinstance(pj_type, PJType):
pj_type = PJType.create(pj_type)
IF (CTE_PROJ_VERSION_MAJOR, CTE_PROJ_VERSION_MINOR) < (9, 2):
if pj_type is PJType.DERIVED_PROJECTED_CRS:
raise NotImplementedError(
"DERIVED_PROJECTED_CRS requires PROJ 9.2+"
)
return _PJ_TYPE_MAP[pj_type]


def get_authorities():
"""
.. versionadded:: 2.4.0
Expand Down Expand Up @@ -92,7 +106,7 @@ def get_codes(str auth_name not None, pj_type not None, bint allow_deprecated=Fa
Codes associated with authorities in PROJ database.
"""
cdef PJ_CONTEXT* context = NULL
cdef PJ_TYPE cpj_type = _PJ_TYPE_MAP[PJType.create(pj_type)]
cdef PJ_TYPE cpj_type = get_pj_type(pj_type)
cdef PROJ_STRING_LIST proj_code_list = NULL
try:
context = pyproj_context_create()
Expand Down Expand Up @@ -210,7 +224,7 @@ def query_crs_info(
pj_type_count * sizeof(PJ_TYPE)
)
for iii in range(pj_type_count):
pj_type_list[iii] = _PJ_TYPE_MAP[PJType.create(pj_types[iii])]
pj_type_list[iii] = get_pj_type(pj_types[iii])

context = pyproj_context_create()
query_params = proj_get_crs_list_parameters_create()
Expand Down
1 change: 1 addition & 0 deletions pyproj/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ class PJType(BaseEnum):
GEOGRAPHIC_3D_CRS = "GEOGRAPHIC_3D_CRS"
VERTICAL_CRS = "VERTICAL_CRS"
PROJECTED_CRS = "PROJECTED_CRS"
DERIVED_PROJECTED_CRS = "DERIVED_PROJECTED_CRS"
COMPOUND_CRS = "COMPOUND_CRS"
TEMPORAL_CRS = "TEMPORAL_CRS"
ENGINEERING_CRS = "ENGINEERING_CRS"
Expand Down
1 change: 1 addition & 0 deletions pyproj/proj.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ cdef extern from "proj.h" nogil:
PJ_TYPE_TEMPORAL_DATUM
PJ_TYPE_ENGINEERING_DATUM
PJ_TYPE_PARAMETRIC_DATUM
PJ_TYPE_DERIVED_PROJECTED_CRS

PJ_TYPE proj_get_type(const PJ *obj)
const char* proj_get_name(const PJ *obj)
Expand Down
48 changes: 47 additions & 1 deletion test/crs/test_crs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1235,7 +1235,7 @@ def test_from_authority__ignf():

def test_ignf_authority_repr():
assert repr(CRS.from_authority("IGNF", "ETRS89UTM28")).startswith(
"<Derived Projected CRS: IGNF:ETRS89UTM28>"
"<Projected CRS: IGNF:ETRS89UTM28>"
)


Expand Down Expand Up @@ -1270,6 +1270,52 @@ def test_crs_is_exact_same__non_crs_input():
assert not CRS(4326).is_exact_same("+init=epsg:4326")


def test_derived_projected_crs():
wkt = (
'DERIVEDPROJCRS["derived projectedCRS",\n'
' BASEPROJCRS["WGS 84 / UTM zone 31N",\n'
' BASEGEOGCRS["WGS 84",\n'
' DATUM["World Geodetic System 1984",\n'
' ELLIPSOID["WGS 84",6378137,298.257223563,\n'
' LENGTHUNIT["metre",1]]],\n'
' PRIMEM["Greenwich",0,\n'
' ANGLEUNIT["degree",0.0174532925199433]]],\n'
' CONVERSION["UTM zone 31N",\n'
' METHOD["Transverse Mercator",\n'
' ID["EPSG",9807]],\n'
' PARAMETER["Latitude of natural origin",0,\n'
' ANGLEUNIT["degree",0.0174532925199433],\n'
' ID["EPSG",8801]],\n'
' PARAMETER["Longitude of natural origin",3,\n'
' ANGLEUNIT["degree",0.0174532925199433],\n'
' ID["EPSG",8802]],\n'
' PARAMETER["Scale factor at natural origin",0.9996,\n'
' SCALEUNIT["unity",1],\n'
' ID["EPSG",8805]],\n'
' PARAMETER["False easting",500000,\n'
' LENGTHUNIT["metre",1],\n'
' ID["EPSG",8806]],\n'
' PARAMETER["False northing",0,\n'
' LENGTHUNIT["metre",1],\n'
' ID["EPSG",8807]]]],\n'
' DERIVINGCONVERSION["unnamed",\n'
' METHOD["PROJ unimplemented"],\n'
' PARAMETER["foo",1.0,UNIT["metre",1]]],\n'
" CS[Cartesian,2],\n"
' AXIS["(E)",east,\n'
" ORDER[1],\n"
' LENGTHUNIT["metre",1,\n'
' ID["EPSG",9001]]],\n'
' AXIS["(N)",north,\n'
" ORDER[2],\n"
' LENGTHUNIT["metre",1,\n'
' ID["EPSG",9001]]]]'
)
crs = CRS(wkt)
assert crs.is_derived
assert crs.type_name == "Derived Projected CRS"


def test_to_string__no_auth():
proj = CRS("+proj=latlong +ellps=GRS80 +towgs84=-199.87,74.79,246.62")
assert (
Expand Down
4 changes: 2 additions & 2 deletions test/crs/test_crs_maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_make_projected_crs(tmp_path):
aeaop = AlbersEqualAreaConversion(0, 0)
pc = ProjectedCRS(conversion=aeaop, name="Albers")
assert pc.name == "Albers"
assert pc.type_name == "Derived Projected CRS"
assert pc.type_name == "Projected CRS"
assert pc.coordinate_operation == aeaop
assert_can_pickle(pc, tmp_path)

Expand Down Expand Up @@ -243,7 +243,7 @@ def test_compund_crs(tmp_path):
)
assert compcrs.name == "NAD83 / Pennsylvania South + NAVD88 height"
assert compcrs.type_name == "Compound CRS"
assert compcrs.sub_crs_list[0].type_name == "Derived Projected CRS"
assert compcrs.sub_crs_list[0].type_name == "Projected CRS"
assert compcrs.sub_crs_list[1].type_name == "Vertical CRS"
assert_can_pickle(compcrs, tmp_path)

Expand Down
21 changes: 21 additions & 0 deletions test/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
query_utm_crs_info,
)
from pyproj.enums import PJType
from test.conftest import PROJ_GTE_92


def test_backwards_compatible_import_paths():
Expand Down Expand Up @@ -109,6 +110,16 @@ def test_get_codes__empty(auth, pj_type):
assert not get_codes(auth, pj_type)


def test_get_codes__derived_projected_crs():
if PROJ_GTE_92:
assert not get_codes("EPSG", PJType.DERIVED_PROJECTED_CRS)
else:
with pytest.raises(
NotImplementedError, match="DERIVED_PROJECTED_CRS requires PROJ 9.2+"
):
get_codes("EPSG", PJType.DERIVED_PROJECTED_CRS)


def test_get_codes__invalid_auth():
with pytest.raises(TypeError):
get_codes(123, PJType.BOUND_CRS)
Expand Down Expand Up @@ -140,6 +151,16 @@ def test_query_crs_info(auth, pj_type, deprecated):
assert not any_deprecated


def test_query_crs_info__derived_projected_crs():
if PROJ_GTE_92:
assert not query_crs_info(pj_types=PJType.DERIVED_PROJECTED_CRS)
else:
with pytest.raises(
NotImplementedError, match="DERIVED_PROJECTED_CRS requires PROJ 9.2+"
):
query_crs_info(pj_types=PJType.DERIVED_PROJECTED_CRS)


@pytest.mark.parametrize(
"auth, pj_type",
[
Expand Down

0 comments on commit 52cba95

Please sign in to comment.