8000 Lazy loading of GEOS functions · ddriddle/django@61d09e6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 61d09e6

Browse files
committed
Lazy loading of GEOS functions
1 parent 143255c commit 61d09e6

File tree

11 files changed

+357
-384
lines changed

11 files changed

+357
-384
lines changed

django/contrib/gis/db/backends/spatialite/features.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures
2-
from django.contrib.gis.geos import geos_version
2+
from django.contrib.gis.geos import geos_version_info
33
from django.db.backends.sqlite3.features import \
44
DatabaseFeatures as SQLiteDatabaseFeatures
5-
from django.utils.encoding import force_text
65
from django.utils.functional import cached_property
76

87

@@ -20,4 +19,4 @@ def supports_initspatialmetadata_in_one_transaction(self):
2019

2120
@cached_property
2221
def supports_3d_storage(self):
23-
return force_text(geos_version()) >= '3.3'
22+
return geos_version_info()['version'] >= '3.3'

django/contrib/gis/geos/libgeos.py

Lines changed: 86 additions & 60 deletions
< 10000 td data-grid-cell-id="diff-8b3d9bdd211601af93cf0acd8c8b23cca2451a981eb88d830859dc5ac2399c5b-62-72-2" data-line-anchor="diff-8b3d9bdd211601af93cf0acd8c8b23cca2451a981eb88d830859dc5ac2399c5bR72" data-selected="false" role="gridcell" style="background-color:var(--bgColor-default);padding-right:24px" tabindex="-1" valign="top" class="focusable-grid-cell diff-text-cell right-side-diff-cell left-side">

Original file line numberDiff line numberDiff line change
@@ -14,51 +14,61 @@
1414

1515
from django.contrib.gis.geos.error import GEOSException
1616
from django.core.exceptions import ImproperlyConfigured
17+
from django.utils.functional import SimpleLazyObject
1718

1819
logger = logging.getLogger('django.contrib.gis')
1920

20-
# Custom library path set?
21-
try:
22-
from django.conf import settings
23-
lib_path = settings.GEOS_LIBRARY_PATH
24-
except (AttributeError, EnvironmentError,
25-
ImportError, ImproperlyConfigured):
26-
lib_path = None
27-
28-
# Setting the appropriate names for the GEOS-C library.
29-
if lib_path:
30-
lib_names = None
31-
elif os.name == 'nt':
32-
# Windows NT libraries
33-
lib_names = ['geos_c', 'libgeos_c-1']
34-
elif os.name == 'posix':
35-
# *NIX libraries
36-
lib_names = ['geos_c', 'GEOS']
37-
else:
38-
raise ImportError('Unsupported OS "%s"' % os.name)
39-
40-
# Using the ctypes `find_library` utility to find the path to the GEOS
41-
# shared library. This is better than manually specifying each library name
42-
# and extension (e.g., libgeos_c.[so|so.1|dylib].).
43-
if lib_names:
44-
for lib_name in lib_names:
45-
lib_path = find_library(lib_name)
46-
if lib_path is not None:
47-
break
48-
49-
# No GEOS library could be found.
50-
if lib_path is None:
51-
raise ImportError(
52-
'Could not find the GEOS library (tried "%s"). '
53-
'Try setting GEOS_LIBRARY_PATH in your settings.' %
54-
'", "'.join(lib_names)
55-
)
56-
57-
# Getting the GEOS C library. The C interface (CDLL) is used for
58-
# both *NIX and Windows.
59-
# See the GEOS C API source code for more details on the library function calls:
60-
# http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
61-
lgeos = CDLL(lib_path)
21+
22+
def load_geos():
23+
# Custom library path set?
24+
try:
25+
from django.conf import settings
26+
lib_path = settings.GEOS_LIBRARY_PATH
27+
except (AttributeError, EnvironmentError,
28+
ImportError, ImproperlyConfigured):
29+
lib_path = None
30+
31+
# Setting the appropriate names for the GEOS-C library.
32+
if lib_path:
33+
lib_names = None
34+
elif os.name == 'nt':
35+
# Windows NT libraries
36+
lib_names = ['geos_c', 'libgeos_c-1']
37+
elif os.name == 'posix':
38+
# *NIX libraries
39+
lib_names = ['geos_c', 'GEOS']
40+
else:
41+
raise ImportError('Unsupported OS "%s"' % os.name)
42+
43+
# Using the ctypes `find_library` utility to find the path to the GEOS
44+
# shared library. This is better than manually specifying each library name
45+
# and extension (e.g., libgeos_c.[so|so.1|dylib].).
46+
if lib_names:
47+
for lib_name in lib_names:
48+
lib_path = find_library(lib_name)
49+
if lib_path is not None:
50+
break
51+
52+
# No GEOS library could be found.
53+
if lib_path is None:
54+
raise ImportError(
55+
'Could not find the GEOS library (tried "%s"). '
56+
'Try setting GEOS_LIBRARY_PATH in your settings.' %
57+
'", "'.join(lib_names)
58+
)
59+
# Getting the GEOS C library. The C interface (CDLL) is used for
60+
# both *NIX and Windows.
61+
# See the GEOS C API source code for more details on the library function calls:
62+
# http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
63+
_lgeos = CDLL(lib_path)
64+
# Here we set up the prototypes for the initGEOS_r and finishGEOS_r
65+
# routines. These functions aren't actually called until they are
66+
# attached to a GEOS context handle -- this actually occurs in
67+
# geos/prototypes/threadsafe.py.
68+
_lgeos.initGEOS_r.restype = CONTEXT_PTR
69+
_lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
70+
return _lgeos
71+
6272
6373
# The notice and error handler C function callback definitions.
6474
# Supposed to mimic the GEOS message handler (C below):
@@ -120,11 +130,42 @@ def get_pointer_arr(n):
120130
GeomArr = GEOM_PTR * n
121131
return GeomArr()
122132

