diff --git a/.pylintrc b/.pylintrc index 5cbadd42e..3fe210519 100644 --- a/.pylintrc +++ b/.pylintrc @@ -348,7 +348,7 @@ indent-string=' ' max-line-length=100 # Maximum number of lines in a module. -max-module-lines=1300 +max-module-lines=1350 # Allow the body of a class to be on the same line as the declaration if body # contains single statement. diff --git a/docs/history.rst b/docs/history.rst index f6251ad0a..4725c79d5 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -5,6 +5,7 @@ Latest ------- - DEP: Minimum PROJ version 8.1 (issue #1011) - BUG: Fix transformer list for 3D transformations in :class:`pyproj.transformer.TransformerGroup` (discussion #1072) +- ENH: Added authority, accuracy, and allow_ballpark kwargs to :class:`pyproj.transformer.TransformerGroup` (pull #1076) 3.3.1 ------- diff --git a/pyproj/_transformer.pyi b/pyproj/_transformer.pyi index a6071bcd3..4dbe23981 100644 --- a/pyproj/_transformer.pyi +++ b/pyproj/_transformer.pyi @@ -35,8 +35,11 @@ class _TransformerGroup: self, crs_from: str, crs_to: str, - always_xy: bool = False, - area_of_interest: Optional[AreaOfInterest] = None, + always_xy: bool, + area_of_interest: Optional[AreaOfInterest], + authority: Optional[str], + accuracy: Optional[float], + allow_ballpark: bool, ) -> None: ... class _Transformer(Base): diff --git a/pyproj/_transformer.pyx b/pyproj/_transformer.pyx index 0ff6842f7..91b0c4e74 100644 --- a/pyproj/_transformer.pyx +++ b/pyproj/_transformer.pyx @@ -116,8 +116,11 @@ cdef class _TransformerGroup: self, _CRS crs_from not None, _CRS crs_to not None, - bint always_xy=False, - area_of_interest=None, + bint always_xy, + area_of_interest, + bint allow_ballpark, + str authority, + double accuracy, ): """ From PROJ docs: @@ -133,13 +136,18 @@ cdef class _TransformerGroup: cdef PJ_OBJ_LIST * pj_operations = NULL cdef PJ* pj_transform = NULL cdef PJ_CONTEXT* context = NULL + cdef const char* c_authority = NULL cdef int num_operations = 0 cdef int is_instantiable = 0 cdef double west_lon_degree, south_lat_degree, east_lon_degree, north_lat_degree + + if authority is not None: + c_authority = authority + try: operation_factory_context = proj_create_operation_factory_context( self.context, - NULL, + c_authority, ) if area_of_interest is not None: if not isinstance(area_of_interest, AreaOfInterest): @@ -159,7 +167,17 @@ cdef class _TransformerGroup: east_lon_degree, north_lat_degree, ) - + if accuracy > 0: + proj_operation_factory_context_set_desired_accuracy( + self.context, + operation_factory_context, + accuracy, + ) + proj_operation_factory_context_set_allow_ballpark_transformations( + self.context, + operation_factory_context, + allow_ballpark, + ) proj_operation_factory_context_set_grid_availability_use( self.context, operation_factory_context, diff --git a/pyproj/proj.pxi b/pyproj/proj.pxi index 13ed15250..8bca7c6e8 100644 --- a/pyproj/proj.pxi +++ b/pyproj/proj.pxi @@ -456,7 +456,16 @@ cdef extern from "proj.h" nogil: double east_lon_degree, double north_lat_degree ) - + void proj_operation_factory_context_set_allow_ballpark_transformations( + PJ_CONTEXT *ctx, + PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + int allow + ) + void proj_operation_factory_context_set_desired_accuracy( + PJ_CONTEXT *ctx, + PJ_OPERATION_FACTORY_CONTEXT *factory_ctx, + double accuracy + ) ctypedef enum PROJ_SPATIAL_CRITERION: PROJ_SPATIAL_CRITERION_STRICT_CONTAINMENT PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION @@ -465,6 +474,7 @@ cdef extern from "proj.h" nogil: PROJ_GRID_AVAILABILITY_USED_FOR_SORTING PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID PROJ_GRID_AVAILABILITY_IGNORED + PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE ctypedef struct PJ_FACTORS: double meridional_scale diff --git a/pyproj/transformer.py b/pyproj/transformer.py index c24d80dd2..5bfe2976a 100644 --- a/pyproj/transformer.py +++ b/pyproj/transformer.py @@ -150,10 +150,15 @@ def __init__( skip_equivalent: bool = False, always_xy: bool = False, area_of_interest: Optional[AreaOfInterest] = None, + authority: Optional[str] = None, + accuracy: Optional[float] = None, + allow_ballpark: bool = True, ) -> 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 + .. deprecated:: 3.1 skip_equivalent Parameters @@ -172,6 +177,21 @@ def __init__( area_of_interest: :class:`pyproj.transformer.AreaOfInterest`, optional The area of interest to help order the transformations based on the best operation for the area. + authority: str, optional + When not specified, coordinate operations from any authority will be + searched, with the restrictions set in the + authority_to_authority_preference database table related to the + authority of the source/target CRS themselves. If authority is set + to “any”, then coordinate operations from any authority will be + searched. If authority is a non-empty string different from "any", + then coordinate operations will be searched only in that authority + namespace (e.g. EPSG). + accuracy: float, optional + The minimum desired accuracy (in metres) of the candidate + coordinate operations. + allow_ballpark: bool, default=True + Set to False to disallow the use of Ballpark transformation + in the candidate coordinate operations. Default is to allow. """ if skip_equivalent: @@ -186,6 +206,9 @@ def __init__( CRS.from_user_input(crs_to)._crs, always_xy=always_xy, area_of_interest=area_of_interest, + authority=authority, + accuracy=-1 if accuracy is None else accuracy, + allow_ballpark=allow_ballpark, ) for iii, transformer in enumerate(self._transformers): # pylint: disable=unsupported-assignment-operation diff --git a/test/test_transformer.py b/test/test_transformer.py index d2265e6bb..14792fffb 100644 --- a/test/test_transformer.py +++ b/test/test_transformer.py @@ -1536,3 +1536,27 @@ def test_pickle_transformer_from_crs(): area_of_interest=AreaOfInterest(-136.46, 49.0, -60.72, 83.17), ) assert transformer == pickle.loads(pickle.dumps(transformer)) + + +def test_transformer_group_accuracy_filter(): + group = TransformerGroup("EPSG:4326", "EPSG:4258", accuracy=0.05) + assert not group.transformers + assert not group.unavailable_operations + + +def test_transformer_group_allow_ballpark_filter(): + group = TransformerGroup( + "EPSG:4326", "EPSG:4258", authority="PROJ", allow_ballpark=False + ) + assert not group.transformers + assert not group.unavailable_operations + + +def test_transformer_group_authority_filter(): + group = TransformerGroup("EPSG:4326", "EPSG:4258", authority="PROJ") + assert len(group.transformers) == 1 + assert not group.unavailable_operations + assert ( + group.transformers[0].description + == "Ballpark geographic offset from WGS 84 to ETRS89" + )