Skip to content

Commit

Permalink
ENH: Added force_over kwarg to Transformer.from_crs
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 committed Aug 24, 2022
1 parent dccefd6 commit d90dabf
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 5 deletions.
1 change: 1 addition & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Latest
- DEP: Minimum PROJ version 8.2 (issue #1011)
- BUG: Fix transformer list for 3D transformations in :class:`.TransformerGroup` (discussion #1072)
- ENH: Added authority, accuracy, and allow_ballpark kwargs to :class:`.TransformerGroup` (pull #1076)
- ENH: Added ``force_over`` kwarg to :meth:`.Transformer.from_crs` (issue #997)
- CLN: Remove deprecated ``skip_equivalent`` kwarg from transformers and ``errcheck`` kwarg from :meth:`.CRS.from_cf` (pull #1077)
- REF: use regex to process PROJ strings in :meth:`.CRS.to_dict` (pull #1086)
- BUG: :class:`.MercatorAConversion` defined only for lat_0 = 0 (issue #1089)
Expand Down
1 change: 1 addition & 0 deletions pyproj/_transformer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class _Transformer(Base):
authority: Optional[str] = None,
accuracy: Optional[str] = None,
allow_ballpark: Optional[bool] = None,
force_over: bool = False,
) -> "_Transformer": ...
@staticmethod
def from_pipeline(proj_pipeline: bytes) -> "_Transformer": ...
Expand Down
17 changes: 13 additions & 4 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ cdef PJ* proj_create_crs_to_crs(
str authority,
str accuracy,
allow_ballpark,
):
bint force_over,
) except NULL:
"""
This is the same as proj_create_crs_to_crs in proj.h
with the options added. It is a hack for stabilily
Expand Down Expand Up @@ -272,6 +273,7 @@ cdef PJ* proj_create_crs_to_crs(
options[1] = NULL
options[2] = NULL
options[3] = NULL
options[4] = NULL
if authority is not None:
b_authority = cstrencode(f"AUTHORITY={authority}")
options[options_index] = b_authority
Expand All @@ -283,6 +285,12 @@ cdef PJ* proj_create_crs_to_crs(
if allow_ballpark is not None:
if not allow_ballpark:
options[options_index] = b"ALLOW_BALLPARK=NO"
options_index += 1
if force_over:
IF CTE_PROJ_VERSION_MAJOR >= 9:
options[options_index] = b"FORCE_OVER=YES"
ELSE:
raise NotImplementedError("force_over requires PROJ 9+.")

cdef PJ* transform = proj_create_crs_to_crs_from_pj(
ctx,
Expand All @@ -293,6 +301,8 @@ cdef PJ* proj_create_crs_to_crs(
)
proj_destroy(source_crs)
proj_destroy(target_crs)
if transform == NULL:
raise ProjError("Error creating Transformer from CRS.")
return transform


Expand Down Expand Up @@ -455,6 +465,7 @@ cdef class _Transformer(Base):
str authority=None,
str accuracy=None,
allow_ballpark=None,
bint force_over=False,
):
"""
Create a transformer from CRS objects
Expand Down Expand Up @@ -493,14 +504,12 @@ cdef class _Transformer(Base):
authority=authority,
accuracy=accuracy,
allow_ballpark=allow_ballpark,
force_over=force_over,
)
finally:
if pj_area_of_interest != NULL:
proj_area_destroy(pj_area_of_interest)

if transformer.projobj == NULL:
raise ProjError("Error creating Transformer from CRS.")

transformer._init_from_crs(always_xy)
return transformer

Expand Down
10 changes: 10 additions & 0 deletions pyproj/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class TransformerFromCRS(TransformerMaker):
"""
.. versionadded:: 3.1.0
.. versionadded:: 3.4.0 force_over
Generates a Cython _Transformer class from input CRS data.
"""

Expand All @@ -87,6 +89,7 @@ class TransformerFromCRS(TransformerMaker):
authority: Optional[str]
accuracy: Optional[str]
allow_ballpark: Optional[bool]
force_over: bool = False

def __call__(self) -> _Transformer:
"""
Expand All @@ -102,6 +105,7 @@ def __call__(self) -> _Transformer:
authority=self.authority,
accuracy=self.accuracy,
allow_ballpark=self.allow_ballpark,
force_over=self.force_over,
)


Expand Down Expand Up @@ -520,12 +524,14 @@ def from_crs(
authority: Optional[str] = None,
accuracy: Optional[float] = None,
allow_ballpark: Optional[bool] = None,
force_over: bool = False,
) -> "Transformer":
"""Make a Transformer from a :obj:`pyproj.crs.CRS` or input used to create one.
.. versionadded:: 2.2.0 always_xy
.. versionadded:: 2.3.0 area_of_interest
.. versionadded:: 3.1.0 authority, accuracy, allow_ballpark
.. versionadded:: 3.4.0 force_over
Parameters
----------
Expand Down Expand Up @@ -554,6 +560,9 @@ def from_crs(
allow_ballpark: bool, optional
Set to False to disallow the use of Ballpark transformation
in the candidate coordinate operations. Default is to allow.
force_over: bool, default=False
If True, it will to force the +over flag on the transformation.
Requires PROJ 9+.
Returns
-------
Expand All @@ -569,6 +578,7 @@ def from_crs(
authority=authority,
accuracy=accuracy if accuracy is None else str(accuracy),
allow_ballpark=allow_ballpark,
force_over=force_over,
)
)

Expand Down
1 change: 1 addition & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

_NETWORK_ENABLED = pyproj.network.is_network_enabled()
PROJ_LOOSE_VERSION = version.parse(pyproj.__proj_version__)
PROJ_GTE_9 = PROJ_LOOSE_VERSION >= version.parse("9.0.0")
PROJ_GTE_901 = PROJ_LOOSE_VERSION >= version.parse("9.0.1")
PROJ_GTE_91 = PROJ_LOOSE_VERSION >= version.parse("9.1")

Expand Down
35 changes: 34 additions & 1 deletion test/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
from pyproj.enums import TransformDirection
from pyproj.exceptions import ProjError
from pyproj.transformer import AreaOfInterest, TransformerGroup
from test.conftest import PROJ_GTE_91, grids_available, proj_env, proj_network_env
from test.conftest import (
PROJ_GTE_9,
PROJ_GTE_91,
grids_available,
proj_env,
proj_network_env,
)


def test_tranform_wgs84_to_custom():
Expand Down Expand Up @@ -1541,3 +1547,30 @@ def test_transformer_group_authority_filter():
group.transformers[0].description
== "Ballpark geographic offset from WGS 84 to ETRS89"
)


def test_transformer_force_over():
if PROJ_GTE_9:
transformer = Transformer.from_crs("EPSG:4326", "EPSG:3857", force_over=True)
# Test a point along the equator.
# The same point, but in two different representations.
xxx, yyy = transformer.transform(0, 140)
xxx_over, yyy_over = transformer.transform(0, -220)
# Web Mercator x's between 0 and 180 longitude come out positive.
# But when forcing the over flag, the -220 calculation makes it flip.
assert xxx > 0
assert xxx_over < 0
# check it works in both directions
xxx_inverse, yyy_inverse = transformer.transform(
xxx, yyy, direction=TransformDirection.INVERSE
)
xxx_over_inverse, yyy_over_inverse = transformer.transform(
xxx_over, yyy_over, direction=TransformDirection.INVERSE
)
assert_almost_equal(xxx_inverse, 0)
assert_almost_equal(xxx_over_inverse, 0)
assert_almost_equal(yyy_inverse, 140)
assert_almost_equal(yyy_over_inverse, -220)
else:
with pytest.raises(NotImplementedError, match="force_over requires PROJ 9"):
Transformer.from_crs("EPSG:4326", "EPSG:3857", force_over=True)

0 comments on commit d90dabf

Please sign in to comment.