133+
134+
lgeos = SimpleLazyObject(load_geos)
135+
136+
137+
class GEOSFuncFactory(object):
138+
argtypes = None
139+
restype = None
140+
errcheck = None
141+
142+
def __init__(self, func_name, *args, **kwargs):
143+
self.func_name = func_name
144+
self.restype = kwargs.pop('restype', self.restype)
145+
self.errcheck = kwargs.pop('errcheck', self.errcheck)
146+
self.argtypes = kwargs.pop('argtypes', self.argtypes)
147+
self.args = args
148+
self.kwargs = kwargs
149+
self.func = None
150+
151+
def __call__(self, *args, **kwargs):
152+
if self.func is None:
153+
self.func = self.get_func(*self.args, **self.kwargs)
154+
return self.func(*args, **kwargs)
155+
156+
def get_func(self, *args, **kwargs):
157+
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
158+
func = GEOSFunc(self.func_name)
159+
func.argtypes = self.argtypes or []
160+
func.restype = self.restype
161+
if self.errcheck:
162+
func.errcheck = self.errcheck
163+
return func
164+
165+
123166
# Returns the string version of the GEOS library. Have to set the restype
124167
# explicitly to c_char_p to ensure compatibility across 32 and 64-bit platforms.
125-
geos_version = lgeos.GEOSversion
126-
g E41F eos_version.argtypes = None
127-
geos_version.restype = c_char_p
168+
geos_version = GEOSFuncFactory('GEOSversion', restype=c_char_p)
128169

