import os
import subprocess
import sys
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
PROJ_MIN_VERSION = parse_version("6.2.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")
proj_ver_bytes = subprocess.check_output(proj, stderr=subprocess.STDOUT)
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 {}, installed "
"version is {}. For more information see: "
"https://pyproj4.github.io/pyproj/stable/installation.html".format(
PROJ_MIN_VERSION, proj_version
)
)
return proj_version
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))
elif proj_dir is None and not os.path.exists(INTERNAL_PROJ_DIR):
proj = find_executable("proj", path=sys.prefix)
if proj is None:
proj = find_executable("proj")
if proj is None:
sys.exit(
"proj executable not found. Please set the PROJ_DIR variable."
"For more information see: "
"https://pyproj4.github.io/pyproj/stable/installation.html"
)
proj_dir = os.path.dirname(os.path.dirname(proj))
elif proj_dir is not None and os.path.exists(proj_dir):
print("PROJ_DIR is set, using existing proj4 installation..\n")
else:
sys.exit("ERROR: Invalid path for PROJ_DIR {}".format(proj_dir))
# check_proj_version
check_proj_version(proj_dir)
return proj_dir
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:
libdir_search_paths = (
os.path.join(proj_dir, "lib"),
os.path.join(proj_dir, "lib64"),
)
for libdir_search_path in libdir_search_paths:
if os.path.exists(libdir_search_path):
libdirs.append(libdir_search_path)
if not libdirs:
sys.exit("ERROR: PROJ_LIBDIR dir not found. Please set PROJ_LIBDIR.")
else:
libdirs.append(proj_libdir)
return libdirs
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:
if os.path.exists(os.path.join(proj_dir, "include")):
incdirs.append(os.path.join(proj_dir, "include"))
else:
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 cythonize with
"""
# Configure optional Cython coverage.
cythonize_options = {
"language_level": sys.version_info[0],
"compiler_directives": {"embedsignature": True},
}
if os.environ.get("PYPROJ_FULL_COVERAGE"):
cythonize_options["compiler_directives"].update(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:
projlib = glob(os.path.join(libdir, "proj*.lib"))
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=include_dirs,
library_dirs=library_dirs,
runtime_library_dirs=library_dirs if os.name != "nt" else None,
libraries=get_libraries(library_dirs),
)
# setup cythonized modules
return cythonize(
[
Extension("pyproj._proj", ["pyproj/_proj.pyx"], **ext_options),
Extension("pyproj._geod", ["pyproj/_geod.pyx"], **ext_options),
Extension("pyproj._crs", ["pyproj/_crs.pyx"], **ext_options),
Extension(
"pyproj._transformer", ["pyproj/_transformer.pyx"], **ext_options
),
Extension("pyproj._datadir", ["pyproj/_datadir.pyx"], **ext_options),
Extension("pyproj._list", ["pyproj/_list.pyx"], **ext_options),
],
quiet=True,
**get_cythonize_options()
)
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.")
def get_long_description():
"""
Get the long description for the file.
"""
with open("README.md", encoding="utf-8") as ld_file:
return ld_file.read()
setup(
name="pyproj",
version=get_version(),
description="Python interface to PROJ (cartographic projections "
"and coordinate transformations library)",
long_description=get_long_description(),
long_description_content_type="text/markdown",
url="https://github.com/pyproj4/pyproj",
download_url="http://python.org/pypi/pyproj",
author="Jeff Whitaker",
author_email="jeffrey.s.whitaker@noaa.gov",
platforms=["any"],
license="OSI Approved",
keywords=["python", "map projections", "GIS", "mapping", "maps"],
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"License :: OSI Approved",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Scientific/Engineering :: GIS",
"Topic :: Scientific/Engineering :: Mathematics",
"Operating System :: OS Independent",
],
packages=["pyproj"],
python_requires=">=3.5",
ext_modules=get_extension_modules(),
package_data=get_package_data(),
)