[Git][debian-gis-team/python-pyproj][upstream] New upstream version 2.1.2+ds

Bas Couwenberg gitlab at salsa.debian.org
Sat Mar 23 16:51:51 GMT 2019


Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-pyproj


Commits:
1d9415ca by Bas Couwenberg at 2019-03-23T16:43:39Z
New upstream version 2.1.2+ds
- - - - -


23 changed files:

- appveyor.yml
- pyproj/__init__.py
- pyproj/_crs.pyx
- + pyproj/_datadir.pxd
- + pyproj/_datadir.pyx
- pyproj/_proj.pyx
- pyproj/_transformer.pxd
- pyproj/_transformer.pyx
- pyproj/datadir.py
- pyproj/exceptions.py
- pyproj/proj.pxi
- pyproj/proj.py
- pyproj/transformer.py
- setup.py
- sphinx/api/datadir.rst
- sphinx/history.rst
- sphinx/index.rst
- sphinx/installation.rst
- + sphinx/optimize_transformations.rst
- unittest/test.py
- unittest/test_datadir.py
- + unittest/test_exception_logging.py
- unittest/test_transformer.py


Changes:

=====================================
appveyor.yml
=====================================
@@ -123,10 +123,12 @@ after_test:
   # If tests are successful, create binary packages for the project.
   - mkdir pyproj\proj_dir\share\proj
   - copy %PROJ_LIB%\* pyproj\proj_dir\share\proj
-  - mkdir pyproj\proj_dir\lib
-  - copy %PROJ_DIR%\lib\* pyproj\proj_dir\lib
-  - copy c:\tools\vcpkg\installed\"%platform%"-windows\bin\sqlite3.dll pyproj\proj_dir\lib
-  - set PROJ_LIBDIR=proj_dir\lib
+  - mkdir pyproj\.lib
+  - mkdir .lib
+  - copy %PROJ_DIR%\lib\* pyproj\.lib
+  - copy %PROJ_DIR%\lib\* .lib
+  - copy c:\tools\vcpkg\installed\"%platform%"-windows\bin\sqlite3.dll pyproj\.lib
+  - set PROJ_LIBDIR=.lib
   - set PROJ_WHEEL=true
   - "%CMD_IN_ENV% python setup.py bdist_wheel"
   # - "%CMD_IN_ENV% python setup.py bdist_wininst"


=====================================
pyproj/__init__.py
=====================================
@@ -47,7 +47,7 @@ CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. """
-__version__ = "2.1.1"
+__version__ = "2.1.2"
 __all__ = [
     "Proj",
     "Geod",


=====================================
pyproj/_crs.pyx
=====================================
@@ -1,5 +1,5 @@
 from pyproj.compat import cstrencode, pystrdecode
-from pyproj.datadir import get_data_dir
+from pyproj._datadir cimport get_pyproj_context
 from pyproj.exceptions import CRSError
 
 def is_wkt(proj_string):
@@ -303,12 +303,7 @@ cdef class _CRS:
         self._area_of_use = None
         self._prime_meridian = None
         # setup the context
-        self.projctx = proj_context_create()
-        py_data_dir = cstrencode(get_data_dir())
-        cdef const char* data_dir = py_data_dir
-        proj_context_set_search_paths(self.projctx, 1, &data_dir)
-        proj_context_use_proj4_init_rules(self.projctx, 1)
-
+        self.projctx = get_pyproj_context()
         # setup proj initialization string.
         if not is_wkt(projstring) \
                 and not projstring.lower().startswith("epsg")\
@@ -584,6 +579,11 @@ cdef class _CRS:
         """
         return self.proj_type == PJ_TYPE_GEOCENTRIC_CRS
 
