-
Notifications
You must be signed in to change notification settings - Fork 206
/
_env.pyx
483 lines (374 loc) · 14.5 KB
/
_env.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
# cython: c_string_type=unicode, c_string_encoding=utf8
"""GDAL and OGR driver and configuration management
The main thread always utilizes CPLSetConfigOption. Child threads
utilize CPLSetThreadLocalConfigOption instead. All threads use
CPLGetConfigOption and not CPLGetThreadLocalConfigOption, thus child
threads will inherit config options from the main thread unless the
option is set to a new value inside the thread.
"""
include "gdal.pxi"
from collections import namedtuple
import logging
import os
import os.path
import sys
import threading
from fiona._err cimport exc_wrap_int, exc_wrap_ogrerr
from fiona._shim cimport set_proj_search_path, get_proj_version
from fiona._err import CPLE_BaseError
from fiona.errors import EnvError
level_map = {
0: 0,
1: logging.DEBUG,
2: logging.WARNING,
3: logging.ERROR,
4: logging.CRITICAL }
code_map = {
0: 'CPLE_None',
1: 'CPLE_AppDefined',
2: 'CPLE_OutOfMemory',
3: 'CPLE_FileIO',
4: 'CPLE_OpenFailed',
5: 'CPLE_IllegalArg',
6: 'CPLE_NotSupported',
7: 'CPLE_AssertionFailed',
8: 'CPLE_NoWriteAccess',
9: 'CPLE_UserInterrupt',
10: 'ObjectNull',
# error numbers 11-16 are introduced in GDAL 2.1. See
# https://github.com/OSGeo/gdal/pull/98.
11: 'CPLE_HttpResponse',
12: 'CPLE_AWSBucketNotFound',
13: 'CPLE_AWSObjectNotFound',
14: 'CPLE_AWSAccessDenied',
15: 'CPLE_AWSInvalidCredentials',
16: 'CPLE_AWSSignatureDoesNotMatch'}
log = logging.getLogger(__name__)
try:
import certifi
os.environ.setdefault("CURL_CA_BUNDLE", certifi.where())
except ImportError:
pass
cdef bint is_64bit = sys.maxsize > 2 ** 32
cdef _safe_osr_release(OGRSpatialReferenceH srs):
"""Wrapper to handle OSR release when NULL."""
if srs != NULL:
OSRRelease(srs)
srs = NULL
def calc_gdal_version_num(maj, min, rev):
"""Calculates the internal gdal version number based on major, minor and revision
GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013)
"""
if (maj, min, rev) >= (1, 10, 0):
return int(maj * 1000000 + min * 10000 + rev * 100)
else:
return int(maj * 1000 + min * 100 + rev * 10)
def get_gdal_version_num():
"""Return current internal version number of gdal"""
return int(GDALVersionInfo("VERSION_NUM"))
def get_gdal_release_name():
"""Return release name of gdal"""
cdef const char *name_c = NULL
name_c = GDALVersionInfo("RELEASE_NAME")
name = name_c
return name
GDALVersion = namedtuple("GDALVersion", ["major", "minor", "revision"])
def get_gdal_version_tuple():
"""
Calculates gdal version tuple from gdal's internal version number.
GDAL Version Information macro changed with GDAL version 1.10.0 (April 2013)
"""
gdal_version_num = get_gdal_version_num()
if gdal_version_num >= calc_gdal_version_num(1, 10, 0):
major = gdal_version_num // 1000000
minor = (gdal_version_num - (major * 1000000)) // 10000
revision = (gdal_version_num - (major * 1000000) - (minor * 10000)) // 100
return GDALVersion(major, minor, revision)
else:
major = gdal_version_num // 1000
minor = (gdal_version_num - (major * 1000)) // 100
revision = (gdal_version_num - (major * 1000) - (minor * 100)) // 10
return GDALVersion(major, minor, revision)
def get_proj_version_tuple():
"""
Returns proj version tuple for gdal >= 3.0.1, otherwise None
"""
cdef int major
cdef int minor
cdef int patch
gdal_version_num = get_gdal_version_num()
if gdal_version_num < calc_gdal_version_num(3, 0, 1):
proj_version = None
else:
get_proj_version(&major, &minor, &patch)
return (major, minor, patch)
cdef void log_error(CPLErr err_class, int err_no, const char* msg) with gil:
"""Send CPL debug messages and warnings to Python's logger."""
log = logging.getLogger(__name__)
if err_no in code_map:
log.log(level_map[err_class], "%s", msg)
else:
log.info("Unknown error number %r.", err_no)
# Definition of GDAL callback functions, one for Windows and one for
# other platforms. Each calls log_error().
IF UNAME_SYSNAME == "Windows":
cdef void __stdcall logging_error_handler(CPLErr err_class, int err_no,
const char* msg) with gil:
log_error(err_class, err_no, msg)
ELSE:
cdef void logging_error_handler(CPLErr err_class, int err_no,
const char* msg) with gil:
log_error(err_class, err_no, msg)
def driver_count():
"""Return the count of all drivers"""
return GDALGetDriverCount() + OGRGetDriverCount()
cpdef get_gdal_config(key, normalize=True):
"""Get the value of a GDAL configuration option. When requesting
``GDAL_CACHEMAX`` the value is returned unaltered.
Parameters
----------
key : str
Name of config option.
normalize : bool, optional
Convert values of ``"ON"'`` and ``"OFF"`` to ``True`` and ``False``.
"""
key = key.encode('utf-8')
# GDAL_CACHEMAX is a special case
if key.lower() == b'gdal_cachemax':
if is_64bit:
return GDALGetCacheMax64()
else:
return GDALGetCacheMax()
else:
val = CPLGetConfigOption(<const char *>key, NULL)
if not val:
return None
elif not normalize:
return val
elif val.isdigit():
return int(val)
else:
if val == u'ON':
return True
elif val == u'OFF':
return False
else:
return val
cpdef set_gdal_config(key, val, normalize=True):
"""Set a GDAL configuration option's value.
Parameters
----------
key : str
Name of config option.
normalize : bool, optional
Convert ``True`` to `"ON"` and ``False`` to `"OFF"``.
"""
key = key.encode('utf-8')
# GDAL_CACHEMAX is a special case
if key.lower() == b'gdal_cachemax':
if is_64bit:
GDALSetCacheMax64(val)
else:
GDALSetCacheMax(val)
return
elif normalize and isinstance(val, bool):
val = ('ON' if val and val else 'OFF').encode('utf-8')
else:
# Value could be an int
val = str(val).encode('utf-8')
if isinstance(threading.current_thread(), threading._MainThread):
CPLSetConfigOption(<const char *>key, <const char *>val)
else:
CPLSetThreadLocalConfigOption(<const char *>key, <const char *>val)
cpdef del_gdal_config(key):
"""Delete a GDAL configuration option.
Parameters
----------
key : str
Name of config option.
"""
key = key.encode('utf-8')
if isinstance(threading.current_thread(), threading._MainThread):
CPLSetConfigOption(<const char *>key, NULL)
else:
CPLSetThreadLocalConfigOption(<const char *>key, NULL)
cdef class ConfigEnv(object):
"""Configuration option management"""
def __init__(self, **options):
self.options = options.copy()
self.update_config_options(**self.options)
def update_config_options(self, **kwargs):
"""Update GDAL config options."""
for key, val in kwargs.items():
set_gdal_config(key, val)
self.options[key] = val
def clear_config_options(self):
"""Clear GDAL config options."""
while self.options:
key, val = self.options.popitem()
del_gdal_config(key)
def get_config_options(self):
return {k: get_gdal_config(k) for k in self.options}
class GDALDataFinder(object):
"""Finds GDAL data files
Note: this is not part of the 1.8.x public API.
"""
def find_file(self, basename):
"""Returns path of a GDAL data file or None
Parameters
----------
basename : str
Basename of a data file such as "header.dxf"
Returns
-------
str (on success) or None (on failure)
"""
cdef const char *path_c = NULL
basename_b = basename.encode('utf-8')
path_c = CPLFindFile("gdal", <const char *>basename_b)
if path_c == NULL:
return None
else:
path = path_c
return path
def search(self, prefix=None):
"""Returns GDAL data directory
Note well that os.environ is not consulted.
Returns
-------
str or None
"""
path = self.search_wheel(prefix or __file__)
if not path:
path = self.search_prefix(prefix or sys.prefix)
if not path:
path = self.search_debian(prefix or sys.prefix)
return path
def search_wheel(self, prefix=None):
"""Check wheel location"""
if prefix is None:
prefix = __file__
datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "gdal_data"))
return datadir if os.path.exists(os.path.join(datadir, 'header.dxf')) else None
def search_prefix(self, prefix=sys.prefix):
"""Check sys.prefix location"""
datadir = os.path.join(prefix, 'share', 'gdal')
return datadir if os.path.exists(os.path.join(datadir, 'header.dxf')) else None
def search_debian(self, prefix=sys.prefix):
"""Check Debian locations"""
gdal_release_name = GDALVersionInfo("RELEASE_NAME")
datadir = os.path.join(prefix, 'share', 'gdal', '{}.{}'.format(*gdal_release_name.split('.')[:2]))
return datadir if os.path.exists(os.path.join(datadir, 'header.dxf')) else None
class PROJDataFinder(object):
"""Finds PROJ data files
Note: this is not part of the public 1.8.x API.
"""
def has_data(self):
"""Returns True if PROJ's data files can be found
Returns
-------
bool
"""
cdef OGRSpatialReferenceH osr = OSRNewSpatialReference(NULL)
try:
exc_wrap_ogrerr(exc_wrap_int(OSRImportFromEPSG(osr, 4326)))
except CPLE_BaseError:
return False
else:
return True
finally:
_safe_osr_release(osr)
def search(self, prefix=None):
"""Returns PROJ data directory
Note well that os.environ is not consulted.
Returns
-------
str or None
"""
path = self.search_wheel(prefix or __file__)
if not path:
path = self.search_prefix(prefix or sys.prefix)
return path
def search_wheel(self, prefix=None):
"""Check wheel location"""
if prefix is None:
prefix = __file__
datadir = os.path.abspath(os.path.join(os.path.dirname(prefix), "proj_data"))
return datadir if os.path.exists(datadir) else None
def search_prefix(self, prefix=sys.prefix):
"""Check sys.prefix location"""
datadir = os.path.join(prefix, 'share', 'proj')
return datadir if os.path.exists(datadir) else None
cdef class GDALEnv(ConfigEnv):
"""Configuration and driver management"""
def __init__(self, **options):
super(GDALEnv, self).__init__(**options)
self._have_registered_drivers = False
def start(self):
CPLPushErrorHandler(<CPLErrorHandler>logging_error_handler)
# The outer if statement prevents each thread from acquiring a
# lock when the environment starts, and the inner avoids a
# potential race condition.
if not self._have_registered_drivers:
with threading.Lock():
if not self._have_registered_drivers:
GDALAllRegister()
OGRRegisterAll()
if 'GDAL_DATA' in os.environ:
log.debug("GDAL_DATA found in environment.")
self.update_config_options(GDAL_DATA=os.environ['GDAL_DATA'])
else:
path = GDALDataFinder().search_wheel()
if path:
log.debug("GDAL data found in package: path=%r.", path)
self.update_config_options(GDAL_DATA=path)
# See https://github.com/mapbox/rasterio/issues/1631.
elif GDALDataFinder().find_file("header.dxf"):
log.debug("GDAL data files are available at built-in paths.")
else:
path = GDALDataFinder().search()
if path:
log.debug("GDAL data found in other locations: path=%r.", path)
self.update_config_options(GDAL_DATA=path)
if 'PROJ_LIB' in os.environ:
log.debug("PROJ_LIB found in environment.")
path = os.environ["PROJ_LIB"]
set_proj_data_search_path(path)
else:
path = PROJDataFinder().search_wheel()
if path:
log.debug("PROJ data found in package: path=%r.", path)
set_proj_data_search_path(path)
elif PROJDataFinder().has_data():
log.debug("PROJ data files are available at built-in paths.")
else:
path = PROJDataFinder().search()
if path:
log.debug("PROJ data found in other locations: path=%r.", path)
set_proj_data_search_path(path)
if driver_count() == 0:
CPLPopErrorHandler()
raise ValueError("Drivers not registered.")
# Flag the drivers as registered, otherwise every thread
# will acquire a threadlock every time a new environment
# is started rather than just whenever the first thread
# actually makes it this far.
self._have_registered_drivers = True
def stop(self):
# NB: do not restore the CPL error handler to its default
# state here. If you do, log messages will be written to stderr
# by GDAL instead of being sent to Python's logging module.
CPLPopErrorHandler()
def drivers(self):
cdef OGRSFDriverH driver = NULL
cdef int i
result = {}
for i in range(OGRGetDriverCount()):
drv = OGRGetDriver(i)
key = <char *>OGR_Dr_GetName(drv)
val = <char *>OGR_Dr_GetName(drv)
result[key] = val
return result
def set_proj_data_search_path(path):
"""Set PROJ data search path"""
set_proj_search_path(path)