129170
# Regular expression should be able to parse version strings such as
130171
# '3.0.0rc4-CAPI-1.3.3', '3.0.0-CAPI-1.4.1', '3.4.0dev-CAPI-1.8.0' or '3.4.0dev-CAPI-1.8.0 r0'
@@ -147,18 +188,3 @@ def geos_version_info():
147188
raise GEOSException('Could not parse version info string "%s"' % ver)
148189
return {key: m.group(key) for key in (
149190
'version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')}
150-
151-
# Version numbers and whether or not prepared geometry support is available.
152-
_verinfo = geos_version_info()
153-
GEOS_MAJOR_VERSION = int(_verinfo['major'])
154-
GEOS_MINOR_VERSION = int(_verinfo['minor'])
155-
GEOS_SUBMINOR_VERSION = int(_verinfo['subminor'])
156-
del _verinfo
157-
GEOS_VERSION = (GEOS_MAJOR_VERSION, GEOS_MINOR_VERSION, GEOS_SUBMINOR_VERSION)
158-
159-
# Here we set up the prototypes for the initGEOS_r and finishGEOS_r
160-
# routines. These functions aren't actually called until they are
161-
# attached to a GEOS context handle -- this actually occurs in
162-
# geos/prototypes/threadsafe.py.
163-
lgeos.initGEOS_r.restype = CONTEXT_PTR
164-
lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
Lines changed: 54 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
from ctypes import POINTER, c_double, c_int, c_uint
22

3-
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR
3+
from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, GEOSFuncFactory
44
from django.contrib.gis.geos.prototypes.errcheck import (
55
GEOSException, last_arg_byref,
66
)
7-
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
87

98

109
# ## Error-checking routines specific to coordinate sequences. ##
11-
def check_cs_ptr(result, func, cargs):
12-
"Error checking on routines that return Geometries."
13-
if not result:
14-
raise GEOSException(
15-
'Error encountered checking Coordinate Sequence returned from GEOS '
16-
'C function "%s".' % func.__name__
17-
)
18-
return result
19-
20-
2110
def check_cs_op(result, func, cargs):
2211
"Checks the status code of a coordinate sequence operation."
2312
if result == 0:
@@ -33,63 +22,73 @@ def check_cs_get(result, func, cargs):
3322
return last_arg_byref(cargs)
3423

3524

36-
# ## Coordinate sequence prototype generation functions. ##
37-
def cs_int(func):
25+
# ## Coordinate sequence prototype factory classes. ##
26+
class CsInt(GEOSFuncFactory):
3827
"For coordinate sequence routines that return an integer."
39-
func.argtypes = [CS_PTR, POINTER(c_uint)]
40-
func.restype = c_int
41-
func.errcheck = check_cs_get
42-
return func
28+
argtypes = [CS_PTR, POINTER(c_uint)]
29+
restype = c_int
30+
errcheck = staticmethod(check_cs_get)
4331

4432

45-
def cs_operation(func, ordinate=False, get=False):
33+
class CsOperation(GEOSFuncFactory):
4634
"For coordinate sequence operations."
47-
if get:
48-
# Get routines get double parameter passed-in by reference.
49-
func.errcheck = check_cs_get
50-
dbl_param = POINTER(c_double)
51-
else:
52-
func.errcheck = check_cs_op
53-
dbl_param = c_double
54-
55-
if ordinate:
56-
# Get/Set ordinate routines have an extra uint parameter.
57-
func.argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
58-
else:
59-
func.argtypes = [CS_PTR, c_uint, dbl_param]
60-
61-
func.restype = c_int
62-
return func
63-
35+
restype = c_int
36+
37+
def get_func(self, ordinate=False, get=False):
38+
if get:
39+
# Get routines have double parameter passed-in by reference.
40+
self.errcheck = check_cs_get
41+
dbl_param = POINTER(c_double)
42+
else:
43+
self.errcheck = check_cs_op
44+
dbl_param = c_double
45+
46+
if ordinate:
47+
# Get/Set ordinate routines have an extra uint parameter.
48+
self.argtypes = [CS_PTR, c_uint, c_uint, dbl_param]
49+
else:
50+
self.argtypes = [CS_PTR, c_uint, dbl_param]
51+
return super(CsOperation, self).get_func()
52+
53+
54+
class CsOutput(GEOSFuncFactory):
55+
restype = CS_PTR
56+
57+
def get_func(self, argtypes):
58+
self.argtypes = argtypes
59+
return super(CsOutput, self).get_func()
60+
61+
@staticmethod
62+
def errcheck(result, func, cargs):
63+
if not result:
64+
raise GEOSException(
65+
'Error encountered checking Coordinate Sequence returned from GEOS '
66+
'C function "%s".' % func.__name__
67+
)
68+
return result
6469

