Skip to content
Commits on Source (4)
......@@ -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"
......
python-pyproj (2.1.2+ds-1~exp1) experimental; urgency=medium
* New upstream release.
-- Bas Couwenberg <sebastic@debian.org> Sat, 23 Mar 2019 17:44:49 +0100
python-pyproj (2.1.1+ds-1~exp1) experimental; urgency=medium
* New upstream release.
......
......@@ -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",
......
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(
......
include "proj.pxi"
cdef PJ_CONTEXT* get_pyproj_context()
\ No newline at end of file
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
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
......@@ -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
......@@ -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.crs.srs)
return cstrencode(in_proj.srs)
return cstrencode(in_proj.to_wkt())
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
......
......@@ -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
......@@ -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."""
......
......@@ -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)
......
......@@ -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"
......
......@@ -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
)
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)
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 '
version = line.split("=")[1].strip()[1:-1]
break
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@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(),
)
......@@ -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
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
......
......@@ -12,6 +12,7 @@ GitHub Repository: https://github.com/pyproj4/pyproj
installation
api/index
optimize_transformations
history
Indices and tables
......
......@@ -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
~~~~~~~~~~~~~~
......
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.