+    def is_exact_same(self, _CRS other):
+        """Compares projections to see if they are exactly the same."""
+        return proj_is_equivalent_to(
+            self.projobj, other.projobj, PJ_COMP_STRICT) == 1
+
     def __eq__(self, _CRS other):
         """Compares projections to see if they are equivalent."""
         return proj_is_equivalent_to(


=====================================
pyproj/_datadir.pxd
=====================================
@@ -0,0 +1,3 @@
+include "proj.pxi"
+
+cdef PJ_CONTEXT* get_pyproj_context()
\ No newline at end of file


=====================================
pyproj/_datadir.pyx
=====================================
@@ -0,0 +1,36 @@
+
+from libc.stdlib cimport malloc, free
+
+from pyproj.compat import cstrencode, pystrdecode
+from pyproj.datadir import get_data_dir
+from pyproj.exceptions import ProjError
+
+cdef void pyproj_log_function(void *user_data, int level, const char *error_msg):
+    """
+    Log function for proj.4 errors with CRS class.
+    """
+    if level == PJ_LOG_ERROR:
+        ProjError.internal_proj_error = pystrdecode(error_msg)
+
+
+cdef PJ_CONTEXT* get_pyproj_context():
+    data_dir = get_data_dir()
+    data_dir_list = data_dir.split(";")
+    cdef PJ_CONTEXT* pyproj_context = NULL
+    cdef char **c_data_dir = <char **>malloc(len(data_dir_list) * sizeof(char*))
+    try:
+        pyproj_context = proj_context_create()
+        for iii in range(len(data_dir_list)):
+            b_data_dir = cstrencode(data_dir_list[iii])
+            c_data_dir[iii] = b_data_dir
+        proj_context_set_search_paths(pyproj_context, len(data_dir_list), c_data_dir)
+    except:
+        if pyproj_context != NULL:
+            proj_context_destroy(pyproj_context)
+        raise
+    finally:
+        free(c_data_dir)
+    proj_context_use_proj4_init_rules(pyproj_context, 1)
+    proj_log_func(pyproj_context, NULL, pyproj_log_function)
+
+    return pyproj_context


=====================================
pyproj/_proj.pyx
=====================================
@@ -1,7 +1,7 @@
 include "base.pxi"
 
 from pyproj.compat import cstrencode, pystrdecode
-from pyproj.datadir import get_data_dir
+from pyproj._datadir cimport get_pyproj_context
 from pyproj.exceptions import ProjError
 
 
@@ -20,11 +20,7 @@ cdef class Proj:
     def __init__(self, const char *projstring):
         self.srs = pystrdecode(projstring)
         # setup the context
-        self.projctx = proj_context_create()
-        py_data_dir = cstrencode(get_data_dir())
-        cdef const char* data_dir = py_data_dir
-        proj_context_set_search_paths(self.projctx, 1, &data_dir)
-        proj_context_use_proj4_init_rules(self.projctx, 1)
+        self.projctx = get_pyproj_context()
         # initialize projection
         self.projpj = proj_create(self.projctx, projstring)
         if self.projpj is NULL:
@@ -174,3 +170,13 @@ cdef class Proj:
 
     def __repr__(self):
         return "Proj('{srs}', preserve_units=True)".format(srs=self.srs)
+
+    def is_exact_same(self, Proj other):
+        """Compares projections to see if they are exactly the same."""
+        return proj_is_equivalent_to(
+            self.projpj, other.projpj, PJ_COMP_STRICT) == 1
+
+    def __eq__(self, Proj other):
+        """Compares projections to see if they are equivalent."""
+        return proj_is_equivalent_to(
+            self.projpj, other.projpj, PJ_COMP_EQUIVALENT) == 1


=====================================
pyproj/_transformer.pxd
=====================================
@@ -8,3 +8,7 @@ cdef class _Transformer:
     cdef public object input_radians
     cdef public object output_radians
     cdef public object is_pipeline
+    cdef public object skip_equivalent
+    cdef public object projections_equivalent
+    cdef public object projections_exact_same
+    


=====================================
pyproj/_transformer.pyx
=====================================
@@ -3,7 +3,7 @@ include "base.pxi"
 from pyproj.crs import CRS
 from pyproj.proj import Proj
 from pyproj.compat import cstrencode, pystrdecode
-from pyproj.datadir import get_data_dir
+from pyproj._datadir cimport get_pyproj_context
 from pyproj.exceptions import ProjError
 
 cdef class _Transformer:
@@ -15,13 +15,13 @@ cdef class _Transformer:
         self.input_radians = False
         self.output_radians = False
         self.is_pipeline = False
+        self.skip_equivalent = False
+        self.projections_equivalent = False
+        self.projections_exact_same = False
 
     def __init__(self):
         # set up the context
-        self.projctx = proj_context_create()
-        py_data_dir = cstrencode(get_data_dir())
-        cdef const char* data_dir = py_data_dir
-        proj_context_set_search_paths(self.projctx, 1, &data_dir)
+        self.projctx = get_pyproj_context()
 
     def __dealloc__(self):
         """destroy projection definition"""
@@ -30,8 +30,12 @@ cdef class _Transformer:
         if self.projctx is not NULL:
             proj_context_destroy(self.projctx)
 
+    def set_radians_io(self):
+        self.input_radians = proj_angular_input(self.projpj, PJ_FWD)
+        self.output_radians = proj_angular_output(self.projpj, PJ_FWD)
+
     @staticmethod
-    def _init_crs_to_crs(proj_from, proj_to):
+    def _init_crs_to_crs(proj_from, proj_to, skip_equivalent=False):
         cdef _Transformer transformer = _Transformer()
         transformer.projpj = proj_create_crs_to_crs(
             transformer.projctx,
@@ -40,29 +44,31 @@ cdef class _Transformer:
             NULL)
         if transformer.projpj is NULL:
             raise ProjError("Error creating CRS to CRS.")
-        transformer.input_radians = proj_angular_input(transformer.projpj, PJ_FWD)
-        transformer.output_radians = proj_angular_output(transformer.projpj, PJ_FWD)
+        transformer.set_radians_io()
+        transformer.projections_exact_same = proj_from.is_exact_same(proj_to)
+        transformer.projections_equivalent = proj_from == proj_to
+        transformer.skip_equivalent = skip_equivalent
         transformer.is_pipeline = False
         return transformer
 
     @staticmethod
-    def from_proj(proj_from, proj_to):
+    def from_proj(proj_from, proj_to, skip_equivalent=False):
         if not isinstance(proj_from, Proj):
             proj_from = Proj(proj_from)
         if not isinstance(proj_to, Proj):
             proj_to = Proj(proj_to)
-        transformer = _Transformer._init_crs_to_crs(proj_from, proj_to)
+        transformer = _Transformer._init_crs_to_crs(proj_from, proj_to, skip_equivalent=skip_equivalent)
         transformer.input_geographic = proj_from.crs.is_geographic
         transformer.output_geographic = proj_to.crs.is_geographic
         return transformer
 
     @staticmethod
-    def from_crs(crs_from, crs_to):
+    def from_crs(crs_from, crs_to, skip_equivalent=False):
         if not isinstance(crs_from, CRS):
             crs_from = CRS.from_user_input(crs_from)
         if not isinstance(crs_to, CRS):
             crs_to = CRS.from_user_input(crs_to)
-        transformer = _Transformer._init_crs_to_crs(crs_from, crs_to)
+        transformer = _Transformer._init_crs_to_crs(crs_from, crs_to, skip_equivalent=skip_equivalent)
         transformer.input_geographic = crs_from.is_geographic
         transformer.output_geographic = crs_to.is_geographic
         return transformer
@@ -75,8 +81,7 @@ cdef class _Transformer:
         transformer.projpj = proj_create(transformer.projctx, proj_pipeline)
         if transformer.projpj is NULL:
             raise ProjError("Invalid projection {}.".format(proj_pipeline))
-        transformer.input_radians = proj_angular_input(transformer.projpj, PJ_FWD)
-        transformer.output_radians = proj_angular_output(transformer.projpj, PJ_FWD)
+        transformer.set_radians_io()
         transformer.is_pipeline = True
         return transformer
 
@@ -93,10 +98,12 @@ cdef class _Transformer:
 
         """
         if isinstance(in_proj, Proj):
-            return cstrencode(in_proj.srs)
-        return cstrencode(in_proj.to_wkt())
+            return cstrencode(in_proj.crs.srs)
+        return cstrencode(in_proj.srs)
 
     def _transform(self, inx, iny, inz, radians, errcheck=False):
+        if self.projections_exact_same or (self.projections_equivalent and self.skip_equivalent):
+            return
         # private function to call pj_transform
         cdef void *xdata
         cdef void *ydata
@@ -163,6 +170,8 @@ cdef class _Transformer:
 
     def _transform_sequence(self, Py_ssize_t stride, inseq, bint switch,
             radians, errcheck=False):
+        if self.projections_exact_same or (self.projections_equivalent and self.skip_equivalent):
+            return
         # private function to itransform function
         cdef:
             void *buffer


=====================================
pyproj/datadir.py
=====================================
@@ -2,10 +2,12 @@
 Set the datadir path to the local data directory
 """
 import os
+from distutils.spawn import find_executable
 
 from pyproj.exceptions import DataDirError
 
 _USER_PROJ_DATA = None
+_VALIDATED_PROJ_DATA = None
 
 
 def set_data_dir(proj_data_dir):
@@ -18,7 +20,22 @@ def set_data_dir(proj_data_dir):
         The path to rhe PROJ.4 data directory.
     """
     global _USER_PROJ_DATA
+    global _VALIDATED_PROJ_DATA
     _USER_PROJ_DATA = proj_data_dir
+    # set to none to re-validate
+    _VALIDATED_PROJ_DATA = None
+
+
+def append_data_dir(proj_data_dir):
+    """
+    Add an additional data directory for PROJ.4 to use.
+
+    Parameters
+    ----------
+    proj_data_dir: str
+        The path to rhe PROJ.4 data directory.
+    """
+    set_data_dir(";".join([get_data_dir(), proj_data_dir]))
 
 
 def get_data_dir():
@@ -27,17 +44,24 @@ def get_data_dir():
 
     1. The one set by pyproj.datadir.set_data_dir (if exists & valid)
     2. The internal proj directory (if exists & valid)
-    3. The directory in PROJ_LIB
+    3. The directory in PROJ_LIB (if exists & valid)
+    4. The directory on the PATH (if exists & valid)
 
     Returns
     -------
     str: The valid data directory.
 
     """
+    # to avoid re-validating
+    global _VALIDATED_PROJ_DATA
+    if _VALIDATED_PROJ_DATA is not None:
+        return _VALIDATED_PROJ_DATA
+
     global _USER_PROJ_DATA
     internal_datadir = os.path.join(
         os.path.dirname(os.path.abspath(__file__)), "proj_dir", "share", "proj"
     )
+    proj_lib_dirs = os.environ.get("PROJ_LIB", "")
 
     def valid_data_dir(potential_data_dir):
         if potential_data_dir is not None and os.path.exists(
@@ -46,21 +70,34 @@ def get_data_dir():
             return True
         return False
 
-    proj_data_dir = None
-    if valid_data_dir(_USER_PROJ_DATA):
-        proj_data_dir = _USER_PROJ_DATA
+    def valid_data_dirs(potential_data_dirs):
+        if potential_data_dirs is None:
+            return False
+        for proj_data_dir in potential_data_dirs.split(";"):
+            if valid_data_dir(proj_data_dir):
+                return True
+                break
+        return None
+
+    if valid_data_dirs(_USER_PROJ_DATA):
+        _VALIDATED_PROJ_DATA = _USER_PROJ_DATA
     elif valid_data_dir(internal_datadir):
-        proj_data_dir = internal_datadir
+        _VALIDATED_PROJ_DATA = internal_datadir
+    elif valid_data_dirs(proj_lib_dirs):
+        _VALIDATED_PROJ_DATA = proj_lib_dirs
     else:
-        proj_lib_dirs = os.environ.get("PROJ_LIB", "")
-        for proj_lib_dir in proj_lib_dirs.split(";"):
-            if valid_data_dir(proj_lib_dir):
-                proj_data_dir = proj_lib_dir
-                break
-    if proj_data_dir is None:
+        proj_exe = find_executable("proj")
+        if proj_exe is not None:
+            system_proj_dir = os.path.join(
+                os.path.dirname(os.path.dirname(proj_exe)), "share", "proj"
+            )
+            if valid_data_dir(system_proj_dir):
+                _VALIDATED_PROJ_DATA = system_proj_dir
+
+    if _VALIDATED_PROJ_DATA is None:
         raise DataDirError(
             "Valid PROJ.4 data directory not found."
             "Either set the path using the environmental variable PROJ_LIB or "
             "with `pyproj.datadir.set_data_dir`."
         )
-    return proj_data_dir
+    return _VALIDATED_PROJ_DATA


=====================================
pyproj/exceptions.py
=====================================
@@ -4,13 +4,26 @@ Exceptions for pyproj
 """
 
 
-class CRSError(RuntimeError):
-    """Raised when a CRS error occurs."""
-
-
 class ProjError(RuntimeError):
     """Raised when a Proj error occurs."""
 
+    internal_proj_error = None
+
+    def __init__(self, error_message):
+        if self.internal_proj_error is not None:
+            error_message = (
+                "{error_message}: (Internal Proj Error: {internal_proj_error})"
+            ).format(
+                error_message=error_message,
+                internal_proj_error=self.internal_proj_error,
+            )
+            self.internal_proj_error = None
+        super(ProjError, self).__init__(error_message)
+
+
+class CRSError(ProjError):
+    """Raised when a CRS error occurs."""
+
 
 class GeodError(RuntimeError):
     """Raised when a Geod error occurs."""


=====================================
pyproj/proj.pxi
=====================================
@@ -24,6 +24,16 @@ cdef extern from "proj.h":
     ctypedef struct PJ_CONTEXT
     PJ_CONTEXT *proj_context_create ()
     PJ_CONTEXT *proj_context_destroy (PJ_CONTEXT *ctx)
+
+    ctypedef enum PJ_LOG_LEVEL:
+        PJ_LOG_NONE  = 0
+        PJ_LOG_ERROR = 1
+        PJ_LOG_DEBUG = 2
+        PJ_LOG_TRACE = 3
+        PJ_LOG_TELL  = 4
+    ctypedef void (*PJ_LOG_FUNCTION)(void *, int, const char *)
+    void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf)
+
     int  proj_errno (const PJ *P)
     int proj_context_errno (PJ_CONTEXT *ctx)
     const char * proj_errno_string (int err)


=====================================
pyproj/proj.py
=====================================
@@ -294,7 +294,7 @@ class Proj(_proj.Proj):
         """
         self.crs = CRS.from_user_input(projparams if projparams is not None else kwargs)
         # make sure units are meters if preserve_units is False.
-        if not preserve_units and self.crs.is_projected:
+        if not preserve_units and "foot" in self.crs.axis_info[0].unit_name:
             projstring = self.crs.to_proj4(4)
             projstring = re.sub(r"\s\+units=[\w-]+", "", projstring)
             projstring += " +units=m"


=====================================
pyproj/transformer.py
=====================================
@@ -40,7 +40,7 @@ class Transformer(object):
     """
 
     @staticmethod
-    def from_proj(proj_from, proj_to):
+    def from_proj(proj_from, proj_to, skip_equivalent=False):
         """Make a Transformer from a :obj:`pyproj.Proj` or input used to create one.
 
         Parameters
@@ -49,6 +49,9 @@ class Transformer(object):
             Projection of input data.
         proj_from: :obj:`pyproj.Proj` or input used to create one
             Projection of output data.
+        skip_equivalent: bool, optional
+            If true, will skip the transformation operation if input and output 
+            projections are equivalent. Default is false.
 
         Returns
         -------
@@ -57,11 +60,13 @@ class Transformer(object):
         """
 
         transformer = Transformer()
-        transformer._transformer = _Transformer.from_proj(proj_from, proj_to)
+        transformer._transformer = _Transformer.from_proj(
+            proj_from, proj_to, skip_equivalent
+        )
         return transformer
 
     @staticmethod
-    def from_crs(crs_from, crs_to):
+    def from_crs(crs_from, crs_to, skip_equivalent=False):
         """Make a Transformer from a :obj:`pyproj.CRS` or input used to create one.
 
         Parameters
@@ -70,6 +75,9 @@ class Transformer(object):
             Projection of input data.
         proj_from: :obj:`pyproj.CRS` or input used to create one
             Projection of output data.
+        skip_equivalent: bool, optional
+            If true, will skip the transformation operation if input and output
+            projections are equivalent. Default is false.
 
         Returns
         -------
@@ -77,7 +85,9 @@ class Transformer(object):
 
         """
         transformer = Transformer()
-        transformer._transformer = _Transformer.from_crs(crs_from, crs_to)
+        transformer._transformer = _Transformer.from_crs(
+            crs_from, crs_to, skip_equivalent
+        )
         return transformer
 
     @staticmethod
@@ -142,6 +152,10 @@ class Transformer(object):
         >>> xpjr, ypjr, zpjr = transprojr.transform(xpj, ypj, zpj, radians=True)
         >>> "%.3f %.3f %.3f" % (xpjr, ypjr, zpjr)
         '-2704026.010 -4253051.810 3895878.820'
+        >>> transformer = Transformer.from_crs("epsg:4326", 4326, skip_equivalent=True)
+        >>> xeq, yeq = transformer.transform(33, 98)
+        >>> "%.0f  %.0f" % (xeq, yeq)
+        '33  98'
 
         """
         # process inputs, making copies that support buffer API.
@@ -203,6 +217,9 @@ class Transformer(object):
         >>> transprojr = Transformer.from_proj('+init=EPSG:4326', {"proj":'geocent', "ellps":'WGS84', "datum":'WGS84'})
         >>> for pt in transprojr.itransform([(-2.137, 0.661, -20.531)], radians=True): '{:.3f} {:.3f} {:.3f}'.format(*pt)
         '-2704214.394 -4254414.478 3894270.731'
+        >>> transproj_eq = Transformer.from_proj('+init=EPSG:4326', 4326, skip_equivalent=True)
+        >>> for pt in transproj_eq.itransform([(-2.137, 0.661)]): '{:.3f} {:.3f}'.format(*pt)
+        '-2.137 0.661'
 
         """
         it = iter(points)  # point iterator
@@ -226,14 +243,17 @@ class Transformer(object):
             if len(buff) == 0:
                 break
 
-            self._transformer._transform_sequence(stride, buff, switch,
-                    radians, errcheck=errcheck)
+            self._transformer._transform_sequence(
+                stride, buff, switch, radians, errcheck=errcheck
+            )
 
             for pt in zip(*([iter(buff)] * stride)):
                 yield pt
 
 
-def transform(p1, p2, x, y, z=None, radians=False, errcheck=False):
+def transform(
+    p1, p2, x, y, z=None, radians=False, errcheck=False, skip_equivalent=False
+):
     """
     x2, y2, z2 = transform(p1, p2, x1, y1, z1)
 
@@ -252,6 +272,9 @@ def transform(p1, p2, x, y, z=None, radians=False, errcheck=False):
     exception is raised if the transformation is
     invalid. By default errcheck=False and ``inf`` is returned for an
     invalid transformation (and no exception is raised).
+    If the optional kwarg skip_equivalent is true (default is False), 
+    it will skip the transformation operation if input and output 
+    projections are equivalent.
 
     In addition to converting between cartographic and geographic
     projection coordinates, this function can take care of datum
@@ -313,15 +336,21 @@ def transform(p1, p2, x, y, z=None, radians=False, errcheck=False):
     '1402291.0  5076289.5'
     >>> pj = Proj(init="epsg:4214")
     >>> pjx, pjy = pj(116.366, 39.867)
-    >>> xr, yr = transform(pj, Proj(4326), pjx, pjy, radians=True)
+    >>> xr, yr = transform(pj, Proj(4326), pjx, pjy, radians=True, errcheck=True)
     >>> "%.3f %.3f" % (xr, yr)
-    '2.031 0.696'
+    '0.696 2.031'
+    >>> xeq, yeq = transform(4326, 4326, 30, 60, skip_equivalent=True)
+    >>> "%.0f %.0f" % (xeq, yeq)
+    '30 60'
     """
-    return Transformer.from_proj(p1, p2).transform(x, y, z, radians,
-            errcheck=errcheck)
+    return Transformer.from_proj(p1, p2, skip_equivalent=skip_equivalent).transform(
+        x, y, z, radians, errcheck=errcheck
+    )
 
 
-def itransform(p1, p2, points, switch=False, radians=False, errcheck=False):
+def itransform(
+    p1, p2, points, switch=False, radians=False, errcheck=False, skip_equivalent=False
+):
     """
     points2 = itransform(p1, p2, points1)
     Iterator/generator version of the function pyproj.transform.
@@ -343,6 +372,13 @@ def itransform(p1, p2, points, switch=False, radians=False, errcheck=False):
     of points are switched to y, x or lat, lon. If the optional keyword 'radians' is True
     (default is False), then all input and output coordinates will be in radians instead
     of the default of degrees for geographic input/output projections.
+    If the optional keyword 'errcheck' is set to True an 
+    exception is raised if the transformation is
+    invalid. By default errcheck=False and ``inf`` is returned for an
+    invalid transformation (and no exception is raised).
+    If the optional kwarg skip_equivalent is true (default is False), 
+    it will skip the transformation operation if input and output 
+    projections are equivalent.
 
 
     Example usage:
@@ -357,13 +393,17 @@ def itransform(p1, p2, points, switch=False, radians=False, errcheck=False):
     >>> points = [(22.95, 40.63), (22.81, 40.53), (23.51, 40.86)]
     >>> # transform this point to projection 2 coordinates.
     >>> for pt in itransform(p1,p2,points): '%6.3f %7.3f' % pt
-    '411200.657 4498214.742'
-    '399210.500 4487264.963'
-    '458703.102 4523331.451'
+    '411050.470 4497928.574'
+    '399060.236 4486978.710'
+    '458553.243 4523045.485'
     >>> pj = Proj(init="epsg:4214")
     >>> pjx, pjy = pj(116.366, 39.867)
-    >>> for pt in itransform(pj, Proj(4326), [(pjx, pjy)], radians=True): '{:.3f} {:.3f}'.format(*pt)
-    '2.031 0.696'
+    >>> for pt in itransform(pj, Proj(4326), [(pjx, pjy)], radians=True, errcheck=True): '{:.3f} {:.3f}'.format(*pt)
+    '0.696 2.031'
+    >>> for pt in itransform(4326, 4326, [(30, 60)], skip_equivalent=True): '{:.0f} {:.0f}'.format(*pt)
+    '30 60'
+
     """
-    return Transformer.from_proj(p1, p2).itransform(points, switch, radians,
-            errcheck=errcheck)
+    return Transformer.from_proj(p1, p2, skip_equivalent=skip_equivalent).itransform(
+        points, switch, radians, errcheck=errcheck
+    )


=====================================
setup.py
=====================================
@@ -1,5 +1,3 @@
-from __future__ import with_statement
-
 import os
 import subprocess
 import sys
@@ -7,58 +5,40 @@ from collections import defaultdict
 from distutils.spawn import find_executable
 from glob import glob
 
+from pkg_resources import parse_version
 from setuptools import Extension, setup
 
-# Use Cython if available.
-if "clean" not in sys.argv:
-    try:
-        from Cython.Build import cythonize
-    except ImportError:
-        sys.exit(
-            "ERROR: Cython.Build.cythonize not found. "
-            "Cython is required to build from a repo."
-        )
-
-
-PROJ_MIN_VERSION = (6, 0, 0)
+PROJ_MIN_VERSION = parse_version("6.0.0")
 CURRENT_FILE_PATH = os.path.dirname(os.path.abspath(__file__))
 BASE_INTERNAL_PROJ_DIR = "proj_dir"
 INTERNAL_PROJ_DIR = os.path.join(CURRENT_FILE_PATH, "pyproj", BASE_INTERNAL_PROJ_DIR)
 
 
 def check_proj_version(proj_dir):
+    """checks that the PROJ library meets the minimum version"""
     proj = os.path.join(proj_dir, "bin", "proj")
     try:
-        proj_version = subprocess.check_output([proj], stderr=subprocess.STDOUT)
+        proj_ver_bytes = subprocess.check_output([proj], stderr=subprocess.STDOUT)
     except subprocess.CalledProcessError:
         if os.name == "nt":
             return
         raise
-    proj_version = proj_version.split()[1].split(b".")
-    proj_version = tuple(int(v.strip(b",")) for v in proj_version)
+    proj_ver_bytes = (proj_ver_bytes.decode("ascii").split()[1]).strip(",")
+    proj_version = parse_version(proj_ver_bytes)
     if proj_version < PROJ_MIN_VERSION:
         sys.exit(
-            "ERROR: Minimum supported proj version is {}.".format(
-                ".".join([str(version_part) for version_part in PROJ_MIN_VERSION])
-            )
+            "ERROR: Minimum supported proj version is {}, installed "
+            "version is {}.".format(PROJ_MIN_VERSION, proj_version)
         )
 
+    return proj_version
 
-# By default we'll try to get options PROJ_DIR or the local version of proj
-include_dirs = []
-library_dirs = []
-libraries = []
-extra_link_args = []
-extensions = []
-proj_dir = os.environ.get("PROJ_DIR")
-package_data = defaultdict(list)
-
-if os.environ.get("PROJ_WHEEL") is not None and os.path.exists(INTERNAL_PROJ_DIR):
-    package_data["pyproj"].append(
-        os.path.join(BASE_INTERNAL_PROJ_DIR, "share", "proj", "*")
-    )
 
-if "clean" not in sys.argv:
+def get_proj_dir():
+    """
+    This function finds the base PROJ directory.
+    """
+    proj_dir = os.environ.get("PROJ_DIR")
     if proj_dir is None and os.path.exists(INTERNAL_PROJ_DIR):
         proj_dir = INTERNAL_PROJ_DIR
         print("Internally compiled directory being used {}.".format(INTERNAL_PROJ_DIR))
@@ -74,13 +54,13 @@ if "clean" not in sys.argv:
 
     # check_proj_version
     check_proj_version(proj_dir)
+    return proj_dir
 
-    # Configure optional Cython coverage.
-    cythonize_options = {"language_level": sys.version_info[0]}
-    if os.environ.get("PYPROJ_FULL_COVERAGE"):
-        cythonize_options["compiler_directives"] = {"linetrace": True}
-        cythonize_options["annotate"] = True
 
+def get_proj_libdirs(proj_dir):
+    """
+    This function finds the library directories
+    """
     proj_libdir = os.environ.get("PROJ_LIBDIR")
     libdirs = []
     if proj_libdir is None:
@@ -95,12 +75,13 @@ if "clean" not in sys.argv:
             sys.exit("ERROR: PROJ_LIBDIR dir not found. Please set PROJ_LIBDIR.")
     else:
         libdirs.append(proj_libdir)
+    return libdirs
 
-    if os.environ.get("PROJ_WHEEL") is not None and os.path.exists(
-        os.path.join(BASE_INTERNAL_PROJ_DIR, "lib")
-    ):
-        package_data["pyproj"].append(os.path.join(BASE_INTERNAL_PROJ_DIR, "lib", "*"))
 
+def get_proj_incdirs(proj_dir):
+    """
+    This function finds the include directories
+    """
     proj_incdir = os.environ.get("PROJ_INCDIR")
     incdirs = []
     if proj_incdir is None:
@@ -110,7 +91,25 @@ if "clean" not in sys.argv:
             sys.exit("ERROR: PROJ_INCDIR dir not found. Please set PROJ_INCDIR.")
     else:
         incdirs.append(proj_incdir)
+    return incdirs
+
 
+def get_cythonize_options():
+    """
+    This function gets the options to cythinize with
+    """
+    # Configure optional Cython coverage.
+    cythonize_options = {"language_level": sys.version_info[0]}
+    if os.environ.get("PYPROJ_FULL_COVERAGE"):
+        cythonize_options["compiler_directives"] = {"linetrace": True}
+        cythonize_options["annotate"] = True
+    return cythonize_options
+
+
+def get_libraries(libdirs):
+    """
+    This function gets the libraries to cythonize with
+    """
     libraries = ["proj"]
     if os.name == "nt":
         for libdir in libdirs:
@@ -118,17 +117,42 @@ if "clean" not in sys.argv:
             if projlib:
                 libraries = [os.path.basename(projlib[0]).split(".lib")[0]]
                 break
+    return libraries
 
+
+def get_extension_modules():
+    """
+    This function retrieves the extension modules
+    """
+    if "clean" in sys.argv:
+        return None
+
+    # make sure cython is available
+    try:
+        from Cython.Build import cythonize
+    except ImportError:
+        sys.exit(
+            "ERROR: Cython.Build.cythonize not found. "
+            "Cython is required to build from a repo."
+        )
+
+    # By default we'll try to get options PROJ_DIR or the local version of proj
+    proj_dir = get_proj_dir()
+    library_dirs = get_proj_libdirs(proj_dir)
+    include_dirs = get_proj_incdirs(proj_dir)
+
+    # setup extension options
     ext_options = dict(
-        include_dirs=incdirs,
-        library_dirs=libdirs,
-        runtime_library_dirs=libdirs if os.name != "nt" else None,
-        libraries=libraries,
+        include_dirs=include_dirs,
+        library_dirs=library_dirs,
+        runtime_library_dirs=library_dirs if os.name != "nt" else None,
+        libraries=get_libraries(library_dirs),
     )
     if os.name != "nt":
         ext_options["embedsignature"] = True
 
-    ext_modules = cythonize(
+    # setup cythonized modules
+    return cythonize(
         [
             Extension("pyproj._proj", ["pyproj/_proj.pyx"], **ext_options),
             Extension("pyproj._geod", ["pyproj/_geod.pyx"], **ext_options),
@@ -136,25 +160,47 @@ if "clean" not in sys.argv:
             Extension(
                 "pyproj._transformer", ["pyproj/_transformer.pyx"], **ext_options
             ),
+            Extension("pyproj._datadir", ["pyproj/_datadir.pyx"], **ext_options),
         ],
         quiet=True,
-        **cythonize_options
+        **get_cythonize_options()
     )
-else:
-    ext_modules = []
-
-# retreive pyproj version information (stored in _proj.pyx) in version variable
-# (taken from Fiona)
-with open(os.path.join("pyproj", "__init__.py"), "r") as f:
-    for line in f:
-        if line.find("__version__") >= 0:
-            # parse __version__ and remove surrounding " or '
-            version = line.split("=")[1].strip()[1:-1]
-            break
+
+
+def get_package_data():
+    """
+    This function retrieves the package data
+    """
+    # setup package data
+    package_data = defaultdict(list)
+    if os.environ.get("PROJ_WHEEL") is not None and os.path.exists(INTERNAL_PROJ_DIR):
+        package_data["pyproj"].append(
+            os.path.join(BASE_INTERNAL_PROJ_DIR, "share", "proj", "*")
+        )
+    if os.environ.get("PROJ_WHEEL") is not None and os.path.exists(
+        os.path.join(CURRENT_FILE_PATH, "pyproj", ".lib")
+    ):
+        package_data["pyproj"].append(os.path.join(".lib", "*"))
+
+    return package_data
+
+
+def get_version():
+    """
+    retreive pyproj version information (stored in _proj.pyx) in version variable
+    (taken from Fiona)
+    """
+    with open(os.path.join("pyproj", "__init__.py"), "r") as f:
+        for line in f:
+            if line.find("__version__") >= 0:
+                # parse __version__ and remove surrounding " or '
+                return line.split("=")[1].strip()[1:-1]
+    sys.exit("ERROR: pyproj version not fount.")
+
 
 setup(
     name="pyproj",
-    version=version,
+    version=get_version(),
     description="Python interface to PROJ.4 library",
     long_description="""
 Performs cartographic transformations between geographic (lat/lon)
@@ -162,7 +208,7 @@ and map projection (x/y) coordinates. Can also transform directly
 from one map projection coordinate system to another.
 Coordinates can be given as numpy arrays, python arrays, lists or scalars.
 Optimized for numpy arrays.""",
-    url="https://github.com/jswhit/pyproj",
+    url="https://github.com/pyproj4/pyproj",
     download_url="http://python.org/pypi/pyproj",
     author="Jeff Whitaker",
     author_email="jeffrey.s.whitaker at noaa.gov",
@@ -174,11 +220,8 @@ Optimized for numpy arrays.""",
         "Intended Audience :: Science/Research",
         "License :: OSI Approved",
         "Programming Language :: Python :: 2",
-        "Programming Language :: Python :: 2.6",
         "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.3",
-        "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
@@ -188,6 +231,6 @@ Optimized for numpy arrays.""",
         "Operating System :: OS Independent",
     ],
     packages=["pyproj"],
-    ext_modules=ext_modules,
-    package_data=package_data,
+    ext_modules=get_extension_modules(),
+    package_data=get_package_data(),
 )


=====================================
sphinx/api/datadir.rst
=====================================
@@ -11,3 +11,9 @@ pyproj.datadir.set_data_dir
 ---------------------------
 
 .. autofunction:: pyproj.datadir.set_data_dir
+
+
+pyproj.datadir.append_data_dir
+---------------------------
+
+.. autofunction:: pyproj.datadir.append_data_dir


=====================================
sphinx/history.rst
=====================================
@@ -1,5 +1,13 @@
 Change Log
 ==========
+
+2.1.2
+~~~~~
+* Updated to use the CRS definition for Proj instances in transforms (issue #207)
+* Add option to skip tranformation operation if input and output projections are equivalent
+ and always skip if the input and output projections are exact (issue #128)
+ * Update setup.py method for checking PROJ version (pull #211)
+
 2.1.1
 ~~~~~
 * Restore behavior of 1.9.6 when illegal projection transformation requested


=====================================
sphinx/index.rst
=====================================
@@ -12,6 +12,7 @@ GitHub Repository: https://github.com/pyproj4/pyproj
 
    installation
    api/index
+   optimize_transformations
    history
 
 Indices and tables


=====================================
sphinx/installation.rst
=====================================
@@ -68,7 +68,8 @@ The order of preference for the data directory is:
 
 1. The one set by pyproj.datadir.set_data_dir (if exists & valid)
 2. The internal proj directory (if exists & valid)
-3. The directory in the PROJ_LIB environment variable (if exists & valid)
+3. The directory in PROJ_LIB (if exists & valid)
+4. The directory on the PATH (if exists & valid)
 
 Install pyproj
 ~~~~~~~~~~~~~~


=====================================
sphinx/optimize_transformations.rst
=====================================
@@ -0,0 +1,50 @@
+Optimize Transformations
+========================
+
+Here are a few tricks to try out if you want to optimize your transformations.
+
+
+Repeated transformations
+------------------------
+
+If you use the same transform, using the :class:`pyproj.Transformer` can help
+optimize your transformations.
+
+.. code-block:: python
+
+    import numpy as np                                                      
+    from pyproj import Transformer, transform
+    
+    transformer = Transformer.from_proj(2263, 4326)
+    x_coords = np.random.randint(80000, 120000)                            
+    y_coords = np.random.randint(200000, 250000) 
+
+
+Example with `pyproj.transform`:
+
+.. code-block:: python
+
+   transform(2263, 4326, x_coords, y_coords)                                             
+
+Results: 160 ms ± 3.68 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
+
+Example with :class:`pyproj.Transformer`:
+
+.. code-block:: python
+
+   transformer.transform(x_coords, y_coords)                                             
+
+Results: 6.32 µs ± 49.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
+
+
+Tranforming with the same projections
+-------------------------------------
+
+pyproj will skip transformations if they are exacly the same by default. However, if you
+sometimes throw in the projections that are about the same and the results being close enough
+is what you want, the `skip_equivalent` option can help.
+
+.. note:: From PROJ code: The objects are equivalent for the purpose of coordinate operations.
+    They can differ by the name of their objects, identifiers, other metadata.
+    Parameters may be expressed in different units, provided that the value is 
+    (with some tolerance) the same once expressed in a common unit.


=====================================
unittest/test.py
=====================================
@@ -1,23 +1,12 @@
 # -*- coding: utf-8 -*-
 """Rewrite part of test.py in pyproj in the form of unittests."""
-from __future__ import with_statement
 
 import math
-from distutils.version import LooseVersion
-from sys import version_info as sys_version_info
+import unittest
 
-import pyproj
-from pyproj import Geod, Proj, pj_ellps, pj_list, proj_version_str, transform
+from pyproj import Geod, Proj, pj_ellps, pj_list, transform
 from pyproj.crs import CRSError
 
-if sys_version_info[:2] < (2, 7):
-    # for Python 2.4 - 2.6 use the backport of unittest from Python 2.7 and onwards
-    import unittest2 as unittest
-    from unittest2 import skipIf
-else:
-    import unittest
-    from unittest import skipIf
-
 try:
     import nose2
     import nose2.tools
@@ -59,19 +48,14 @@ class BasicTest(unittest.TestCase):
         self.assertEqual(items, items_dict)
 
         expected = sorted(
-            (
+            [
+                "+proj=lcc",
                 "+R=6371200",
-                "+lat_0=0",
                 "+lat_1=50",
                 "+lat_2=50",
                 "+lon_0=-107",
-                "+no_defs",
-                "+proj=lcc",
                 "+type=crs",
-                "+units=m",
-                "+x_0=0",
-                "+y_0=0",
-            )
+            ]
         )
         self.assertEqual(items, expected)
 
@@ -121,10 +105,7 @@ class BasicTest(unittest.TestCase):
 
 class InverseHammerTest(unittest.TestCase):
     # This is a unit test of the inverse of the hammer projection, which
-    # was added to the PROJ.4 repository on 2015-12-13.
-    # PROJ.4 versions 4.9.2 and below do not contain this feature but future
-    # releases of PROJ.4 should support the inverse of the hammer projection.
-    # Therefore, different tests are to test the expected behavior on versions.
+    # was added in the 4.9.3 version of PROJ (then PROJ.4).
     @classmethod
     def setUpClass(self):
         self.p = Proj(proj="hammer")  # hammer proj
@@ -134,25 +115,7 @@ class InverseHammerTest(unittest.TestCase):
         self.assertAlmostEqual(self.x, -2711575.083, places=3)
         self.assertAlmostEqual(self.y, 4395506.619, places=3)
 
-    @skipIf(
-        LooseVersion(proj_version_str) > LooseVersion("4.9.2"),
-        "test is for PROJ.4 version 4.9.2 and below ({0} installed)"
-        "".format(proj_version_str),
-    )
-    def test_inverse_proj_4_9_2_and_below(self):
-        try:
-            lon, lat = self.p(self.x, self.y, inverse=True)
-            self.assertAlmostEqual(lon, -30.0, places=3)
-            self.assertAlmostEqual(lat, 40.0, places=3)
-        except RuntimeError:
-            pass
-
-    @skipIf(
-        LooseVersion(proj_version_str) <= LooseVersion("4.9.2"),
-        "test is for PROJ.4 versions above 4.9.2 ({0} installed)"
-        "".format(proj_version_str),
-    )
-    def test_inverse_above_proj_4_9_2(self):
+    def test_inverse(self):
         lon, lat = self.p(self.x, self.y, inverse=True)
         self.assertAlmostEqual(lon, -30.0, places=3)
         self.assertAlmostEqual(lat, 40.0, places=3)
@@ -408,6 +371,12 @@ class Geod_NaN_Issue112_Test(unittest.TestCase):
         self.assertTrue(azi2 == azi2)
 
 
+def test_proj_equals():
+    assert Proj(4326) == Proj("epsg:4326")
+    assert Proj(4326) != Proj("epsg:3857")
+    assert Proj(4326) == Proj(Proj("epsg:4326").crs.to_proj4())
+
+
 if __name__ == "__main__":
     if HAS_NOSE2 is True:
         nose2.discover()


=====================================
unittest/test_datadir.py
=====================================
@@ -5,9 +5,9 @@ import unittest
 from contextlib import contextmanager
 
 import pytest
-
 from mock import patch
-from pyproj.datadir import DataDirError, get_data_dir, set_data_dir
+
+from pyproj.datadir import DataDirError, append_data_dir, get_data_dir, set_data_dir
 
 
 def create_projdb(tmpdir):
@@ -44,7 +44,7 @@ def temporary_directory():
 def test_get_data_dir__missing():
     with proj_env(), pytest.raises(DataDirError), patch(
         "pyproj.datadir.os.path.abspath", return_value="INVALID"
-    ):
+    ), patch("pyproj.datadir.find_executable", return_value=None):
         set_data_dir(None)
         os.environ.pop("PROJ_LIB", None)
         get_data_dir()
@@ -86,3 +86,43 @@ def test_get_data_dir__from_env_var():
         os.environ["PROJ_LIB"] = tmpdir
         create_projdb(tmpdir)
         assert get_data_dir() == tmpdir
+
+
+ at unittest.skipIf(os.name == "nt", reason="Cannot modify Windows environment variables.")
+def test_get_data_dir__from_env_var__multiple():
+    with proj_env(), temporary_directory() as tmpdir, patch(
+        "pyproj.datadir.os.path.abspath", return_value="INVALID"
+    ):
+        set_data_dir(None)
+        os.environ["PROJ_LIB"] = ";".join([tmpdir, tmpdir, tmpdir])
+        create_projdb(tmpdir)
+        assert get_data_dir() == ";".join([tmpdir, tmpdir, tmpdir])
+
+
+ at unittest.skipIf(os.name == "nt", reason="Cannot modify Windows environment variables.")
+def test_get_data_dir__from_path():
+    with proj_env(), temporary_directory() as tmpdir, patch(
+        "pyproj.datadir.os.path.abspath", return_value="INVALID"
+    ), patch("pyproj.datadir.find_executable") as find_exe:
+        set_data_dir(None)
+        os.environ.pop("PROJ_LIB", None)
+        find_exe.return_value = os.path.join(tmpdir, "bin", "proj")
+        proj_dir = os.path.join(tmpdir, "share", "proj")
+        os.makedirs(proj_dir)
+        create_projdb(proj_dir)
+        assert get_data_dir() == proj_dir
+
+
+def test_append_data_dir__internal():
+    with proj_env(), temporary_directory() as tmpdir:
+        set_data_dir(None)
+        os.environ["PROJ_LIB"] = tmpdir
+        create_projdb(tmpdir)
+        internal_proj_dir = os.path.join(tmpdir, "proj_dir", "share", "proj")
+        os.makedirs(internal_proj_dir)
+        create_projdb(internal_proj_dir)
+        extra_datadir = str(os.path.join(tmpdir, "extra_datumgrids"))
+        with patch("pyproj.datadir.os.path.abspath") as abspath_mock:
+            abspath_mock.return_value = os.path.join(tmpdir, "randomfilename.py")
+            append_data_dir(extra_datadir)
+            assert get_data_dir() == ";".join([internal_proj_dir, extra_datadir])


=====================================
unittest/test_exception_logging.py
=====================================
@@ -0,0 +1,14 @@
+import pytest
+
+from pyproj import CRS, Proj
+from pyproj.exceptions import CRSError, ProjError
+
+
+def test_proj_exception():
+    with pytest.raises(ProjError, match="Internal Proj Error"):
+        Proj("+proj=bobbyjoe")
+
+
+def test_crs_exception():
+    with pytest.raises(CRSError, match="Internal Proj Error"):
+        CRS("+proj=bobbyjoe")


=====================================
unittest/test_transformer.py
=====================================
@@ -1,6 +1,8 @@
-import pyproj
 import numpy as np
-from numpy.testing import assert_equal
+from numpy.testing import assert_almost_equal, assert_equal
+
+import pyproj
+from pyproj import Transformer
 from pyproj.exceptions import ProjError
 
 
@@ -22,15 +24,87 @@ def test_transform_wgs84_to_alaska():
     xx, yy = pyproj.transform(lat_lon_proj, alaska_aea_proj, *test)
     assert "{:.3f} {:.3f}".format(xx, yy) == "-1824924.495 330822.800"
 
+
 def test_illegal_transformation():
     # issue 202
-    p1 = pyproj.Proj(init='epsg:4326')
-    p2 = pyproj.Proj(init='epsg:3857')
-    xx, yy = pyproj.transform(p1,p2,(-180,-180,180,180,-180),(-90,90,90,-90,-90))
+    p1 = pyproj.Proj(init="epsg:4326")
+    p2 = pyproj.Proj(init="epsg:3857")
+    xx, yy = pyproj.transform(
+        p1, p2, (-180, -180, 180, 180, -180), (-90, 90, 90, -90, -90)
+    )
     assert np.all(np.isinf(xx))
     assert np.all(np.isinf(yy))
     try:
-        xx,yy = pyproj.transform(p1,p2,(-180,-180,180,180,-180),(-90,90,90,-90,-90),errcheck=True)
-        assert_equal(None, 'Should throw an exception when errcheck=True')
+        xx, yy = pyproj.transform(
+            p1, p2, (-180, -180, 180, 180, -180), (-90, 90, 90, -90, -90), errcheck=True
+        )
+        assert_equal(None, "Should throw an exception when errcheck=True")
     except ProjError:
         pass
+
+
+def test_lambert_conformal_transform():
+    # issue 207
+    Midelt = pyproj.Proj(init="epsg:26191")
+    WGS84 = pyproj.Proj(init="epsg:4326")
+
+    E = 567623.931
+    N = 256422.787
+    h = 1341.467
+
+    Long1, Lat1, H1 = pyproj.transform(Midelt, WGS84, E, N, h, radians=False)
+    assert_almost_equal((Long1, Lat1, H1), (-4.6753456, 32.902199, 1341.467), decimal=5)
+
+
+def test_equivalent_crs():
+    transformer = Transformer.from_crs("epsg:4326", 4326, skip_equivalent=True)
+    assert transformer._transformer.projections_equivalent
+    assert transformer._transformer.projections_exact_same
+    assert transformer._transformer.skip_equivalent
+
+
+def test_equivalent_crs__disabled():
+    transformer = Transformer.from_crs("epsg:4326", 4326)
+    assert not transformer._transformer.skip_equivalent
+    assert transformer._transformer.projections_equivalent
+    assert transformer._transformer.projections_exact_same
+
+
+def test_equivalent_crs__different():
+    transformer = Transformer.from_crs("epsg:4326", 3857, skip_equivalent=True)
+    assert transformer._transformer.skip_equivalent
+    assert not transformer._transformer.projections_equivalent
+    assert not transformer._transformer.projections_exact_same
+
+
+def test_equivalent_proj():
+    transformer = Transformer.from_proj(
+        "+init=epsg:4326", pyproj.Proj(4326).crs.to_proj4(), skip_equivalent=True
+    )
+    assert transformer._transformer.skip_equivalent
+    assert transformer._transformer.projections_equivalent
+    assert transformer._transformer.projections_exact_same
+
+
+def test_equivalent_proj__disabled():
+    transformer = Transformer.from_proj(3857, pyproj.Proj(3857).crs.to_proj4())
+    assert not transformer._transformer.skip_equivalent
+    assert transformer._transformer.projections_equivalent
+    assert transformer._transformer.projections_exact_same
+
+
+def test_equivalent_proj__different():
+    transformer = Transformer.from_proj(3857, 4326, skip_equivalent=True)
+    assert transformer._transformer.skip_equivalent
+    assert not transformer._transformer.projections_equivalent
+    assert not transformer._transformer.projections_exact_same
+
+
+def test_equivalent_pipeline():
+    transformer = Transformer.from_pipeline(
+        "+proj=pipeline +step +proj=longlat +ellps=WGS84 +step "
+        "+proj=unitconvert +xy_in=rad +xy_out=deg"
+    )
+    assert not transformer._transformer.skip_equivalent
+    assert not transformer._transformer.projections_equivalent
+    assert not transformer._transformer.projections_exact_same



View it on GitLab: https://salsa.debian.org/debian-gis-team/python-pyproj/commit/1d9415ca90f4a3ef998d762db36144a11a18b21b

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-pyproj/commit/1d9415ca90f4a3ef998d762db36144a11a18b21b
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20190323/18a2eb85/attachment-0001.html>


More information about the Pkg-grass-devel mailing list