Skip to content

Commit

Permalink
ENH: Add network methods to Transformer (#637)
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 authored May 14, 2020
1 parent c900639 commit c5027f1
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 13 deletions.
1 change: 1 addition & 0 deletions docs/history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Change Log
* Refactor Proj to inherit from Transformer (issue #624)
* ENH: Support obects with '__array__' method (pandas.Series, xarray.DataArray, dask.array.Array) (issue #573)
* ENH: Added :func:`pyproj.datadir.get_user_data_dir` (pull #636)
* ENH: Add network methods to Transformer (issue #629)

2.6.1
~~~~~
Expand Down
3 changes: 3 additions & 0 deletions docs/transformation_grids.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ Available methods for download include:
- Enabling `PROJ network <https://proj.org/usage/network.html>`__ capabilities.
.. note:: You can use the `network` kwarg when initializing
:class:`pyproj.Proj <pyproj.proj.Proj>` or :class:`pyproj.Transformer <pyproj.transformer.Transformer>`
- Use `conda <https://conda.io/en/latest/>`__ with the `conda-forge <https://conda-forge.org/>`__ channel:
.. code-block:: bash
Expand Down
3 changes: 2 additions & 1 deletion pyproj/_datadir.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ include "proj.pxi"

cdef void pyproj_context_initialize(
PJ_CONTEXT* context,
bint free_context_on_error) except *
bint free_context_on_error,
network=*) except *

cdef class ContextManager:
cdef PJ_CONTEXT *context
7 changes: 6 additions & 1 deletion pyproj/_datadir.pyx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os

import warnings
from libc.stdlib cimport malloc, free

from pyproj.compat import cstrencode, pystrdecode
Expand Down Expand Up @@ -33,13 +33,18 @@ cdef void set_context_data_dir(PJ_CONTEXT* context) except *:
cdef void pyproj_context_initialize(
PJ_CONTEXT* context,
bint free_context_on_error,
network=None,
) except *:
"""
Setup the context for pyproj
"""
proj_log_func(context, NULL, pyproj_log_function)
proj_context_use_proj4_init_rules(context, 1)
proj_context_set_autoclose_database(context, 1)
if network is not None:
enabled = proj_context_set_enable_network(context, bool(network))
if network and not enabled:
warnings.warn("PROJ network cannot be enabled.")
try:
set_context_data_dir(context)
except DataDirError:
Expand Down
8 changes: 7 additions & 1 deletion pyproj/_transformer.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class _TransformerGroup:
skip_equivalent: bool = False,
always_xy: bool = False,
area_of_interest: Optional[AreaOfInterest] = None,
network: Optional[bool] = None,
) -> None: ...

class _Transformer(Base):
Expand All @@ -60,16 +61,21 @@ class _Transformer(Base):
def area_of_use(self) -> AreaOfUse: ...
@property
def operations(self) -> Union[Tuple[CoordinateOperation], None]: ...
@property
def is_network_enabled(self) -> bool: ...
@staticmethod
def from_crs(
crs_from: _CRS,
crs_to: _CRS,
skip_equivalent: bool = False,
always_xy: bool = False,
area_of_interest: Optional[AreaOfInterest] = None,
network: Optional[bool] = None,
) -> "_Transformer": ...
@staticmethod
def from_pipeline(proj_pipeline: str) -> "_Transformer": ...
def from_pipeline(
proj_pipeline: str, network: Optional[bool] = None
) -> "_Transformer": ...
def _transform(
self,
inx: Any,
Expand Down
22 changes: 17 additions & 5 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ cdef class _TransformerGroup:
skip_equivalent=False,
always_xy=False,
area_of_interest=None,
network=None,
):
"""
From PROJ docs:
Expand All @@ -143,7 +144,7 @@ cdef class _TransformerGroup:
with unknown accuracy are sorted last, whatever their area.
"""
self.context = proj_context_create()
pyproj_context_initialize(self.context, False)
pyproj_context_initialize(self.context, False, network=network)
cdef PJ_OPERATION_FACTORY_CONTEXT* operation_factory_context = NULL
cdef PJ_OBJ_LIST * pj_operations = NULL
cdef PJ* pj_transform = NULL
Expand Down Expand Up @@ -194,7 +195,7 @@ cdef class _TransformerGroup:
num_operations = proj_list_get_count(pj_operations)
for iii in range(num_operations):
context = proj_context_create()
pyproj_context_initialize(context, True)
pyproj_context_initialize(context, True, network=network)
pj_transform = proj_list_get(
context,
pj_operations,
Expand Down Expand Up @@ -326,13 +327,24 @@ cdef class _Transformer(Base):
self._operations = _get_concatenated_operations(self.context, self.projobj)
return self._operations

@property
def is_network_enabled(self):
"""
.. versionadded:: 3.0.0
bool:
If the network is enabled.
"""
return proj_context_is_network_enabled(self.context) == 1

@staticmethod
def from_crs(
_CRS crs_from,
_CRS crs_to,
skip_equivalent=False,
always_xy=False,
area_of_interest=None,
network=None,
):
"""
Create a transformer from CRS objects
Expand Down Expand Up @@ -363,7 +375,7 @@ cdef class _Transformer(Base):
north_lat_degree,
)
transformer.context = proj_context_create()
pyproj_context_initialize(transformer.context, False)
pyproj_context_initialize(transformer.context, False, network=network)
transformer.projobj = proj_create_crs_to_crs(
transformer.context,
cstrencode(crs_from.srs),
Expand Down Expand Up @@ -413,13 +425,13 @@ cdef class _Transformer(Base):
return transformer

@staticmethod
def from_pipeline(const char *proj_pipeline):
def from_pipeline(const char *proj_pipeline, network=None):
"""
Create Transformer from a PROJ pipeline string.
"""
cdef _Transformer transformer = _Transformer()
transformer.context = proj_context_create()
pyproj_context_initialize(transformer.context, False)
pyproj_context_initialize(transformer.context, False, network=network)
# initialize projection
transformer.projobj = proj_create(
transformer.context,
Expand Down
3 changes: 2 additions & 1 deletion pyproj/proj.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ cdef extern from "proj.h":
double dy_dphi

PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) nogil

# neworking related
const char *proj_context_get_user_writable_directory(PJ_CONTEXT *ctx, int create)
int proj_context_set_enable_network(PJ_CONTEXT* ctx, int enabled)
int proj_context_is_network_enabled(PJ_CONTEXT* ctx)
17 changes: 15 additions & 2 deletions pyproj/proj.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ class Proj(Transformer):
"""

def __init__(
self, projparams: Any = None, preserve_units: bool = True, **kwargs
self,
projparams: Any = None,
preserve_units: bool = True,
network=None,
**kwargs,
) -> None:
"""
A Proj class instance is initialized with proj map projection
Expand All @@ -55,12 +59,19 @@ def __init__(
https://proj.org/operations/projections/index.html for examples of
key/value pairs defining different map projections.
.. versionadded:: 3.0.0 network
Parameters
----------
projparams: int, str, dict, pyproj.CRS
A PROJ or WKT string, PROJ dict, EPSG integer, or a pyproj.CRS instance.
preserve_units: bool
If false, will ensure +units=m.
network: bool, optional
Default is None, which uses the system defaults for networking.
If True, it will force the use of network for grids regardless of
any other network setting. If False, it will force disable use of
network for grids regardless of any other network setting.
**kwargs:
PROJ projection parameters.
Expand Down Expand Up @@ -131,7 +142,9 @@ def __init__(
projstring = self.crs.to_proj4() or self.crs.srs

self.srs = re.sub(r"\s\+?type=crs", "", projstring).strip()
super().__init__(_Transformer.from_pipeline(cstrencode(self.srs)))
super().__init__(
_Transformer.from_pipeline(cstrencode(self.srs), network=network)
)

def __call__(
self,
Expand Down
48 changes: 46 additions & 2 deletions pyproj/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def __init__(
skip_equivalent: bool = False,
always_xy: bool = False,
area_of_interest: Optional[AreaOfInterest] = None,
network: Optional[bool] = None,
) -> None:
"""Get all possible transformations from a :obj:`pyproj.crs.CRS`
or input used to create one.
Expand All @@ -73,6 +74,11 @@ 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.
network: bool, optional
Default is None, which uses the system defaults for networking.
If True, it will force the use of network for grids regardless of
any other network setting. If False, it will force disable use of
network for grids regardless of any other network setting.
"""
super().__init__(
Expand All @@ -81,6 +87,7 @@ def __init__(
skip_equivalent=skip_equivalent,
always_xy=always_xy,
area_of_interest=area_of_interest,
network=network,
)
for iii, transformer in enumerate(self._transformers):
self._transformers[iii] = Transformer(transformer)
Expand Down Expand Up @@ -222,19 +229,31 @@ def operations(self) -> Optional[Tuple[CoordinateOperation]]:
"""
return self._transformer.operations

@property
def is_network_enabled(self) -> bool:
"""
.. versionadded:: 3.0.0
bool:
If the network is enabled.
"""
return self._transformer.is_network_enabled

@staticmethod
def from_proj(
proj_from: Any,
proj_to: Any,
skip_equivalent: bool = False,
always_xy: bool = False,
area_of_interest: Optional[AreaOfInterest] = None,
network: Optional[bool] = None,
) -> "Transformer":
"""Make a Transformer from a :obj:`pyproj.proj.Proj` or input used to create one.
.. versionadded:: 2.1.2 skip_equivalent
.. versionadded:: 2.2.0 always_xy
.. versionadded:: 2.3.0 area_of_interest
.. versionadded:: 3.0.0 network
Parameters
----------
Expand All @@ -252,6 +271,11 @@ def from_proj(
Default is false.
area_of_interest: :class:`pyproj.transformer.AreaOfInterest`, optional
The area of interest to help select the transformation.
network: bool, optional
Default is None, which uses the system defaults for networking.
If True, it will force the use of network for grids regardless of
any other network setting. If False, it will force disable use of
network for grids regardless of any other network setting.
Returns
-------
Expand All @@ -271,6 +295,7 @@ def from_proj(
skip_equivalent=skip_equivalent,
always_xy=always_xy,
area_of_interest=area_of_interest,
network=network,
)

@staticmethod
Expand All @@ -280,12 +305,14 @@ def from_crs(
skip_equivalent: bool = False,
always_xy: bool = False,
area_of_interest: Optional[AreaOfInterest] = None,
network: Optional[bool] = None,
) -> "Transformer":
"""Make a Transformer from a :obj:`pyproj.crs.CRS` or input used to create one.
.. versionadded:: 2.1.2 skip_equivalent
.. versionadded:: 2.2.0 always_xy
.. versionadded:: 2.3.0 area_of_interest
.. versionadded:: 3.0.0 network
Parameters
----------
Expand All @@ -303,6 +330,11 @@ def from_crs(
Default is false.
area_of_interest: :class:`pyproj.transformer.AreaOfInterest`, optional
The area of interest to help select the transformation.
network: bool, optional
Default is None, which uses the system defaults for networking.
If True, it will force the use of network for grids regardless of
any other network setting. If False, it will force disable use of
network for grids regardless of any other network setting.
Returns
-------
Expand All @@ -316,26 +348,38 @@ def from_crs(
skip_equivalent=skip_equivalent,
always_xy=always_xy,
area_of_interest=area_of_interest,
network=network,
)
)

@staticmethod
def from_pipeline(proj_pipeline: str) -> "Transformer":
def from_pipeline(
proj_pipeline: str, network: Optional[bool] = None
) -> "Transformer":
"""Make a Transformer from a PROJ pipeline string.
https://proj.org/operations/pipeline.html
.. versionadded:: 3.0.0 network
Parameters
----------
proj_pipeline: str
Projection pipeline string.
network: bool, optional
Default is None, which uses the system defaults for networking.
If True, it will force the use of network for grids regardless of
any other network setting. If False, it will force disable use of
network for grids regardless of any other network setting.
Returns
-------
Transformer
"""
return Transformer(_Transformer.from_pipeline(cstrencode(proj_pipeline)))
return Transformer(
_Transformer.from_pipeline(cstrencode(proj_pipeline), network=network)
)

def transform(
self,
Expand Down
19 changes: 19 additions & 0 deletions test/test_proj.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
import math
import os
import sys
import unittest

import numpy as np
import pytest
from mock import patch
from numpy.testing import assert_almost_equal

from pyproj import Geod, Proj, pj_ellps, pj_list, transform
Expand Down Expand Up @@ -533,3 +535,20 @@ def test_proj_radians_warning():
proj = Proj("epsg:4326")
with pytest.warns(UserWarning, match="radian"):
proj(1, 2, radians=True)


@patch.dict("os.environ", {"PROJ_NETWORK": "ON"}, clear=True)
def test_network__disable():
transformer = Proj(3857, network=False)
assert transformer.is_network_enabled is False


@patch.dict("os.environ", {"PROJ_NETWORK": "OFF"}, clear=True)
def test_network__enable():
transformer = Proj(3857, network=True)
assert transformer.is_network_enabled is True


def test_network__default():
transformer = Proj(3857)
assert transformer.is_network_enabled == (os.environ.get("PROJ_NETWORK") == "ON")
Loading

0 comments on commit c5027f1

Please sign in to comment.