Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Latest
- WHL: Wheels contain PROJ 9.7.0 (pull #1529)
- WHL: Remove MacOS 13 (X86_64) wheels (issue #1532)
- ENH: Add :meth:`database.query_geodetic_crs_from_datum` (pull #1390)
- ENH: Added crs_extent_use kwarg to :class:`pyproj.transformer.TransformerGroup` aligning with PROJ CLI --crs-extent-use
- ENH: Added :class:`pyproj.enums.CRSExtentUse` enum for TransformerGroup ``crs_extent_use`` kwarg

3.7.2
-----
Expand Down
30 changes: 30 additions & 0 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ cdef class _TransformerGroup:
str authority,
double accuracy,
bint allow_superseded,
crs_extent_use=None,
):
"""
From PROJ docs:
Expand Down Expand Up @@ -203,6 +204,35 @@ cdef class _TransformerGroup:
operation_factory_context,
PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
)
if crs_extent_use is not None:
# Accept CRSExtentUse enum or string matching CLI options
if hasattr(crs_extent_use, "value"):
crs_extent_use_value = crs_extent_use.value
else:
crs_extent_use_value = crs_extent_use
if isinstance(crs_extent_use_value, str):
crs_extent_use_lower = crs_extent_use_value.lower()
if crs_extent_use_lower == "none":
_crs_extent_enum = PJ_CRS_EXTENT_NONE
elif crs_extent_use_lower == "both":
_crs_extent_enum = PJ_CRS_EXTENT_BOTH
elif crs_extent_use_lower == "intersection":
_crs_extent_enum = PJ_CRS_EXTENT_INTERSECTION
elif crs_extent_use_lower == "smallest":
_crs_extent_enum = PJ_CRS_EXTENT_SMALLEST
else:
raise ProjError(
"Invalid crs_extent_use value. Expected one of 'none', 'both', 'intersection', 'smallest'."
)
else:
raise ProjError(
"crs_extent_use must be CRSExtentUse enum or string (one of 'none', 'both', 'intersection', 'smallest')."
)
proj_operation_factory_context_set_crs_extent_use(
self.context,
operation_factory_context,
_crs_extent_enum,
)
pj_operations = proj_create_operations(
self.context,
crs_from.projobj,
Expand Down
27 changes: 27 additions & 0 deletions pyproj/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,30 @@ class GeodIntermediateFlag(IntFlag):

AZIS_DISCARD = 0x000
AZIS_KEEP = 0x100


class CRSExtentUse(BaseEnum):
"""
.. versionadded:: 3.7.3

Controls how CRS extents are used when building candidate coordinate
operations in :class:`pyproj.transformer.TransformerGroup`.

Mirrors the PROJ CLI option ``--crs-extent-use``.

Values
------
NONE
Ignore CRS areas of use.
BOTH
Require both source & target CRS extents to intersect operation area.
INTERSECTION
Use intersection of source & target CRS extents.
SMALLEST
Use the smallest of source or target CRS extent.
"""

NONE = "NONE"
BOTH = "BOTH"
INTERSECTION = "INTERSECTION"
SMALLEST = "SMALLEST"
11 changes: 11 additions & 0 deletions pyproj/proj.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,11 @@ cdef extern from "proj.h" nogil:
PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
PROJ_SPATIAL_CRITERION criterion
)
void proj_operation_factory_context_set_crs_extent_use(
PJ_CONTEXT *ctx,
PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
PROJ_CRS_EXTENT_USE use
)
void proj_operation_factory_context_set_area_of_interest(
PJ_CONTEXT *ctx,
PJ_OPERATION_FACTORY_CONTEXT *factory_ctx,
Expand Down Expand Up @@ -513,6 +518,12 @@ cdef extern from "proj.h" nogil:
PROJ_GRID_AVAILABILITY_IGNORED
PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE

ctypedef enum PROJ_CRS_EXTENT_USE:
PJ_CRS_EXTENT_NONE
PJ_CRS_EXTENT_BOTH
PJ_CRS_EXTENT_INTERSECTION
PJ_CRS_EXTENT_SMALLEST

ctypedef struct PJ_FACTORS:
double meridional_scale
double parallel_scale
Expand Down
12 changes: 10 additions & 2 deletions pyproj/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from dataclasses import dataclass
from itertools import chain, islice
from pathlib import Path
from typing import Any, overload
from typing import Any, Literal, overload

from pyproj import CRS
from pyproj._compat import cstrencode
Expand All @@ -29,7 +29,7 @@
_TransformerGroup,
)
from pyproj.datadir import get_user_data_dir
from pyproj.enums import ProjVersion, TransformDirection, WktVersion
from pyproj.enums import ProjVersion, TransformDirection, WktVersion, CRSExtentUse
from pyproj.exceptions import ProjError
from pyproj.sync import _download_resource_file
from pyproj.utils import _convertback, _copytobuffer
Expand Down Expand Up @@ -163,12 +163,14 @@ def __init__(
accuracy: float | None = None,
allow_ballpark: bool = True,
allow_superseded: bool = False,
crs_extent_use: CRSExtentUse | Literal["none", "both", "intersection", "smallest"] | None = None,
) -> None:
"""Get all possible transformations from a :obj:`pyproj.crs.CRS`
or input used to create one.

.. versionadded:: 3.4.0 authority, accuracy, allow_ballpark
.. versionadded:: 3.6.0 allow_superseded
.. versionadded:: 3.7.3 crs_extent_use

Parameters
----------
Expand Down Expand Up @@ -202,6 +204,11 @@ def __init__(
Set to True to allow the use of superseded (but not deprecated)
transformations in the candidate coordinate operations. Default is
to disallow.
crs_extent_use: CRSExtentUse or {"none", "both", "intersection", "smallest"}, optional
Corresponds to the PROJ CLI ``--crs-extent-use`` flag controlling how
CRS areas of use are employed to filter candidate operations. May be
provided as a :class:`pyproj.enums.CRSExtentUse` enum member or case-insensitive
string. If not provided, PROJ's internal default is used.

"""
super().__init__(
Expand All @@ -213,6 +220,7 @@ def __init__(
accuracy=-1 if accuracy is None else accuracy,
allow_ballpark=allow_ballpark,
allow_superseded=allow_superseded,
crs_extent_use=crs_extent_use,
)
for iii, transformer in enumerate(self._transformers):
# pylint: disable=unsupported-assignment-operation
Expand Down
29 changes: 29 additions & 0 deletions test/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from pyproj.enums import TransformDirection
from pyproj.exceptions import ProjError
from pyproj.transformer import AreaOfInterest, TransformerGroup
from pyproj.enums import CRSExtentUse
from test.conftest import grids_available, proj_env, proj_network_env


Expand Down Expand Up @@ -1584,6 +1585,34 @@ def test_transformer_group_allow_superseded_filter():
assert len(superseded_group.transformers) > len(default_group.transformers)


def test_transformer_group_crs_extent_use_none():
group_default = TransformerGroup(4230, 32632)
group_no_extent = TransformerGroup(4230, 32632, crs_extent_use=CRSExtentUse.NONE)
# lengths may differ depending on PROJ heuristic, but group_no_extent should
# be longer than group_default
assert len(group_default.transformers) >= 1
assert len(group_no_extent.transformers) >= 1
assert len(group_no_extent.transformers) >= len(group_default.transformers)


def test_transformer_group_crs_extent_use_invalid():
with pytest.raises(ProjError):
TransformerGroup(4326, 3857, crs_extent_use="invalid-option")


def test_transformer_group_crs_extent_use_intersection_enum():
group_intersection = TransformerGroup(4230, 32632, crs_extent_use=CRSExtentUse.INTERSECTION)
assert len(group_intersection.transformers) >= 1

def test_transformer_group_crs_extent_use_smallest_enum():
group_smallest = TransformerGroup(4230, 32632, crs_extent_use=CRSExtentUse.SMALLEST)
assert len(group_smallest.transformers) >= 1

def test_transformer_group_crs_extent_use_both_enum():
group_both = TransformerGroup(4230, 32632, crs_extent_use=CRSExtentUse.BOTH)
assert len(group_both.transformers) >= 1


def test_transformer_group_authority_filter():
group = TransformerGroup("EPSG:4326", "EPSG:4258", authority="PROJ")
assert len(group.transformers) == 1
Expand Down
Loading