Skip to content

Commit

Permalink
REF: Use pyproj global context instead of the one from PROJ (#723)
Browse files Browse the repository at this point in the history
  • Loading branch information
snowman2 authored Oct 20, 2020
1 parent f7852ad commit 340971c
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 53 deletions.
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ build_script:
(call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" %platform% )
# setup PROJ
- set PROJ_DIR=%PROJ_BASE_DIR%\proj-%PROJSOURCE:~0,5%
- if "%PROJSOURCE%" == "git" git clone https://github.com/OSGeo/proj.4.git proj-git
- if "%PROJSOURCE%" == "git" git clone https://github.com/OSGeo/PROJ.git proj-git
- if "%PROJSOURCE%" == "git" rmdir /s /q %PROJ_DIR%
- if not "%PROJSOURCE%" == "git" if not exist %PROJ_DIR% set BUILD_PROJ_STABLE=1
- if defined BUILD_PROJ_STABLE curl -o "proj-%PROJSOURCE:~0,5%.zip" "https://download.osgeo.org/proj/proj-%PROJSOURCE%.zip"
Expand Down
2 changes: 1 addition & 1 deletion ci/travis/proj-dl-and-compile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/bash
echo "Building PROJ $1 version from source..."
if [[ $1 == "git" ]]; then
git clone https://github.com/OSGeo/proj.git proj-git
git clone https://github.com/OSGeo/PROJ.git proj-git
else
curl https://download.osgeo.org/proj/proj-$1.tar.gz > "proj-${1:0:5}.tar.gz"
tar zxf "proj-${1:0:5}.tar.gz"
Expand Down
45 changes: 23 additions & 22 deletions pyproj/_crs.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import warnings
from collections import OrderedDict

from pyproj._compat cimport cstrdecode
from pyproj._datadir cimport pyproj_context_create
from pyproj._datadir cimport pyproj_context_create, pyproj_context_destroy

from pyproj.aoi import AreaOfUse
from pyproj.compat import cstrencode, pystrdecode
Expand Down Expand Up @@ -333,7 +333,7 @@ cdef class Base:
if self.projobj != NULL:
proj_destroy(self.projobj)
if self.context != NULL:
proj_context_destroy(self.context)
pyproj_context_destroy(self.context)

cdef _set_base_info(self):
"""
Expand Down Expand Up @@ -559,6 +559,7 @@ cdef class CoordinateSystem(_CRSParts):
cdef CoordinateSystem coord_system = CoordinateSystem.__new__(CoordinateSystem)
coord_system.context = context
coord_system.projobj = coord_system_pj

cdef PJ_COORDINATE_SYSTEM_TYPE cs_type = proj_cs_get_type(
coord_system.context,
coord_system.projobj,
Expand Down Expand Up @@ -620,7 +621,7 @@ cdef class CoordinateSystem(_CRSParts):
coordinate_system_pj,
) == PJ_CS_TYPE_UNKNOWN:
proj_destroy(coordinate_system_pj)
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(
"Invalid coordinate system string: "
f"{pystrdecode(coordinate_system_string)}"
Expand Down Expand Up @@ -829,7 +830,7 @@ cdef class Ellipsoid(_CRSParts):
)

if ellipsoid_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
CRSError.clear()
return Ellipsoid.create(context, ellipsoid_pj)
Expand Down Expand Up @@ -879,7 +880,7 @@ cdef class Ellipsoid(_CRSParts):
)
if ellipsoid_pj == NULL or proj_get_type(ellipsoid_pj) != PJ_TYPE_ELLIPSOID:
proj_destroy(ellipsoid_pj)
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(
f"Invalid ellipsoid string: {pystrdecode(ellipsoid_string)}"
)
Expand Down Expand Up @@ -983,7 +984,7 @@ cdef class Ellipsoid(_CRSParts):
PJ_TYPE_ELLIPSOID,
)
if ellipsoid_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid ellipsoid name: {pystrdecode(ellipsoid_name)}")
CRSError.clear()
return Ellipsoid.create(context, ellipsoid_pj)
Expand Down Expand Up @@ -1107,7 +1108,7 @@ cdef class PrimeMeridian(_CRSParts):
)

if prime_meridian_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
CRSError.clear()
return PrimeMeridian.create(context, prime_meridian_pj)
Expand Down Expand Up @@ -1160,7 +1161,7 @@ cdef class PrimeMeridian(_CRSParts):
proj_get_type(prime_meridian_pj) != PJ_TYPE_PRIME_MERIDIAN
):
proj_destroy(prime_meridian_pj)
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(
f"Invalid prime meridian string: {pystrdecode(prime_meridian_string)}"
)
Expand Down Expand Up @@ -1267,7 +1268,7 @@ cdef class PrimeMeridian(_CRSParts):
PJ_TYPE_PRIME_MERIDIAN,
)
if prime_meridian_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(
f"Invalid prime meridian name: {pystrdecode(prime_meridian_name)}"
)
Expand Down Expand Up @@ -1355,7 +1356,7 @@ cdef class Datum(_CRSParts):
)

if datum_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
CRSError.clear()
return Datum.create(context, datum_pj)
Expand Down Expand Up @@ -1407,7 +1408,7 @@ cdef class Datum(_CRSParts):
proj_get_type(datum_pj) not in _DATUM_TYPE_MAP
):
proj_destroy(datum_pj)
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid datum string: {pystrdecode(datum_string)}")
CRSError.clear()
return Datum.create(context, datum_pj)
Expand Down Expand Up @@ -1476,7 +1477,7 @@ cdef class Datum(_CRSParts):
<PJ_TYPE>pj_datum_type,
)
if datum_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid datum name: {pystrdecode(datum_name)}")
CRSError.clear()
return Datum.create(context, datum_pj)
Expand Down Expand Up @@ -1585,7 +1586,7 @@ cdef class Datum(_CRSParts):
)
CRSError.clear()
if ellipsoid_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
self._ellipsoid = False
return None
self._ellipsoid = Ellipsoid.create(context, ellipsoid_pj)
Expand All @@ -1608,7 +1609,7 @@ cdef class Datum(_CRSParts):
)
CRSError.clear()
if prime_meridian_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
self._prime_meridian = False
return None
self._prime_meridian = PrimeMeridian.create(
Expand Down Expand Up @@ -1915,7 +1916,7 @@ cdef class CoordinateOperation(_CRSParts):
)

if coord_operation_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(f"Invalid authority or code ({auth_name}, {code})")
CRSError.clear()
return CoordinateOperation.create(context, coord_operation_pj)
Expand Down Expand Up @@ -1972,7 +1973,7 @@ cdef class CoordinateOperation(_CRSParts):
)
):
proj_destroy(coord_operation_pj)
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(
"Invalid coordinate operation string: "
f"{pystrdecode(coordinate_operation_string)}"
Expand Down Expand Up @@ -2085,7 +2086,7 @@ cdef class CoordinateOperation(_CRSParts):
<PJ_TYPE>pj_coordinate_operation_type,
)
if coordinate_operation_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
raise CRSError(
"Invalid coordinate operation name: "
f"{pystrdecode(coordinate_operation_name)}"
Expand Down Expand Up @@ -2342,7 +2343,7 @@ cdef class _CRS(Base):
)
CRSError.clear()
if ellipsoid_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
self._ellipsoid = False
return None
self._ellipsoid = Ellipsoid.create(context, ellipsoid_pj)
Expand All @@ -2367,7 +2368,7 @@ cdef class _CRS(Base):
)
CRSError.clear()
if prime_meridian_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
self._prime_meridian = False
return None
self._prime_meridian = PrimeMeridian.create(context, prime_meridian_pj)
Expand Down Expand Up @@ -2396,7 +2397,7 @@ cdef class _CRS(Base):
)
CRSError.clear()
if datum_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
self._datum = False
return None
self._datum = Datum.create(context, datum_pj)
Expand All @@ -2420,7 +2421,7 @@ cdef class _CRS(Base):
)
CRSError.clear()
if coord_system_pj == NULL:
proj_context_destroy(context)
pyproj_context_destroy(context)
self._coordinate_system = False
return None

Expand Down Expand Up @@ -2452,7 +2453,7 @@ cdef class _CRS(Base):
)
CRSError.clear()
if coord_pj == NULL:
proj_context_destroy
pyproj_context_destroy(context)
self._coordinate_operation = False
return None
self._coordinate_operation = CoordinateOperation.create(
Expand Down
2 changes: 2 additions & 0 deletions pyproj/_datadir.pxd
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
include "proj.pxi"


cdef PJ_CONTEXT* PYPROJ_GLOBAL_CONTEXT
cdef PJ_CONTEXT* pyproj_context_create() except *
cdef void pyproj_context_destroy(PJ_CONTEXT* context) except *
64 changes: 47 additions & 17 deletions pyproj/_datadir.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def set_use_global_context(active=None):
if active is None:
active = strtobool(os.environ.get("PYPROJ_GLOBAL_CONTEXT", "OFF"))
_USE_GLOBAL_CONTEXT = bool(active)
proj_context_set_autoclose_database(NULL, not _USE_GLOBAL_CONTEXT)
proj_context_set_autoclose_database(PYPROJ_GLOBAL_CONTEXT, not _USE_GLOBAL_CONTEXT)


def get_user_data_dir(create=False):
Expand All @@ -72,7 +72,9 @@ def get_user_data_dir(create=False):
str:
The user writable data directory.
"""
return pystrdecode(proj_context_get_user_writable_directory(NULL, bool(create)))
return pystrdecode(proj_context_get_user_writable_directory(
PYPROJ_GLOBAL_CONTEXT, bool(create)
))


cdef void pyproj_log_function(void *user_data, int level, const char *error_msg) nogil:
Expand Down Expand Up @@ -120,38 +122,66 @@ cdef void set_context_data_dir(PJ_CONTEXT* context) except *:
free(c_data_dir)


cdef void pyproj_context_initialize(
PJ_CONTEXT* context,
bint autoclose_database=True,
) except *:
cdef void pyproj_context_initialize(PJ_CONTEXT* context) except *:
"""
Setup the context for pyproj
"""
proj_log_func(context, NULL, pyproj_log_function)
proj_context_use_proj4_init_rules(context, 1)
if autoclose_database:
proj_context_set_autoclose_database(context, 1)
proj_context_set_autoclose_database(context, not _USE_GLOBAL_CONTEXT)
set_context_data_dir(context)


cdef class ContextManager:
"""
The only purpose of this class is
to ensure the context is cleaned up properly.
"""
cdef PJ_CONTEXT* context

def __cinit__(self):
self.context = NULL

def __dealloc__(self):
if self.context != NULL:
proj_context_destroy(self.context)

@staticmethod
cdef create(PJ_CONTEXT* context):
cdef ContextManager context_manager = ContextManager()
context_manager.context = context
return context_manager


# Different libraries that modify the PROJ global context will influence
# each other without realizing it. Due to this, pyproj is creating it's own
# global context so that it doesn't bother other libraries and is insulated
# against possible external changes made to the PROJ global context.
# See: https://github.com/pyproj4/pyproj/issues/722
cdef PJ_CONTEXT* PYPROJ_GLOBAL_CONTEXT = proj_context_create()
cdef ContextManager CONTEXT_MANAGER = ContextManager.create(PYPROJ_GLOBAL_CONTEXT)


cdef PJ_CONTEXT* pyproj_context_create() except *:
"""
Create and initialize the context(s) for pyproj.
This also manages whether the global context is used.
"""
global _USE_GLOBAL_CONTEXT
if _USE_GLOBAL_CONTEXT:
return NULL
return proj_context_create()
return PYPROJ_GLOBAL_CONTEXT
return proj_context_clone(PYPROJ_GLOBAL_CONTEXT)

cdef void pyproj_context_destroy(PJ_CONTEXT* context) except *:
"""
Destroy context only if not the global context
"""
if context != PYPROJ_GLOBAL_CONTEXT:
proj_context_destroy(context)


def _pyproj_global_context_initialize():
global _USE_GLOBAL_CONTEXT
pyproj_context_initialize(
NULL,
autoclose_database=not _USE_GLOBAL_CONTEXT
)
pyproj_context_initialize(PYPROJ_GLOBAL_CONTEXT)


def _global_context_set_data_dir():
set_context_data_dir(NULL)
set_context_data_dir(PYPROJ_GLOBAL_CONTEXT)
8 changes: 5 additions & 3 deletions pyproj/_network.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ include "proj.pxi"
import os
from distutils.util import strtobool

from pyproj._datadir cimport PYPROJ_GLOBAL_CONTEXT

from pyproj.compat import cstrencode


Expand All @@ -16,7 +18,7 @@ def _set_ca_bundle_path(ca_bundle_path):
ca_bundle_path: str
The path to the CA Bundle.
"""
proj_context_set_ca_bundle_path(NULL, cstrencode(ca_bundle_path))
proj_context_set_ca_bundle_path(PYPROJ_GLOBAL_CONTEXT, cstrencode(ca_bundle_path))


def set_network_enabled(active=None):
Expand All @@ -39,7 +41,7 @@ def set_network_enabled(active=None):
# setting based on the environment variable every time if None
# because it could have been changed by the user previously
active = strtobool(os.environ.get("PROJ_NETWORK", "OFF"))
proj_context_set_enable_network(NULL, bool(active))
proj_context_set_enable_network(PYPROJ_GLOBAL_CONTEXT, bool(active))


def is_network_enabled():
Expand All @@ -49,4 +51,4 @@ def is_network_enabled():
bool:
If PROJ network is enabled by default.
"""
return proj_context_is_network_enabled(NULL) == 1
return proj_context_is_network_enabled(PYPROJ_GLOBAL_CONTEXT) == 1
4 changes: 3 additions & 1 deletion pyproj/_sync.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ include "proj.pxi"

from pyproj.compat import pystrdecode

from pyproj._datadir cimport PYPROJ_GLOBAL_CONTEXT


def get_proj_endpoint() -> str:
"""
Expand All @@ -10,4 +12,4 @@ def get_proj_endpoint() -> str:
str:
URL of the endpoint where PROJ grids are stored.
"""
return pystrdecode(proj_context_get_url_endpoint(NULL))
return pystrdecode(proj_context_get_url_endpoint(PYPROJ_GLOBAL_CONTEXT))
4 changes: 2 additions & 2 deletions pyproj/_transformer.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ from pyproj._crs cimport (
_get_concatenated_operations,
create_area_of_use,
)
from pyproj._datadir cimport pyproj_context_create
from pyproj._datadir cimport pyproj_context_create, pyproj_context_destroy

from pyproj.aoi import AreaOfInterest
from pyproj.compat import cstrencode, pystrdecode
Expand Down Expand Up @@ -104,7 +104,7 @@ cdef class _TransformerGroup:
def __dealloc__(self):
"""destroy projection definition"""
if self.context != NULL:
proj_context_destroy(self.context)
pyproj_context_destroy(self.context)

def __init__(
self,
Expand Down
Loading

0 comments on commit 340971c

Please sign in to comment.