Skip to content

Commit

Permalink
ENH: Added Transformer.transform_point
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 committed Dec 17, 2022
1 parent e345729 commit 0a04665
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ disable=cyclic-import,


[FORMAT]
max-module-lines=1350
max-module-lines=1500
1 change: 1 addition & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ Change Log

Latest
------
- ENH: Added :meth:`.Transformer.transform_point` (pull #1204)

3.4.1
-----
Expand Down
10 changes: 10 additions & 0 deletions pyproj/_transformer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ class _Transformer(Base):
radians: bool,
errcheck: bool,
) -> None: ...
def _transform_point(
self,
inx: float,
iny: float,
inz: float,
intime: float,
direction: Union[TransformDirection, str],
radians: bool,
errcheck: bool,
) -> None: ...
def _transform_sequence(
self,
stride: int,
Expand Down
54 changes: 54 additions & 0 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,60 @@ cdef class _Transformer(Base):
ybuff.data[iii] = ybuff.data[iii]*_DG2RAD
ProjError.clear()

@cython.boundscheck(False)
@cython.wraparound(False)
def _transform_point(
self,
double inx,
double iny,
double inz,
double intime,
object direction,
bint radians,
bint errcheck,
):
if self.id == "noop":
return inx, iny, inz, intime

tmp_pj_direction = _PJ_DIRECTION_MAP[TransformDirection.create(direction)]
cdef PJ_DIRECTION pj_direction = <PJ_DIRECTION>tmp_pj_direction

cdef PJ_COORD projxyout
cdef PJ_COORD projxyin = proj_coord(inx, iny, inz, intime)
with nogil:
# degrees to radians
if not radians and proj_angular_input(self.projobj, pj_direction):
projxyin.uv.u *= _DG2RAD
projxyin.uv.v *= _DG2RAD
# radians to degrees
elif radians and proj_degree_input(self.projobj, pj_direction):
projxyin.uv.u *= _RAD2DG
projxyin.uv.v *= _RAD2DG

proj_errno_reset(self.projobj)
projxyout = proj_trans(self.projobj, pj_direction, projxyin)
errno = proj_errno(self.projobj)
if errcheck and errno:
with gil:
raise ProjError(
f"transform error: {proj_context_errno_string(self.context, errno)}"
)
elif errcheck:
with gil:
if ProjError.internal_proj_error is not None:
raise ProjError("transform error")

# radians to degrees
if not radians and proj_angular_output(self.projobj, pj_direction):
projxyout.xy.x *= _RAD2DG
projxyout.xy.y *= _RAD2DG
# degrees to radians
elif radians and proj_degree_output(self.projobj, pj_direction):
projxyout.xy.x *= _DG2RAD
projxyout.xy.y *= _DG2RAD
ProjError.clear()
return projxyout.xyzt.x, projxyout.xyzt.y, projxyout.xyzt.z, projxyout.xyzt.t

@cython.boundscheck(False)
@cython.wraparound(False)
def _transform_sequence(
Expand Down
1 change: 1 addition & 0 deletions pyproj/proj.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ cdef extern from "proj.h" nogil:
int proj_degree_input (PJ *P, PJ_DIRECTION dir)
int proj_degree_output (PJ *P, PJ_DIRECTION dir)

PJ_COORD proj_trans (PJ *P, PJ_DIRECTION direction, PJ_COORD coord)
size_t proj_trans_generic (
PJ *P,
PJ_DIRECTION direction,
Expand Down
100 changes: 100 additions & 0 deletions pyproj/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"TransformerGroup",
"AreaOfInterest",
]
import math
import threading
import warnings
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -820,6 +821,105 @@ def transform( # pylint: disable=invalid-name
return_data += (_convertback(t_data_type, intime),)
return return_data

@overload
def transform_point( # pylint: disable=invalid-name
self,
xx: float,
yy: float,
radians: bool = False,
errcheck: bool = False,
direction: Union[TransformDirection, str] = TransformDirection.FORWARD,
) -> Tuple[float, float]:
...

@overload
def transform_point( # pylint: disable=invalid-name
self,
xx: float,
yy: float,
zz: float,
radians: bool = False,
errcheck: bool = False,
direction: Union[TransformDirection, str] = TransformDirection.FORWARD,
) -> Tuple[float, float, float]:
...

@overload
def transform_point( # pylint: disable=invalid-name
self,
xx: float,
yy: float,
zz: float,
tt: float,
radians: bool = False,
errcheck: bool = False,
direction: Union[TransformDirection, str] = TransformDirection.FORWARD,
) -> Tuple[float, float, float, float]:
...

def transform_point( # pylint: disable=invalid-name
self,
xx,
yy,
zz=None,
tt=None,
radians=False,
errcheck=False,
direction=TransformDirection.FORWARD,
):
"""
Optimized to transform a single point between two coordinate systems.
See: :c:func:`proj_trans`
.. versionadded:: 3.5.0
Accepted numeric scalar:
- :class:`int`
- :class:`float`
- :class:`numpy.floating`
- :class:`numpy.integer`
Parameters
----------
xx: scalar
Input x coordinate.
yy: scalar
Input y coordinate.
zz: scalar, optional
Input z coordinate.
tt: scalar, optional
Input time coordinate.
radians: bool, default=False
If True, will expect input data to be in radians and will return radians
if the projection is geographic. Otherwise, it uses degrees.
Ignored for pipeline transformations with pyproj 2,
but will work in pyproj 3.
errcheck: bool, default=False
If True, an exception is raised if the errors are found in the process.
If False, ``inf`` is returned for errors.
direction: pyproj.enums.TransformDirection, optional
The direction of the transform.
Default is :attr:`pyproj.enums.TransformDirection.FORWARD`.
"""
outx, outy, outz, outt = self._transformer._transform_point(
xx,
yy,
inz=0 if zz is None else zz,
intime=math.inf if tt is None else tt,
direction=direction,
radians=radians,
errcheck=errcheck,
)
return_data: Tuple[float, ...] = (outx, outy)
if zz is not None:
return_data += (outz,)
if tt is not None:
return_data += (outt,)
return return_data

def itransform(
self,
points: Any,
Expand Down
75 changes: 48 additions & 27 deletions test/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@
)


@pytest.fixture(
params=[
"transform",
"transform_point",
]
)
def transform_point_method(request):
"""
Test both Transformer.transform and Transformer.transform_point
"""
return request.param


def test_tranform_wgs84_to_custom():
custom_proj = pyproj.Proj(
"+proj=geos +lon_0=0.000000 +lat_0=0 +h=35807.414063"
Expand Down Expand Up @@ -85,28 +98,30 @@ def test_lambert_conformal_transform():
assert_almost_equal((Long1, Lat1, H1), (-4.6753456, 32.902199, 1341.467), decimal=5)


def test_4d_transform():
def test_4d_transform(transform_point_method):
transformer = Transformer.from_pipeline("+init=ITRF2008:ITRF2000")
assert_almost_equal(
transformer.transform(
getattr(transformer, transform_point_method)(
xx=3513638.19380, yy=778956.45250, zz=5248216.46900, tt=2008.75
),
(3513638.1999428216, 778956.4532640711, 5248216.453456361, 2008.75),
)


def test_2d_with_time_transform():
def test_2d_with_time_transform(transform_point_method):
transformer = Transformer.from_pipeline("+init=ITRF2008:ITRF2000")
assert_almost_equal(
transformer.transform(xx=3513638.19380, yy=778956.45250, tt=2008.75),
getattr(transformer, transform_point_method)(
xx=3513638.19380, yy=778956.45250, tt=2008.75
),
(3513638.1999428216, 778956.4532640711, 2008.75),
)


def test_4d_transform_crs_obs1():
def test_4d_transform_crs_obs1(transform_point_method):
transformer = Transformer.from_proj(7789, 8401)
assert_almost_equal(
transformer.transform(
getattr(transformer, transform_point_method)(
xx=3496737.2679, yy=743254.4507, zz=5264462.9620, tt=2019.0
),
(3496737.757717311, 743253.9940103051, 5264462.701132784, 2019.0),
Expand All @@ -123,20 +138,22 @@ def test_4d_transform_orginal_crs_obs1():
)


def test_4d_transform_crs_obs2():
def test_4d_transform_crs_obs2(transform_point_method):
transformer = Transformer.from_proj(4896, 7930)
assert_almost_equal(
transformer.transform(
getattr(transformer, transform_point_method)(
xx=3496737.2679, yy=743254.4507, zz=5264462.9620, tt=2019.0
),
(3496737.7857162016, 743254.0394113371, 5264462.643659916, 2019.0),
)


def test_2d_with_time_transform_crs_obs2():
def test_2d_with_time_transform_crs_obs2(transform_point_method):
transformer = Transformer.from_proj(4896, 7930)
assert_almost_equal(
transformer.transform(xx=3496737.2679, yy=743254.4507, tt=2019.0),
getattr(transformer, transform_point_method)(
xx=3496737.2679, yy=743254.4507, tt=2019.0
),
(3496737.4105305015, 743254.1014318303, 2019.0),
)

Expand Down Expand Up @@ -240,11 +257,11 @@ def test_transform_no_exception():
transformer.itransform([(1.716073972, 52.658007833)], errcheck=True)


def test_transform__out_of_bounds():
def test_transform__out_of_bounds(transform_point_method):
with pytest.warns(FutureWarning):
transformer = Transformer.from_proj("+init=epsg:4326", "+init=epsg:27700")
with pytest.raises(pyproj.exceptions.ProjError):
transformer.transform(100000, 100000, errcheck=True)
getattr(transformer, transform_point_method)(100000, 100000, errcheck=True)


def test_transform_radians():
Expand Down Expand Up @@ -302,10 +319,10 @@ def test_itransform_radians():
)


def test_4d_transform__inverse():
def test_4d_transform__inverse(transform_point_method):
transformer = Transformer.from_pipeline("+init=ITRF2008:ITRF2000")
assert_almost_equal(
transformer.transform(
getattr(transformer, transform_point_method)(
xx=3513638.1999428216,
yy=778956.4532640711,
zz=5248216.453456361,
Expand All @@ -316,23 +333,23 @@ def test_4d_transform__inverse():
)


def test_transform_direction():
def test_transform_direction(transform_point_method):
forward_transformer = Transformer.from_crs(4326, 3857)
inverse_transformer = Transformer.from_crs(3857, 4326)
assert inverse_transformer.transform(
assert getattr(inverse_transformer, transform_point_method)(
-33, 24, direction=TransformDirection.INVERSE
) == forward_transformer.transform(-33, 24)
) == getattr(forward_transformer, transform_point_method)(-33, 24)
ident_transformer = Transformer.from_crs(4326, 3857)
ident_transformer.transform(-33, 24, direction=TransformDirection.IDENT) == (
-33,
24,
)


def test_always_xy__transformer():
def test_always_xy__transformer(transform_point_method):
transformer = Transformer.from_crs(2193, 4326, always_xy=True)
assert_almost_equal(
transformer.transform(1625350, 5504853),
getattr(transformer, transform_point_method)(1625350, 5504853),
(173.29964730317386, -40.60674802693758),
)

Expand Down Expand Up @@ -370,30 +387,34 @@ def test_transform_empty_array_xyzt(empty_array):
)


def test_transform_direction__string():
def test_transform_direction__string(transform_point_method):
forward_transformer = Transformer.from_crs(4326, 3857)
inverse_transformer = Transformer.from_crs(3857, 4326)
assert inverse_transformer.transform(
assert getattr(inverse_transformer, transform_point_method)(
-33, 24, direction="INVERSE"
) == forward_transformer.transform(-33, 24, direction="FORWARD")
) == getattr(forward_transformer, transform_point_method)(
-33, 24, direction="FORWARD"
)
ident_transformer = Transformer.from_crs(4326, 3857)
ident_transformer.transform(-33, 24, direction="IDENT") == (-33, 24)


def test_transform_direction__string_lowercase():
def test_transform_direction__string_lowercase(transform_point_method):
forward_transformer = Transformer.from_crs(4326, 3857)
inverse_transformer = Transformer.from_crs(3857, 4326)
assert inverse_transformer.transform(
assert getattr(inverse_transformer, transform_point_method)(
-33, 24, direction="inverse"
) == forward_transformer.transform(-33, 24, direction="forward")
) == getattr(forward_transformer, transform_point_method)(
-33, 24, direction="forward"
)
ident_transformer = Transformer.from_crs(4326, 3857)
ident_transformer.transform(-33, 24, direction="ident") == (-33, 24)


def test_transform_direction__invalid():
def test_transform_direction__invalid(transform_point_method):
transformer = Transformer.from_crs(4326, 3857)
with pytest.raises(ValueError, match="Invalid value"):
transformer.transform(-33, 24, direction="WHEREVER")
getattr(transformer, transform_point_method)(-33, 24, direction="WHEREVER")


def test_from_pipeline__non_transform_input():
Expand Down

0 comments on commit 0a04665

Please sign in to comment.