253 lines
8.2 KiB
Python
253 lines
8.2 KiB
Python
"""
|
|
Minimal proxy to a GEOS C dynamic library, which is system dependant
|
|
|
|
Two environment variables influence this module: GEOS_LIBRARY_PATH and/or
|
|
GEOS_CONFIG.
|
|
|
|
If GEOS_LIBRARY_PATH is set to a path to a GEOS C shared library, this is
|
|
used. Otherwise GEOS_CONFIG can be set to a path to `geos-config`. If
|
|
`geos-config` is already on the PATH environment variable, then it will
|
|
be used to help better guess the name for the GEOS C dynamic library.
|
|
"""
|
|
|
|
from ctypes import CDLL, cdll, c_void_p, c_char_p
|
|
from ctypes.util import find_library
|
|
import os
|
|
import logging
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
# Add message handler to this module's logger
|
|
log = logging.getLogger(__name__)
|
|
ch = logging.StreamHandler()
|
|
log.addHandler(ch)
|
|
|
|
if 'all' in sys.warnoptions:
|
|
# show GEOS messages in console with: python -W all
|
|
log.setLevel(logging.DEBUG)
|
|
|
|
|
|
# The main point of this module is to load a dynamic library to this variable
|
|
lgeos = None
|
|
|
|
# First try: use GEOS_LIBRARY_PATH environment variable
|
|
if 'GEOS_LIBRARY_PATH' in os.environ:
|
|
geos_library_path = os.environ['GEOS_LIBRARY_PATH']
|
|
try:
|
|
lgeos = CDLL(geos_library_path)
|
|
except:
|
|
log.warning(
|
|
'cannot open shared object from GEOS_LIBRARY_PATH: %s',
|
|
geos_library_path)
|
|
if lgeos:
|
|
if hasattr(lgeos, 'GEOSversion'):
|
|
log.debug('found GEOS C library using GEOS_LIBRARY_PATH')
|
|
else:
|
|
raise OSError(
|
|
'shared object GEOS_LIBRARY_PATH is not a GEOS C library: '
|
|
+ str(geos_library_path))
|
|
|
|
# Second try: use GEOS_CONFIG environment variable
|
|
if 'GEOS_CONFIG' in os.environ:
|
|
geos_config = os.environ['GEOS_CONFIG']
|
|
log.debug('geos_config: %s', geos_config)
|
|
else:
|
|
geos_config = 'geos-config'
|
|
|
|
|
|
def get_geos_config(option):
|
|
'''Get configuration option from the `geos-config` development utility
|
|
|
|
Path to utility is set with a module-level `geos_config` variable, which
|
|
can be changed or unset.
|
|
'''
|
|
geos_config = globals().get('geos_config')
|
|
if not geos_config or not isinstance(geos_config, str):
|
|
raise OSError('Path to geos-config is not set')
|
|
try:
|
|
stdout, stderr = subprocess.Popen(
|
|
[geos_config, option],
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
|
|
except OSError as ex:
|
|
# e.g., [Errno 2] No such file or directory
|
|
raise OSError(
|
|
'Could not find geos-config %r: %s' % (geos_config, ex))
|
|
if stderr and not stdout:
|
|
raise ValueError(stderr.strip())
|
|
if sys.version_info[0] >= 3:
|
|
result = stdout.decode('ascii').strip()
|
|
else:
|
|
result = stdout.strip()
|
|
log.debug('%s %s: %r', geos_config, option, result)
|
|
return result
|
|
|
|
# Now try and use the utility to load from `geos-config --clibs` with
|
|
# some magic smoke to guess the other parts of the library name
|
|
try:
|
|
clibs = get_geos_config('--clibs')
|
|
except OSError:
|
|
geos_config = None
|
|
if not lgeos and geos_config:
|
|
base = ''
|
|
name = 'geos_c'
|
|
for item in clibs.split():
|
|
if item.startswith("-L"):
|
|
base = item[2:]
|
|
elif item.startswith("-l"):
|
|
name = item[2:]
|
|
# Now guess the actual library name using a list of possible formats
|
|
if sys.platform == 'win32':
|
|
# Unlikely, since geos-config is a shell script, but you never know...
|
|
fmts = ['{name}.dll']
|
|
elif sys.platform == 'darwin':
|
|
fmts = ['lib{name}.dylib', '{name}.dylib', '{name}.framework/{name}']
|
|
elif os.name == 'posix':
|
|
fmts = ['lib{name}.so', 'lib{name}.so.1']
|
|
guesses = []
|
|
for fmt in fmts:
|
|
lib_name = fmt.format(name=name)
|
|
geos_library_path = os.path.join(base, lib_name)
|
|
try:
|
|
lgeos = CDLL(geos_library_path)
|
|
break
|
|
except:
|
|
guesses.append(geos_library_path)
|
|
if lgeos:
|
|
if hasattr(lgeos, 'GEOSversion'):
|
|
log.debug('found GEOS C library using geos-config')
|
|
else:
|
|
raise OSError(
|
|
'shared object found by geos-config is not a GEOS C library: '
|
|
+ str(geos_library_path))
|
|
else:
|
|
log.warning(
|
|
"cannot open shared object from '%s --clibs': %r",
|
|
geos_config, clibs)
|
|
log.warning(
|
|
"there were %d guess(es) for this path:\n\t%s",
|
|
len(guesses), '\n\t'.join(guesses))
|
|
|
|
|
|
# Platform-specific attempts, and build `free` object
|
|
|
|
def load_dll(libname, fallbacks=None):
|
|
lib = find_library(libname)
|
|
dll = None
|
|
if lib is not None:
|
|
try:
|
|
log.debug("Trying `CDLL(%s)`", lib)
|
|
dll = CDLL(lib)
|
|
except OSError:
|
|
log.warning("Failed `CDLL(%s)`", lib)
|
|
pass
|
|
|
|
if not dll and fallbacks is not None:
|
|
for name in fallbacks:
|
|
try:
|
|
log.debug("Trying `CDLL(%s)`", name)
|
|
dll = CDLL(name)
|
|
except OSError:
|
|
# move on to the next fallback
|
|
log.warning("Failed `CDLL(%s)`", name)
|
|
pass
|
|
|
|
if dll:
|
|
log.debug("Library path: %r", lib or name)
|
|
log.debug("DLL: %r", dll)
|
|
return dll
|
|
else:
|
|
# No shared library was loaded. Raise OSError.
|
|
raise OSError(
|
|
"Could not find library {} or load any of its variants {}".format(
|
|
libname, fallbacks or []))
|
|
|
|
|
|
if sys.platform.startswith('linux'):
|
|
if not lgeos:
|
|
lgeos = load_dll('geos_c',
|
|
fallbacks=['libgeos_c.so.1', 'libgeos_c.so'])
|
|
free = load_dll('c', fallbacks=['libc.so.6']).free
|
|
free.argtypes = [c_void_p]
|
|
free.restype = None
|
|
|
|
elif sys.platform == 'darwin':
|
|
if not lgeos:
|
|
if hasattr(sys, 'frozen'):
|
|
# .app file from py2app
|
|
alt_paths = [os.path.join(os.environ['RESOURCEPATH'],
|
|
'..', 'Frameworks', 'libgeos_c.dylib')]
|
|
else:
|
|
alt_paths = [
|
|
# The Framework build from Kyng Chaos
|
|
"/Library/Frameworks/GEOS.framework/Versions/Current/GEOS",
|
|
# macports
|
|
'/opt/local/lib/libgeos_c.dylib',
|
|
# homebrew
|
|
'/usr/local/lib/libgeos_c.dylib',
|
|
]
|
|
lgeos = load_dll('geos_c', fallbacks=alt_paths)
|
|
|
|
free = load_dll('c', fallbacks=['/usr/lib/libc.dylib']).free
|
|
free.argtypes = [c_void_p]
|
|
free.restype = None
|
|
|
|
elif sys.platform == 'win32':
|
|
if not lgeos:
|
|
try:
|
|
egg_dlls = os.path.abspath(
|
|
os.path.join(os.path.dirname(__file__), "DLLs"))
|
|
wininst_dlls = os.path.abspath(os.__file__ + "../../../DLLs")
|
|
original_path = os.environ['PATH']
|
|
os.environ['PATH'] = "%s;%s;%s" % \
|
|
(egg_dlls, wininst_dlls, original_path)
|
|
lgeos = CDLL("geos_c.dll")
|
|
except (ImportError, WindowsError, OSError):
|
|
raise
|
|
|
|
def free(m):
|
|
try:
|
|
cdll.msvcrt.free(m)
|
|
except WindowsError:
|
|
# TODO: http://web.archive.org/web/20070810024932/
|
|
# + http://trac.gispython.org/projects/PCL/ticket/149
|
|
pass
|
|
|
|
elif sys.platform == 'sunos5':
|
|
if not lgeos:
|
|
lgeos = load_dll('geos_c',
|
|
fallbacks=['libgeos_c.so.1', 'libgeos_c.so'])
|
|
free = CDLL('libc.so.1').free
|
|
free.argtypes = [c_void_p]
|
|
free.restype = None
|
|
|
|
else: # other *nix systems
|
|
if not lgeos:
|
|
lgeos = load_dll('geos_c',
|
|
fallbacks=['libgeos_c.so.1', 'libgeos_c.so'])
|
|
free = load_dll('c', fallbacks=['libc.so.6']).free
|
|
free.argtypes = [c_void_p]
|
|
free.restype = None
|
|
|
|
# TODO: what to do with 'free'? It isn't used.
|
|
|
|
|
|
def _geos_version():
|
|
# extern const char GEOS_DLL *GEOSversion();
|
|
GEOSversion = lgeos.GEOSversion
|
|
GEOSversion.restype = c_char_p
|
|
GEOSversion.argtypes = []
|
|
# #define GEOS_CAPI_VERSION "@VERSION@-CAPI-@CAPI_VERSION@"
|
|
geos_version_string = GEOSversion()
|
|
if sys.version_info[0] >= 3:
|
|
geos_version_string = geos_version_string.decode('ascii')
|
|
|
|
res = re.findall(r'(\d+)\.(\d+)\.(\d+)', geos_version_string)
|
|
geos_version = tuple(int(x) for x in res[0])
|
|
capi_version = tuple(int(x) for x in res[1])
|
|
|
|
return geos_version_string, geos_version, capi_version
|
|
|
|
geos_version_string, geos_version, geos_capi_version = _geos_version()
|