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

ENH: Add source/target CRS to Transformer #976

Merged
merged 1 commit into from Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/history.rst
Expand Up @@ -6,8 +6,9 @@ Latest
- DEP: Minimum supported Python version 3.8 (issue #930)
- DEP: Minimum PROJ version 8.0 (issue #940)
- BUG: Prepend "Derived" to CRS type name if CRS is derived (issue #932)
- BUG: Improved handling of inf values in :attr:`pyproj.transformer.Transformer.transform_bounds` (pull #961)
- ENH: Add support for transforming bounds at the poles in :attr:`pyproj.transformer.Transformer.transform_bounds` (pull #962)
- BUG: Improved handling of inf values in :meth:`pyproj.transformer.Transformer.transform_bounds` (pull #961)
- ENH: Add support for transforming bounds at the poles in :meth:`pyproj.transformer.Transformer.transform_bounds` (pull #962)
- ENH: Added :attr:`pyproj.transformer.Transformer.source_crs` & :attr:`pyproj.transformer.Transformer.target_crs` (pull #976)

3.2.1
------
Expand Down
10 changes: 10 additions & 0 deletions pyproj/_crs.pxd
@@ -1,5 +1,9 @@
include "proj.pxi"


from pyproj.enums import WktVersion


cdef extern from "proj_experimental.h":
PJ *proj_crs_promote_to_3D(PJ_CONTEXT *ctx,
const char* crs_3D_name,
Expand All @@ -13,6 +17,12 @@ cdef _to_proj4(
object version,
bint pretty,
)
cdef _to_wkt(
PJ_CONTEXT* context,
PJ* projobj,
object version,
bint pretty,
)

cdef class Axis:
cdef readonly str name
Expand Down
47 changes: 35 additions & 12 deletions pyproj/_crs.pyx
Expand Up @@ -69,8 +69,8 @@ def is_proj(str proj_string not None):
cdef _to_wkt(
PJ_CONTEXT* context,
PJ* projobj,
object version=WktVersion.WKT2_2019,
bint pretty=False
object version,
bint pretty,
):
"""
Convert a PJ object to a wkt string.
Expand Down Expand Up @@ -2557,12 +2557,10 @@ cdef class _CRS(Base):
@property
def source_crs(self):
"""
The base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS,
or the source CRS of a CoordinateOperation.

Returns
-------
_CRS
_CRS:
The base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS.
"""
if self._source_crs is not None:
return None if self._source_crs is False else self._source_crs
Expand All @@ -2572,7 +2570,12 @@ cdef class _CRS(Base):
self._source_crs = False
return None
try:
self._source_crs = _CRS(_to_wkt(self.context, projobj))
self._source_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return self._source_crs
Expand All @@ -2585,7 +2588,7 @@ cdef class _CRS(Base):
Returns
-------
_CRS:
The hub CRS of a BoundCRS or the target CRS of a CoordinateOperation.
The hub CRS of a BoundCRS.
"""
if self._target_crs is not None:
return None if self._target_crs is False else self._target_crs
Expand All @@ -2595,7 +2598,12 @@ cdef class _CRS(Base):
self._target_crs = False
return None
try:
self._target_crs = _CRS(_to_wkt(self.context, projobj))
self._target_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return self._target_crs
Expand Down Expand Up @@ -2624,7 +2632,12 @@ cdef class _CRS(Base):
self._sub_crs_list = []
while projobj != NULL:
try:
self._sub_crs_list.append(_CRS(_to_wkt(self.context, projobj)))
self._sub_crs_list.append(_CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
)))
finally:
proj_destroy(projobj) # deallocate temp proj
iii += 1
Expand Down Expand Up @@ -2655,7 +2668,12 @@ cdef class _CRS(Base):
self._geodetic_crs = False
return None
try:
self._geodetic_crs = _CRS(_to_wkt(self.context, projobj))
self._geodetic_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj) # deallocate temp proj
return self._geodetic_crs
Expand Down Expand Up @@ -2905,7 +2923,12 @@ cdef class _CRS(Base):
if projobj == NULL:
return self
try:
crs_3d = _CRS(_to_wkt(self.context, projobj))
crs_3d = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return crs_3d
Expand Down
4 changes: 3 additions & 1 deletion pyproj/_transformer.pxd
@@ -1,6 +1,6 @@
include "proj.pxi"

from pyproj._crs cimport Base
from pyproj._crs cimport _CRS, Base


cdef class _TransformerGroup:
Expand All @@ -14,6 +14,8 @@ cdef class _Transformer(Base):
cdef readonly _area_of_use
cdef readonly str type_name
cdef readonly tuple _operations
cdef readonly _CRS _source_crs
cdef readonly _CRS _target_crs

@staticmethod
cdef _Transformer _from_pj(
Expand Down
4 changes: 4 additions & 0 deletions pyproj/_transformer.pyi
Expand Up @@ -56,6 +56,10 @@ class _Transformer(Base):
@property
def area_of_use(self) -> AreaOfUse: ...
@property
def source_crs(self) -> Optional[_CRS]: ...
@property
def target_crs(self) -> Optional[_CRS]: ...
@property
def operations(self) -> Union[Tuple[CoordinateOperation], None]: ...
@property
def is_network_enabled(self) -> bool: ...
Expand Down
61 changes: 60 additions & 1 deletion pyproj/_transformer.pyx
Expand Up @@ -15,13 +15,14 @@ from pyproj._crs cimport (
CoordinateOperation,
_get_concatenated_operations,
_to_proj4,
_to_wkt,
create_area_of_use,
)
from pyproj._datadir cimport pyproj_context_create, pyproj_context_destroy

from pyproj._datadir import _LOGGER
from pyproj.aoi import AreaOfInterest
from pyproj.enums import ProjVersion, TransformDirection
from pyproj.enums import ProjVersion, TransformDirection, WktVersion
from pyproj.exceptions import ProjError

# version number string for PROJ
Expand Down Expand Up @@ -798,6 +799,8 @@ cdef class _Transformer(Base):
self._area_of_use = None
self.type_name = "Unknown Transformer"
self._operations = None
self._source_crs = None
self._target_crs = None

def _initialize_from_projobj(self):
self.proj_info = proj_pj_info(self.projobj)
Expand Down Expand Up @@ -841,6 +844,62 @@ cdef class _Transformer(Base):
self._area_of_use = create_area_of_use(self.context, self.projobj)
return self._area_of_use

@property
def source_crs(self):
"""
.. versionadded:: 3.3.0

Returns
-------
Optional[_CRS]:
The source CRS of a CoordinateOperation.
"""
if self._source_crs is not None:
return None if self._source_crs is False else self._source_crs
cdef PJ * projobj = proj_get_source_crs(self.context, self.projobj)
ProjError.clear()
if projobj == NULL:
self._source_crs = False
return None
try:
self._source_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return self._source_crs

@property
def target_crs(self):
"""
.. versionadded:: 3.3.0

Returns
-------
Optional[_CRS]:
The target CRS of a CoordinateOperation.
"""
if self._target_crs is not None:
return None if self._target_crs is False else self._target_crs
cdef PJ * projobj = proj_get_target_crs(self.context, self.projobj)
ProjError.clear()
if projobj == NULL:
self._target_crs = False
return None
try:
self._target_crs = _CRS(_to_wkt(
self.context,
projobj,
version=WktVersion.WKT2_2019,
pretty=False,
))
finally:
proj_destroy(projobj)
return self._target_crs

@property
def operations(self):
"""
Expand Down
32 changes: 32 additions & 0 deletions pyproj/transformer.py
Expand Up @@ -416,6 +416,38 @@ def is_network_enabled(self) -> bool:
"""
return self._transformer.is_network_enabled

@property
def source_crs(self) -> Optional[CRS]:
"""
.. versionadded:: 3.3.0

Returns
-------
Optional[CRS]:
The source CRS of a CoordinateOperation.
"""
return (
None
if self._transformer.source_crs is None
else CRS(self._transformer.source_crs)
)

@property
def target_crs(self) -> Optional[CRS]:
"""
.. versionadded:: 3.3.0

Returns
-------
Optional[CRS]:
The target CRS of a CoordinateOperation.
"""
return (
None
if self._transformer.target_crs is None
else CRS(self._transformer.target_crs)
)

@staticmethod
def from_proj(
proj_from: Any,
Expand Down
12 changes: 12 additions & 0 deletions test/test_transformer.py
Expand Up @@ -1522,3 +1522,15 @@ def test_4d_transform__inplace__numpy__int():
assert zarr[0] == 5264462
assert tarr is not t_tarr
assert tarr[0] == 2019


def test_transformer_source_target_crs():
transformer = Transformer.from_crs("EPSG:4326", "EPSG:4258")
assert transformer.source_crs == "EPSG:4326"
assert transformer.target_crs == "EPSG:4258"


def test_transformer_source_target_crs__none():
transformer = Transformer.from_pipeline("+init=ITRF2008:ITRF2000")
assert transformer.source_crs is None
assert transformer.target_crs is None