Skip to content

Commit

Permalink
ENH: Add source/target CRS to Transformer
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 committed Oct 8, 2021
1 parent b4ab1d8 commit 4b4a38e
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 16 deletions.
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

0 comments on commit 4b4a38e

Please sign in to comment.