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

PERF: Optimize point transformations #1204

Merged
merged 2 commits into from
Dec 21, 2022
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
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
4 changes: 1 addition & 3 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ Change Log

Latest
------

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
11 changes: 11 additions & 0 deletions pyproj/_transformer.pyi
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numbers
from array import array
from typing import Any, List, NamedTuple, Optional, Tuple, Union

Expand Down Expand Up @@ -96,6 +97,16 @@ class _Transformer(Base):
radians: bool,
errcheck: bool,
) -> None: ...
def _transform_point(
self,
inx: numbers.Real,
iny: numbers.Real,
inz: numbers.Real,
intime: numbers.Real,
direction: Union[TransformDirection, str],
radians: bool,
errcheck: bool,
) -> None: ...
def _transform_sequence(
self,
stride: int,
Expand Down
85 changes: 85 additions & 0 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,91 @@ cdef class _Transformer(Base):
ybuff.data[iii] = ybuff.data[iii]*_DG2RAD
_clear_proj_error()

@cython.boundscheck(False)
@cython.wraparound(False)
def _transform_point(
self,
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
snowman2 marked this conversation as resolved.
Show resolved Hide resolved
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_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(coord_x, coord_y, coord_z, coord_t)
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 _clear_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
_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)
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
17 changes: 15 additions & 2 deletions pyproj/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,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 @@ -802,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 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