65-
def cs_output(func, argtypes):
66-
"For routines that return a coordinate sequence."
67-
func.argtypes = argtypes
68-
func.restype = CS_PTR
69-
func.errcheck = check_cs_ptr
70-
return func
7170

7271
# ## Coordinate Sequence ctypes prototypes ##
7372

7473
# Coordinate Sequence constructors & cloning.
75-
cs_clone = cs_output(GEOSFunc('GEOSCoordSeq_clone'), [CS_PTR])
76-
create_cs = cs_output(GEOSFunc('GEOSCoordSeq_create'), [c_uint, c_uint])
77-
get_cs = cs_output(GEOSFunc('GEOSGeom_getCoordSeq'), [GEOM_PTR])
74+
cs_clone = CsOutput('GEOSCoordSeq_clone', [CS_PTR])
75+
create_cs = CsOutput('GEOSCoordSeq_create', [c_uint, c_uint])
76+
get_cs = CsOutput('GEOSGeom_getCoordSeq', [GEOM_PTR])
7877

7978
# Getting, setting ordinate
80-
cs_getordinate = cs_operation(GEOSFunc('GEOSCoordSeq_getOrdinate'), ordinate=True, get=True)
81-
cs_setordinate = cs_operation(GEOSFunc('GEOSCoordSeq_setOrdinate'), ordinate=True)
79+
cs_getordinate = CsOperation('GEOSCoordSeq_getOrdinate', ordinate=True, get=True)
80+
cs_setordinate = CsOperation('GEOSCoordSeq_setOrdinate', ordinate=True)
8281

8382
# For getting, x, y, z
84-
cs_getx = cs_operation(GEOSFunc('GEOSCoordSeq_getX'), get=True)
85-
cs_gety = cs_operation(GEOSFunc('GEOSCoordSeq_getY'), get=True)
86-
cs_getz = cs_operation(GEOSFunc('GEOSCoordSeq_getZ'), get=True)
83+
cs_getx = CsOperation('GEOSCoordSeq_getX', get=True)
84+
cs_gety = CsOperation('GEOSCoordSeq_getY', get=True)
85+
cs_getz = CsOperation('GEOSCoordSeq_getZ', get=True)
8786

8887
# For setting, x, y, z
89-
cs_setx = cs_operation(GEOSFunc('GEOSCoordSeq_setX'))
90-
cs_sety = cs_operation(GEOSFunc('GEOSCoordSeq_setY'))
91-
cs_setz = cs_operation(GEOSFunc('GEOSCoordSeq_setZ'))
88+
cs_setx = CsOperation('GEOSCoordSeq_setX')
89+
cs_sety = CsOperation('GEOSCoordSeq_setY')
90+
cs_setz = CsOperation('GEOSCoordSeq_setZ')
9291

9392
# These routines return size & dimensions.
94-
cs_getsize = cs_int(GEOSFunc('GEOSCoordSeq_getSize'))
95-
cs_getdims = cs_int(GEOSFunc('GEOSCoordSeq_getDimensions'))
93+
cs_getsize = CsInt('GEOSCoordSeq_getSize')
94+
cs_getdims = CsInt('GEOSCoordSeq_getDimensions')

django/contrib/gis/geos/prototypes/errcheck.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
from ctypes import c_void_p, string_at
55

66
from django.contrib.gis.geos.error import GEOSException
7-
from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
7+
from django.contrib.gis.geos.libgeos import GEOSFuncFactory
88

99
# Getting the `free` routine used to free the memory allocated for
1010
# string pointers returned by GEOS.
11-
free = GEOSFunc('GEOSFree')
11+
free = GEOSFuncFactory('GEOSFree')
1212
free.argtypes = [c_void_p]
13-
free.restype = None
1413

1514

1615
def last_arg_byref(args):

0 commit comments

Comments
 (0)
0