Skip to content

Commit

Permalink
REF: Use _transform_point inside of transform
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 committed Dec 21, 2022
1 parent f258835 commit c9126d3
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 182 deletions.
5 changes: 1 addition & 4 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ Change Log

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

3.5.0
-----
- ENH: Add `return_back_azimuth: bool` to allow compatibility between the azimuth output of the following functions (issue #1163):
`fwd` and `fwd_intermediate`, `inv` and `inv_intermediate`,
Note: BREAKING CHANGE for the default value `return_back_azimuth=True` in the functions `fwd_intermediate` and `inv_intermediate`
to mach the default value in `fwd` and `inv`
- PERF: Optimize point transformations (pull #1204)

3.4.1
-----
Expand Down
51 changes: 42 additions & 9 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -722,20 +722,47 @@ cdef class _Transformer(Base):
@cython.wraparound(False)
def _transform_point(
self,
double inx,
double iny,
double inz,
double intime,
object inx,
object iny,
object inz,
object intime,
object direction,
bint radians,
bint errcheck,
):
"""
Optimized to transform a single point between two coordinate systems.
"""
cdef double coord_x = inx
cdef double coord_y = iny
cdef double coord_z = 0
cdef double coord_t = HUGE_VAL
cdef tuple expected_numeric_types = (int, float)
if not isinstance(inx, expected_numeric_types):
raise TypeError("Scalar input expected for x")
if not isinstance(iny, expected_numeric_types):
raise TypeError("Scalar input expected for y")
if inz is not None:
if not isinstance(inz, expected_numeric_types):
raise TypeError("Scalar input expected for z")
coord_z = inz
if intime is not None:
if not isinstance(intime, expected_numeric_types):
raise TypeError("Scalar input expected for t")
coord_t = intime

cdef tuple return_data
if self.id == "noop":
return inx, iny, inz, intime
return_data = (inx, iny)
if inz is not None:
return_data += (inz,)
if intime is not None:
return_data += (intime,)
return return_data

cdef PJ_DIRECTION pj_direction = get_pj_direction(direction)
cdef PJ_COORD projxyout
cdef PJ_COORD projxyin = proj_coord(inx, iny, inz, intime)
cdef PJ_COORD projxyin = proj_coord(coord_x, coord_y, coord_z, coord_t)
with nogil:
# degrees to radians
if not radians and proj_angular_input(self.projobj, pj_direction):
Expand All @@ -756,7 +783,7 @@ cdef class _Transformer(Base):
)
elif errcheck:
with gil:
if ProjError.internal_proj_error is not None:
if _clear_proj_error() is not None:
raise ProjError("transform error")

# radians to degrees
Expand All @@ -767,8 +794,14 @@ cdef class _Transformer(Base):
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
_clear_proj_error()

return_data = (projxyout.xyzt.x, projxyout.xyzt.y)
if inz is not None:
return_data += (projxyout.xyzt.z,)
if intime is not None:
return_data += (projxyout.xyzt.t,)
return return_data

@cython.boundscheck(False)
@cython.wraparound(False)
Expand Down
118 changes: 15 additions & 103 deletions pyproj/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
"TransformerGroup",
"AreaOfInterest",
]
import math
import numbers
import threading
import warnings
from abc import ABC, abstractmethod
Expand Down Expand Up @@ -791,6 +789,19 @@ def transform( # pylint: disable=invalid-name
'33 98'
"""
try:
# function optimized for point data
return self._transformer._transform_point(
inx=xx,
iny=yy,
inz=zz,
intime=tt,
direction=direction,
radians=radians,
errcheck=errcheck,
)
except TypeError:
pass
# process inputs, making copies that support buffer API.
inx, x_data_type = _copytobuffer(xx, inplace=inplace)
iny, y_data_type = _copytobuffer(yy, inplace=inplace)
Expand All @@ -804,8 +815,8 @@ def transform( # pylint: disable=invalid-name
intime = None
# call pj_transform. inx,iny,inz buffers modified in place.
self._transformer._transform(
inx,
iny,
inx=inx,
iny=iny,
inz=inz,
intime=intime,
direction=direction,
Expand All @@ -822,105 +833,6 @@ 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: numbers.Real,
yy: numbers.Real,
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: numbers.Real,
yy: numbers.Real,
zz: numbers.Real,
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: numbers.Real,
yy: numbers.Real,
zz: numbers.Real,
tt: numbers.Real,
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
Examples of accepted numeric scalar:
- :class:`int`
- :class:`float`
- :class:`numpy.floating`
- :class:`numpy.integer`
Parameters
----------
xx: numbers.Real
Input x coordinate.
yy: numbers.Real
Input y coordinate.
zz: numbers.Real, optional
Input z coordinate.
tt: numbers.Real, 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
26 changes: 26 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from contextlib import contextmanager
from pathlib import Path

import numpy
import pytest
from packaging import version

import pyproj
Expand Down Expand Up @@ -89,3 +91,27 @@ def assert_can_pickle(raw_obj, tmp_path):
unpickled = pickle.load(f)

assert raw_obj == unpickled


def _make_longer_array(data: float):
"""
Turn the float into a 2-element array
"""
return numpy.array([data] * 2)


@pytest.fixture(
params=[
float,
numpy.array,
_make_longer_array,
]
)
def scalar_and_array(request):
"""
Ensure cython methods are tested
with scalar and arrays to trigger
point optimized functions as well
as the main functions supporting arrays.
"""
return request.param

0 comments on commit c9126d3

Please sign in to comment.