[python-shapely] 01/14: Imported Upstream version 1.5.10
Sebastiaan Couwenberg
sebastic at moszumanska.debian.org
Fri Aug 28 14:21:44 UTC 2015
This is an automated email from the git hooks/post-receive script.
sebastic pushed a commit to branch master
in repository python-shapely.
commit 7369eeff99cc91f59fa34c04d4159298906a2fab
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date: Fri Aug 28 14:51:41 2015 +0200
Imported Upstream version 1.5.10
---
.gitignore | 1 +
CHANGES.txt | 5 +
README.rst | 7 +-
docs/manual.rst | 42 +++++++-
requirements-dev.txt | 1 +
setup.py | 190 +++++++++++++++++++++++----------
shapely/__init__.py | 2 +-
shapely/ctypes_declarations.py | 22 +++-
shapely/geometry/base.py | 29 +++--
shapely/geometry/linestring.py | 15 +--
shapely/geometry/multipoint.py | 2 +-
shapely/geometry/polygon.py | 4 +-
shapely/geos.py | 150 ++++++--------------------
shapely/impl.py | 31 +++++-
shapely/libgeos.py | 236 +++++++++++++++++++++++++++++++++++++++++
shapely/linref.py | 4 +-
shapely/speedups/__init__.py | 2 +-
tests/__init__.py | 9 +-
tests/test_default_impl.py | 24 +++++
tests/test_dlls.py | 2 +-
tests/test_multi.py | 8 ++
tests/test_multilinestring.py | 10 +-
tests/test_multipoint.py | 9 +-
tests/test_multipolygon.py | 10 +-
tests/test_operations.py | 11 +-
tests/test_parallel_offset.py | 33 ++++++
tests/test_predicates.py | 23 +++-
27 files changed, 663 insertions(+), 219 deletions(-)
diff --git a/.gitignore b/.gitignore
index 7662bd5..76a880b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.pyc
+*.pyo
*.c
*.so
diff --git a/CHANGES.txt b/CHANGES.txt
index 93f3772..b66b6ac 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,11 @@
Changes
=======
+1.5.10 (2015-08-22)
+-------------------
+- Monkey patch affinity module by absolute reference (#299).
+- Raise TopologicalError in relate() instead of crashing (#294, #295, #303).
+
1.5.9 (2015-05-27)
------------------
- Fix for 64 bit speedups compatibility (#274).
diff --git a/README.rst b/README.rst
index ea892c0..f80bba3 100644
--- a/README.rst
+++ b/README.rst
@@ -44,12 +44,13 @@ system library path, and install Shapely from the Python package index.
$ pip install shapely
-If you've installed GEOS to a non-standard location, you can use the
-geos-config program to find compiler and linker options.
+If you've installed GEOS to a non-standard location, the geos-config program
+will be used to get compiler and linker options. If it is not on the PATH,
+it can be specified with a GEOS_CONFIG environment variable, e.g.:
.. code-block:: console
- $ CFLAGS=`geos-config --cflags` LDFLAGS=`geos-config --clibs` pip install shapely
+ $ GEOS_CONFIG=/path/to/geos-config pip install shapely
If your system's GEOS version is < 3.3.0 you cannot use Shapely 1.3+ and must
stick to 1.2.x as shown below.
diff --git a/docs/manual.rst b/docs/manual.rst
index 90b0a3d..07971b8 100644
--- a/docs/manual.rst
+++ b/docs/manual.rst
@@ -1054,8 +1054,9 @@ differently.
.. method:: object.contains(other)
- Returns ``True`` if the object's `interior` contains the `boundary` and
- `interior` of the other object and their boundaries do not touch at all.
+ Returns ``True`` if no points of `other` lie in the exterior of the `object`
+ and at least one point of the interior of `other` lies in the interior of
+ `object`.
This predicate applies to all types, and is inverse to :meth:`within`. The
expression ``a.contains(b) == b.within(a)`` always evaluates to ``True``.
@@ -1124,8 +1125,8 @@ This predicate applies to all types and is the inverse of :meth:`intersects`.
Returns ``True`` if the `boundary` and `interior` of the object intersect in
any way with those of the other.
-This predicate is equivalent to the OR-ing of :meth:`contains`, :meth:`crosses`,
-:meth:`equals`, :meth:`touches`, and :meth:`within`.
+In other words, geometric objects intersect if they have any boundary or
+interior point in common.
.. method:: object.touches(other)
@@ -1234,6 +1235,37 @@ elements.
>>> Point(0, 0).relate(LineString([(0, 0), (1, 1)]))
'F0FFFF102'
+.. method:: object.relate_pattern(other, pattern)
+
+ Returns True if the DE-9IM string code for the relationship between the
+ geometries satisfies the pattern, otherwise False.
+
+The :meth:`relate_pattern` compares the DE-9IM code string for two geometries
+against a specified pattern. If the string matches the pattern then ``True`` is
+returned, otherwise ``False``. The pattern specified can be an exact match
+(``0``, ``1`` or ``2``), a boolean match (``T`` or ``F``), or a wildcard
+(``*``). For example, the pattern for the `within` predicate is ``T*****FF*``.
+
+.. code-block:: pycon
+
+ >> point = Point(0.5, 0.5)
+ >> square = Polygon([(0, 0), (0, 1), (1, 1), (1, 0)])
+ >> square.relate_pattern(point, 'T*****FF*')
+ True
+ >> point.within(square)
+ True
+
+Note that the order or the geometries is significant, as demonstrated below.
+In this example the square contains the point, but the point does not contain
+the square.
+
+.. code-block:: pycon
+
+ >>> point.relate(square)
+ '0FFFFF212'
+ >>> square.relate(point)
+ '0F2FF1FF2'
+
Further discussion of the DE-9IM matrix is beyond the scope of this manual. See
[4]_ and http://pypi.python.org/pypi/de9im.
@@ -2037,7 +2069,7 @@ one geometry to the vertices in a second geometry with a given tolerance.
The `tolerance` argument specifies the minimum distance between vertices for
them to be snapped.
- `New in version 1.4.5`
+ `New in version 1.5.0`
.. code-block:: pycon
diff --git a/requirements-dev.txt b/requirements-dev.txt
index b16d71b..04f77c6 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -2,4 +2,5 @@ setuptools
Numpy>=1.8.0
Cython>=0.19
descartes==1.0.1
+packaging
pytest
diff --git a/setup.py b/setup.py
index 9748470..b2b7bd5 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,24 @@
#!/usr/bin/env python
-from __future__ import print_function
+# Two environment variables influence this script.
+#
+# GEOS_LIBRARY_PATH: a path to a GEOS C shared library.
+#
+# GEOS_CONFIG: the path to a geos-config program that points to GEOS version,
+# headers, and libraries.
+#
+# NB: within this setup scripts, software versions are evaluated according
+# to https://www.python.org/dev/peps/pep-0440/.
+import errno
+import glob
+import logging
+import os
+import platform
+import re
+import shutil
+import subprocess
+import sys
try:
# If possible, use setuptools
from setuptools import setup
@@ -11,36 +28,54 @@ except ImportError:
from distutils.core import setup
from distutils.extension import Extension
from distutils.command.build_ext import build_ext as distutils_build_ext
-from distutils.cmd import Command
from distutils.errors import CCompilerError, DistutilsExecError, \
DistutilsPlatformError
-from distutils.sysconfig import get_config_var
-import errno
-import glob
-import os
-import platform
-import shutil
-import subprocess
-import sys
+from packaging.version import Version
+
+# Get geos_version from GEOS dynamic library, which depends on
+# GEOS_LIBRARY_PATH and/or GEOS_CONFIG environment variables
+from shapely.libgeos import geos_version_string, geos_version, \
+ geos_config, get_geos_config
+
+logging.basicConfig()
+log = logging.getLogger(__file__)
+
+# python -W all setup.py ...
+if 'all' in sys.warnoptions:
+ log.level = logging.DEBUG
# Get the version from the shapely module
-version = None
+shapely_version = None
with open('shapely/__init__.py', 'r') as fp:
for line in fp:
- if "__version__" in line:
- exec(line.replace('_', ''))
+ if line.startswith("__version__"):
+ shapely_version = Version(
+ line.split("=")[1].strip().strip("\"'"))
break
-if version is None:
+
+if not shapely_version:
raise ValueError("Could not determine Shapely's version")
+# Fail installation if the GEOS shared library does not meet the minimum
+# version. We ship it with Shapely for Windows, so no need to check on
+# that platform.
+log.debug('GEOS shared library: %s %s', geos_version_string, geos_version)
+if (set(sys.argv).intersection(['install', 'build', 'build_ext']) and
+ shapely_version >= Version('1.3') and
+ geos_version < (3, 3)):
+ log.critical(
+ "Shapely >= 1.3 requires GEOS >= 3.3. "
+ "Install GEOS 3.3+ and reinstall Shapely.")
+ sys.exit(1)
+
# Handle UTF-8 encoding of certain text files.
open_kwds = {}
if sys.version_info >= (3,):
open_kwds['encoding'] = 'utf-8'
with open('VERSION.txt', 'w', **open_kwds) as fp:
- fp.write(version)
+ fp.write(str(shapely_version))
with open('README.rst', 'r', **open_kwds) as fp:
readme = fp.read()
@@ -55,8 +90,8 @@ long_description = readme + '\n\n' + credits + '\n\n' + changes
setup_args = dict(
name = 'Shapely',
- version = version,
- requires = ['Python (>=2.6)', 'libgeos_c (>=3.1)'],
+ version = str(shapely_version),
+ requires = ['Python (>=2.6)', 'libgeos_c (>=3.3)'],
description = 'Geometric objects, predicates, and operations',
license = 'BSD',
keywords = 'geometry topology gis',
@@ -74,7 +109,6 @@ setup_args = dict(
'shapely.speedups',
'shapely.vectorized',
],
- cmdclass = {},
classifiers = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
@@ -86,7 +120,8 @@ setup_args = dict(
'Programming Language :: Python :: 3',
'Topic :: Scientific/Engineering :: GIS',
],
- data_files = [('shapely', ['shapely/_geos.pxi'])]
+ data_files = [('shapely', ['shapely/_geos.pxi'])],
+ cmdclass = {},
)
# Add DLLs for Windows
@@ -111,6 +146,47 @@ if sys.platform == 'win32':
)
+# Prepare build opts and args for the speedups extension module.
+include_dirs = []
+library_dirs = []
+libraries = []
+extra_link_args = []
+
+try:
+ # Get the version from geos-config. Show error if this version tuple is
+ # different to the GEOS version loaded from the dynamic library.
+ geos_config_version_string = get_geos_config('--version')
+ res = re.findall(r'(\d+)\.(\d+)\.(\d+)', geos_config_version_string)
+ geos_config_version = tuple(int(x) for x in res[0])
+
+ if geos_config_version != geos_version:
+ log.error("The GEOS dynamic library version is %s %s,",
+ geos_version_string, geos_version)
+ log.error("but the version reported by %s is %s %s.", geos_config,
+ geos_config_version_string, geos_config_version)
+ sys.exit(1)
+except OSError as ex:
+ log.error(ex)
+ log.error('Cannot find geos-config to get headers and check version.')
+ log.error('If available, specify a path to geos-config with a '
+ 'GEOS_CONFIG environment variable')
+ geos_config = None
+
+if geos_config:
+ # Collect other options from GEOS
+ for item in get_geos_config('--cflags').split():
+ if item.startswith("-I"):
+ include_dirs.extend(item[2:].split(":"))
+ for item in get_geos_config('--clibs').split():
+ if item.startswith("-L"):
+ library_dirs.extend(item[2:].split(":"))
+ elif item.startswith("-l"):
+ libraries.append(item[2:])
+ else:
+ # e.g. -framework GEOS
+ extra_link_args.append(item)
+
+
# Optional compilation of speedups
# setuptools stuff from Bob Ippolito's simplejson project
if sys.platform == 'win32' and sys.version_info > (2, 6):
@@ -149,10 +225,6 @@ if (hasattr(platform, 'python_implementation')
# python_implementation is only available since 2.6
ext_modules = []
libraries = []
-elif sys.platform == 'win32':
- libraries = ['geos']
-else:
- libraries = ['geos_c']
if os.path.exists("MANIFEST.in"):
@@ -166,67 +238,77 @@ if os.path.exists("MANIFEST.in"):
try:
if (force_cython or not os.path.exists(c_file)
or os.path.getmtime(pyx_file) > os.path.getmtime(c_file)):
- print("Updating C extension with Cython.", file=sys.stderr)
+ log.info("Updating C extension with Cython.")
subprocess.check_call(["cython", "shapely/speedups/_speedups.pyx"])
except (subprocess.CalledProcessError, OSError):
- print("Warning: Could not (re)create C extension with Cython.",
- file=sys.stderr)
+ log.warn("Could not (re)create C extension with Cython.")
if force_cython:
raise
- if not os.path.exists("shapely/speedups/_speedups.c"):
- print("Warning: speedup extension not found", file=sys.stderr)
+ if not os.path.exists(c_file):
+ log.warn("speedup extension not found")
ext_modules = [
Extension(
"shapely.speedups._speedups",
["shapely/speedups/_speedups.c"],
+ include_dirs=include_dirs,
+ library_dirs=library_dirs,
libraries=libraries,
- include_dirs=[get_config_var('INCLUDEDIR')],),
+ extra_link_args=extra_link_args,
+ ),
]
cmd_classes = setup_args.setdefault('cmdclass', {})
try:
- import numpy as np
+ import numpy
from Cython.Distutils import build_ext as cython_build_ext
from distutils.extension import Extension as DistutilsExtension
- if 'build_ext' in cmd_classes:
+ if 'build_ext' in setup_args['cmdclass']:
raise ValueError('We need to put the Cython build_ext in '
'cmd_classes, but it is already defined.')
- cmd_classes['build_ext'] = cython_build_ext
-
- ext_modules.append(DistutilsExtension("shapely.vectorized._vectorized",
- sources=["shapely/vectorized/_vectorized.pyx"],
- libraries=libraries + [np.get_include()],
- include_dirs=[get_config_var('INCLUDEDIR'),
- np.get_include()],
- ))
+ setup_args['cmdclass']['build_ext'] = cython_build_ext
+
+ include_dirs.append(numpy.get_include())
+ libraries.append(numpy.get_include())
+
+ ext_modules.append(DistutilsExtension(
+ "shapely.vectorized._vectorized",
+ sources=["shapely/vectorized/_vectorized.pyx"],
+ include_dirs=include_dirs,
+ library_dirs=library_dirs,
+ libraries=libraries,
+ extra_link_args=extra_link_args,
+ ))
except ImportError:
- print("Numpy or Cython not available, shapely.vectorized submodule not "
- "being built.")
+ log.info("Numpy or Cython not available, shapely.vectorized submodule "
+ "not being built.")
try:
# try building with speedups
- existing_build_ext = setup_args['cmdclass'].get('build_ext', distutils_build_ext)
- setup_args['cmdclass']['build_ext'] = construct_build_ext(existing_build_ext)
- setup(
- ext_modules=ext_modules,
- **setup_args
- )
+ existing_build_ext = setup_args['cmdclass'].\
+ get('build_ext', distutils_build_ext)
+ setup_args['cmdclass']['build_ext'] = \
+ construct_build_ext(existing_build_ext)
+ setup(ext_modules=ext_modules, **setup_args)
except BuildFailed as ex:
- BUILD_EXT_WARNING = "Warning: The C extension could not be compiled, " \
+ BUILD_EXT_WARNING = "The C extension could not be compiled, " \
"speedups are not enabled."
- print(ex)
- print(BUILD_EXT_WARNING)
- print("Failure information, if any, is above.")
- print("I'm retrying the build without the C extension now.")
+ log.warn(ex)
+ log.warn(BUILD_EXT_WARNING)
+ log.warn("Failure information, if any, is above.")
+ log.warn("I'm retrying the build without the C extension now.")
+
+ # Remove any previously defined build_ext command class.
+ if 'build_ext' in setup_args['cmdclass']:
+ del setup_args['cmdclass']['build_ext']
if 'build_ext' in cmd_classes:
del cmd_classes['build_ext']
setup(**setup_args)
- print(BUILD_EXT_WARNING)
- print("Plain-Python installation succeeded.")
+ log.warn(BUILD_EXT_WARNING)
+ log.info("Plain-Python installation succeeded.")
diff --git a/shapely/__init__.py b/shapely/__init__.py
index 424ebc4..63d18fb 100644
--- a/shapely/__init__.py
+++ b/shapely/__init__.py
@@ -1 +1 @@
-__version__ = "1.5.9"
+__version__ = "1.5.10"
diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py
index 3ee5141..ac38fc5 100644
--- a/shapely/ctypes_declarations.py
+++ b/shapely/ctypes_declarations.py
@@ -129,8 +129,16 @@ def prototype(lgeos, geos_version):
lgeos.GEOSBufferWithStyle.restype = c_void_p
lgeos.GEOSBufferWithStyle.argtypes = [c_void_p, c_double, c_int, c_int, c_int, c_double]
- lgeos.GEOSSingleSidedBuffer.restype = c_void_p
- lgeos.GEOSSingleSidedBuffer.argtypes = [c_void_p, c_double, c_int, c_int, c_double, c_int]
+ if geos_version >= (3, 3, 0):
+
+ lgeos.GEOSOffsetCurve.restype = c_void_p
+ lgeos.GEOSOffsetCurve.argtypes = [c_void_p, c_double, c_int, c_int, c_double]
+
+ else:
+
+ # deprecated in GEOS 3.3.0 in favour of GEOSOffsetCurve
+ lgeos.GEOSSingleSidedBuffer.restype = c_void_p
+ lgeos.GEOSSingleSidedBuffer.argtypes = [c_void_p, c_double, c_int, c_int, c_double, c_int]
'''
Geometry constructors
@@ -290,12 +298,16 @@ def prototype(lgeos, geos_version):
Dimensionally Extended 9 Intersection Model related
'''
- lgeos.GEOSRelatePattern.restype = c_char
- lgeos.GEOSRelatePattern.argtypes = [c_void_p, c_void_p, c_char_p]
-
lgeos.GEOSRelate.restype = allocated_c_char_p
lgeos.GEOSRelate.argtypes = [c_void_p, c_void_p]
+ lgeos.GEOSRelatePattern.restype = c_byte
+ lgeos.GEOSRelatePattern.argtypes = [c_void_p, c_void_p, c_char_p]
+
+ if geos_version >= (3, 3, 0):
+ lgeos.GEOSRelatePatternMatch.restype = c_byte
+ lgeos.GEOSRelatePatternMatch.argtypes = [c_char_p, c_char_p]
+
'''
Prepared Geometry Binary predicates
Return 2 on exception, 1 on true, 0 on false
diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py
index 69d8405..6498e06 100644
--- a/shapely/geometry/base.py
+++ b/shapely/geometry/base.py
@@ -14,6 +14,16 @@ from shapely.impl import DefaultImplementation, delegated
if sys.version_info[0] < 3:
range = xrange
+ integer_types = (int, long)
+else:
+ integer_types = (int,)
+
+try:
+ import numpy as np
+ integer_types = integer_types + (np.integer,)
+except ImportError:
+ pass
+
GEOMETRY_TYPES = [
'Point',
@@ -63,7 +73,7 @@ def geom_factory(g, parent=None):
[geom_type],
)
ob.__class__ = getattr(mod, geom_type)
- ob.__geom__ = g
+ ob._geom = g
ob.__p__ = parent
if lgeos.methods['has_z'](g):
ob._ndim = 3
@@ -188,14 +198,11 @@ class BaseGeometry(object):
_ndim = None
_crs = None
_other_owned = False
+ _is_empty = True
# Backend config
impl = DefaultImplementation
- @property
- def _is_empty(self):
- return self.__geom__ in [EMPTY, None]
-
# a reference to the so/dll proxy to preserve access during clean up
_lgeos = lgeos
@@ -207,6 +214,7 @@ class BaseGeometry(object):
self._lgeos.GEOSGeom_destroy(self.__geom__)
except AttributeError:
pass # _lgeos might be empty on shutdown
+ self._is_empty = True
self.__geom__ = val
def __del__(self):
@@ -235,6 +243,7 @@ class BaseGeometry(object):
@_geom.setter
def _geom(self, val):
self.empty()
+ self._is_empty = val in [EMPTY, None]
self.__geom__ = val
# Operators
@@ -528,7 +537,7 @@ class BaseGeometry(object):
@delegated
def simplify(self, tolerance, preserve_topology=True):
- """Returns a simplified geometry produced by the Douglas-Puecker
+ """Returns a simplified geometry produced by the Douglas-Peucker
algorithm
Coordinates of the simplified geometry will be no more than the
@@ -663,6 +672,12 @@ class BaseGeometry(object):
specified decimal place"""
return self.equals_exact(other, 0.5 * 10**(-decimal))
+ def relate_pattern(self, other, pattern):
+ """Returns True if the DE-9IM string code for the relationship between
+ the geometries satisfies the pattern, else False"""
+ pattern = c_char_p(pattern.encode('ascii'))
+ return bool(self.impl['relate_pattern'](self, other, pattern))
+
# Linear referencing
# ------------------
@@ -828,7 +843,7 @@ class GeometrySequence(object):
def __getitem__(self, key):
self._update()
m = self.__len__()
- if isinstance(key, int):
+ if isinstance(key, integer_types):
if key + m < 0 or key >= m:
raise IndexError("index out of range")
if key < 0:
diff --git a/shapely/geometry/linestring.py b/shapely/geometry/linestring.py
index 7cb5cf1..7a9fb6b 100644
--- a/shapely/geometry/linestring.py
+++ b/shapely/geometry/linestring.py
@@ -110,16 +110,18 @@ class LineString(BaseGeometry):
return self.coords.xy
def parallel_offset(
- self, distance, side,
+ self, distance, side='right',
resolution=16, join_style=JOIN_STYLE.round, mitre_limit=5.0):
"""Returns a LineString or MultiLineString geometry at a distance from
the object on its right or its left side.
- Distance must be a positive float value. The side parameter may be
- 'left' or 'right'. The resolution of the buffer around each vertex of
- the object increases by increasing the resolution keyword parameter or
- third positional parameter.
+ The side parameter may be 'left' or 'right' (default is 'right'). The
+ resolution of the buffer around each vertex of the object increases by
+ increasing the resolution keyword parameter or third positional
+ parameter. If the distance parameter is negative the side is inverted,
+ e.g. distance=5.0, side='left' is the same as distance=-5.0,
+ side='right'.
The join style is for outside corners between line segments. Accepted
values are JOIN_STYLE.round (1), JOIN_STYLE.mitre (2), and
@@ -136,8 +138,7 @@ class LineString(BaseGeometry):
'Cannot compute offset from zero-length line segment')
try:
return geom_factory(self.impl['parallel_offset'](
- self, distance, resolution, join_style, mitre_limit,
- bool(side == 'left')))
+ self, distance, resolution, join_style, mitre_limit, side))
except OSError:
raise TopologicalError()
diff --git a/shapely/geometry/multipoint.py b/shapely/geometry/multipoint.py
index 9ea8a6e..d9f8908 100644
--- a/shapely/geometry/multipoint.py
+++ b/shapely/geometry/multipoint.py
@@ -53,7 +53,7 @@ class MultiPoint(BaseMultipartGeometry):
"""
super(MultiPoint, self).__init__()
- if points is None:
+ if points is None or len(points) == 0:
# allow creation of empty multipoints, to support unpickling
pass
else:
diff --git a/shapely/geometry/polygon.py b/shapely/geometry/polygon.py
index 167f4d9..7c7c0cb 100644
--- a/shapely/geometry/polygon.py
+++ b/shapely/geometry/polygon.py
@@ -176,7 +176,7 @@ class InteriorRingSequence(object):
if i not in self.__rings__:
g = lgeos.GEOSGetInteriorRingN(self._geom, i)
ring = LinearRing()
- ring.__geom__ = g
+ ring._geom = g
ring.__p__ = self
ring._other_owned = True
ring._ndim = self._ndim
@@ -235,7 +235,7 @@ class Polygon(BaseGeometry):
elif self._exterior is None or self._exterior() is None:
g = lgeos.GEOSGetExteriorRing(self._geom)
ring = LinearRing()
- ring.__geom__ = g
+ ring._geom = g
ring.__p__ = self
ring._other_owned = True
ring._ndim = self._ndim
diff --git a/shapely/geos.py b/shapely/geos.py
index a00aa26..c767d72 100644
--- a/shapely/geos.py
+++ b/shapely/geos.py
@@ -1,8 +1,7 @@
"""
-Proxies for the libgeos_c shared lib, GEOS-specific exceptions, and utilities
+Proxies for libgeos, GEOS-specific exceptions, and utilities
"""
-import os
import re
import sys
import atexit
@@ -12,8 +11,9 @@ from ctypes import CDLL, cdll, pointer, string_at, cast, POINTER
from ctypes import c_void_p, c_size_t, c_char_p, c_int, c_float
from ctypes.util import find_library
-from . import ftools
from .ctypes_declarations import prototype, EXCEPTION_HANDLER_FUNCTYPE
+from .libgeos import lgeos as _lgeos, geos_version
+from . import ftools
# Add message handler to this module's logger
@@ -31,111 +31,6 @@ else:
LOG.addHandler(NullHandler())
-# Find and load the GEOS and C libraries
-# If this ever gets any longer, we'll break it into separate modules
-
-def load_dll(libname, fallbacks=None):
- lib = find_library(libname)
- if lib is not None:
- try:
- return CDLL(lib)
- except OSError:
- pass
- if fallbacks is not None:
- for name in fallbacks:
- try:
- return CDLL(name)
- except OSError:
- # move on to the next fallback
- pass
- # No shared library was loaded. Raise OSError.
- raise OSError(
- "Could not find library %s or load any of its variants %s" % (
- libname, fallbacks or []))
-
-
-if sys.platform.startswith('linux'):
- _lgeos = load_dll('geos_c', fallbacks=['libgeos_c.so.1', 'libgeos_c.so'])
- free = load_dll('c').free
- free.argtypes = [c_void_p]
- free.restype = None
-
-elif sys.platform == 'darwin':
- # First test to see if this is a delocated wheel with a GEOS dylib.
- geos_whl_dylib = os.path.abspath(
- os.path.join(os.path.dirname(__file__), '.dylibs/libgeos_c.1.dylib'))
- if os.path.exists(geos_whl_dylib):
- _lgeos = CDLL(geos_whl_dylib)
- else:
- if hasattr(sys, 'frozen'):
- try:
- # .app file from py2app
- alt_paths = [os.path.join(os.environ['RESOURCEPATH'],
- '..', 'Frameworks', 'libgeos_c.dylib')]
- except KeyError:
- # binary from pyinstaller
- alt_paths = [os.path.join(sys.executable, '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',
- ]
- _lgeos = load_dll('geos_c', fallbacks=alt_paths)
-
- free = load_dll('c').free
- free.argtypes = [c_void_p]
- free.restype = None
-
-elif sys.platform == 'win32':
- 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.dll")
- except (ImportError, WindowsError, OSError):
- raise
-
- def free(m):
- try:
- cdll.msvcrt.free(m)
- except WindowsError:
- # XXX: See http://trac.gispython.org/projects/PCL/ticket/149
- pass
-
-elif sys.platform == 'sunos5':
- _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
- _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
-
-
-def _geos_version():
- # extern const char GEOS_DLL *GEOSversion();
- GEOSversion = _lgeos.GEOSversion
- GEOSversion.restype = c_char_p
- GEOSversion.argtypes = []
- #define GEOS_CAPI_VERSION "@VERSION at -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)
- assert len(res) == 2, res
- 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()
-
# If we have the new interface, then record a baseline so that we know what
# additional functions are declared in ctypes_declarations.
if geos_version >= (3, 1, 0):
@@ -430,7 +325,7 @@ class WKBWriter(object):
def big_endian(self):
"""Byte order is big endian, True (default) or False"""
return (self._lgeos.GEOSWKBWriter_getByteOrder(self._writer) ==
- self._ENDIAN_BIG)
+ self._ENDIAN_BIG)
@big_endian.setter
def big_endian(self, value):
@@ -500,7 +395,7 @@ class WKBWriter(object):
# Errcheck functions for ctypes
def errcheck_wkb(result, func, argtuple):
- '''Returns bytes from a C pointer'''
+ """Returns bytes from a C pointer"""
if not result:
return None
size_ref = argtuple[-1]
@@ -511,7 +406,7 @@ def errcheck_wkb(result, func, argtuple):
def errcheck_just_free(result, func, argtuple):
- '''Returns string from a C pointer'''
+ """Returns string from a C pointer"""
retval = string_at(result)
lgeos.GEOSFree(result)
if sys.version_info[0] >= 3:
@@ -519,9 +414,15 @@ def errcheck_just_free(result, func, argtuple):
else:
return retval
+def errcheck_null_exception(result, func, argtuple):
+ """Wraps errcheck_just_free, raising a TopologicalError if result is NULL"""
+ if not result:
+ raise TopologicalError("The operation '{0}' could not be performed."
+ "Likely cause is invalidity of the geometry.".format(func.__name__))
+ return errcheck_just_free(result, func, argtuple)
def errcheck_predicate(result, func, argtuple):
- '''Result is 2 on exception, 1 on True, 0 on False'''
+ """Result is 2 on exception, 1 on True, 0 on False"""
if result == 2:
raise PredicateError("Failed to evaluate %s" % repr(func))
return result
@@ -562,7 +463,7 @@ class LGEOS300(LGEOSBase):
# Deprecated
self.GEOSGeomToWKB_buf.errcheck = errcheck_wkb
self.GEOSGeomToWKT.errcheck = errcheck_just_free
- self.GEOSRelate.errcheck = errcheck_just_free
+ self.GEOSRelate.errcheck = errcheck_null_exception
for pred in (
self.GEOSDisjoint,
self.GEOSTouches,
@@ -573,6 +474,7 @@ class LGEOS300(LGEOSBase):
self.GEOSOverlaps,
self.GEOSEquals,
self.GEOSEqualsExact,
+ self.GEOSRelatePattern,
self.GEOSisEmpty,
self.GEOSisValid,
self.GEOSisSimple,
@@ -608,6 +510,7 @@ class LGEOS300(LGEOSBase):
self.methods['symmetric_difference'] = self.GEOSSymDifference
self.methods['union'] = self.GEOSUnion
self.methods['intersection'] = self.GEOSIntersection
+ self.methods['relate_pattern'] = self.GEOSRelatePattern
self.methods['simplify'] = self.GEOSSimplify
self.methods['topology_preserve_simplify'] = \
self.GEOSTopologyPreserveSimplify
@@ -637,7 +540,7 @@ class LGEOS310(LGEOSBase):
# Deprecated
self.GEOSGeomToWKB_buf.func.errcheck = errcheck_wkb
self.GEOSGeomToWKT.func.errcheck = errcheck_just_free
- self.GEOSRelate.func.errcheck = errcheck_just_free
+ self.GEOSRelate.func.errcheck = errcheck_null_exception
for pred in (
self.GEOSDisjoint,
self.GEOSTouches,
@@ -658,6 +561,7 @@ class LGEOS310(LGEOSBase):
self.GEOSPreparedContainsProperly,
self.GEOSPreparedCovers,
self.GEOSPreparedIntersects,
+ self.GEOSRelatePattern,
self.GEOSisEmpty,
self.GEOSisValid,
self.GEOSisSimple,
@@ -706,6 +610,7 @@ class LGEOS310(LGEOSBase):
self.GEOSPreparedContainsProperly
self.methods['prepared_overlaps'] = self.GEOSPreparedOverlaps
self.methods['prepared_covers'] = self.GEOSPreparedCovers
+ self.methods['relate_pattern'] = self.GEOSRelatePattern
self.methods['simplify'] = self.GEOSSimplify
self.methods['topology_preserve_simplify'] = \
self.GEOSTopologyPreserveSimplify
@@ -731,7 +636,15 @@ class LGEOS320(LGEOS311):
def __init__(self, dll):
super(LGEOS320, self).__init__(dll)
- self.methods['parallel_offset'] = self.GEOSSingleSidedBuffer
+ if geos_version >= (3, 2, 0):
+ def parallel_offset(geom, distance, resolution=16, join_style=1, mitre_limit=5.0, side='right'):
+ side = side == 'left'
+ if distance < 0:
+ distance = abs(distance)
+ side = not side
+ return self.GEOSSingleSidedBuffer(geom, distance, resolution, join_style, mitre_limit, side)
+ self.methods['parallel_offset'] = parallel_offset
+
self.methods['project'] = self.GEOSProject
self.methods['project_normalized'] = self.GEOSProjectNormalized
self.methods['interpolate'] = self.GEOSInterpolate
@@ -760,6 +673,12 @@ class LGEOS330(LGEOS320):
for pred in (self.GEOSisClosed,):
pred.func.errcheck = errcheck_predicate
+ def parallel_offset(geom, distance, resolution=16, join_style=1, mitre_limit=5.0, side='right'):
+ if side == 'right':
+ distance *= -1
+ return self.GEOSOffsetCurve(geom, distance, resolution, join_style, mitre_limit)
+ self.methods['parallel_offset'] = parallel_offset
+
self.methods['unary_union'] = self.GEOSUnaryUnion
self.methods['is_closed'] = self.GEOSisClosed
self.methods['cascaded_union'] = self.methods['unary_union']
@@ -793,6 +712,7 @@ else:
lgeos = L(_lgeos)
+
def cleanup(proxy):
del proxy
diff --git a/shapely/impl.py b/shapely/impl.py
index fda6281..828e0ae 100644
--- a/shapely/impl.py
+++ b/shapely/impl.py
@@ -21,6 +21,13 @@ from shapely.predicates import BinaryPredicate, UnaryPredicate
from shapely.topology import BinaryRealProperty, BinaryTopologicalOp
from shapely.topology import UnaryRealProperty, UnaryTopologicalOp
+
+class ImplementationError(
+ AttributeError, KeyError, NotImplementedError):
+ """To be raised when the registered implementation does not
+ support the requested method."""
+
+
def delegated(func):
"""A delegated method raises AttributeError in the absence of backend
support."""
@@ -29,27 +36,43 @@ def delegated(func):
try:
return func(*args, **kwargs)
except KeyError:
- raise AttributeError("Method %r is not supported by %r" %
- (func.__name__, args[0].impl))
+ raise ImplementationError(
+ "Method '%s' not provided by registered "
+ "implementation '%s'" % (func.__name__, args[0].impl))
return wrapper
# Map geometry methods to their GEOS delegates
+
class BaseImpl(object):
+ """Base class for registrable implementations."""
+
def __init__(self, values):
self.map = dict(values)
+
def update(self, values):
self.map.update(values)
+
def __getitem__(self, key):
- return self.map[key]
+ try:
+ return self.map[key]
+ except KeyError:
+ raise ImplementationError(
+ "Method '%s' not provided by registered "
+ "implementation '%s'" % (key, self.map))
+
def __contains__(self, key):
return key in self.map
+
class GEOSImpl(BaseImpl):
+ """GEOS implementation"""
+
def __repr__(self):
return '<GEOSImpl object: GEOS C API version %s>' % (
lgeos.geos_capi_version,)
+
IMPL300 = {
'area': (UnaryRealProperty, 'area'),
'distance': (BinaryRealProperty, 'distance'),
@@ -85,6 +108,7 @@ IMPL300 = {
'within': (BinaryPredicate, 'within'),
'covers': (BinaryPredicate, 'covers'),
'equals_exact': (BinaryPredicate, 'equals_exact'),
+ 'relate_pattern': (BinaryPredicate, 'relate_pattern'),
# First pure Python implementation
'is_ccw': (cga.is_ccw_impl, 'is_ccw'),
@@ -121,6 +145,7 @@ IMPL320 = {
IMPL330 = {
'is_closed': (UnaryPredicate, 'is_closed')}
+
def impl_items(defs):
return [(k, v[0](v[1])) for k, v in list(defs.items())]
diff --git a/shapely/libgeos.py b/shapely/libgeos.py
new file mode 100644
index 0000000..8bae437
--- /dev/null
+++ b/shapely/libgeos.py
@@ -0,0 +1,236 @@
+"""
+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.
+"""
+
+import os
+import logging
+import re
+import subprocess
+import sys
+from ctypes import CDLL, cdll, c_void_p, c_char_p
+from ctypes.util import find_library
+
+
+logging.basicConfig()
+log = logging.getLogger(__name__)
+if 'all' in sys.warnoptions:
+ log.level = 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.warn('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.warn("cannot open shared object from '%s --clibs': %r",
+ geos_config, clibs)
+ log.warn("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=[]):
+ '''Load GEOS dynamic library'''
+ lib = find_library(libname)
+ if lib is not None:
+ try:
+ return CDLL(lib)
+ except OSError:
+ pass
+ for name in fallbacks:
+ try:
+ return CDLL(name)
+ except OSError:
+ # move on to the next fallback
+ pass
+ raise OSError(
+ "Could not find library %s or load any of its variants %s" % (
+ libname, fallbacks))
+
+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').free
+ free.argtypes = [c_void_p]
+ free.restype = None
+
+elif sys.platform == 'darwin':
+ if not lgeos:
+ # First test to see if this is a delocated wheel with a GEOS dylib.
+ geos_whl_dylib = os.path.abspath(os.path.join(
+ os.path.dirname(__file__), '.dylibs/libgeos_c.1.dylib'))
+ if os.path.exists(geos_whl_dylib):
+ lgeos = CDLL(geos_whl_dylib)
+ else:
+ 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',
+ ]
+ 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.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 at -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)
+ assert len(res) == 2, res
+ 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()
diff --git a/shapely/linref.py b/shapely/linref.py
index fc3d0f4..8e727fc 100644
--- a/shapely/linref.py
+++ b/shapely/linref.py
@@ -7,9 +7,7 @@ from shapely.topology import Delegating
class LinearRefBase(Delegating):
def _validate_line(self, ob):
super(LinearRefBase, self)._validate(ob)
- try:
- assert ob.geom_type in ['LineString', 'MultiLineString']
- except AssertionError:
+ if not ob.geom_type in ['LinearRing', 'LineString', 'MultiLineString']:
raise TypeError("Only linear types support this operation")
class ProjectOp(LinearRefBase):
diff --git a/shapely/speedups/__init__.py b/shapely/speedups/__init__.py
index 60d9d0b..2e84a1f 100644
--- a/shapely/speedups/__init__.py
+++ b/shapely/speedups/__init__.py
@@ -58,5 +58,5 @@ def disable():
coords.CoordinateSequence.__iter__ = _orig['CoordinateSequence.__iter__']
linestring.geos_linestring_from_py = _orig['geos_linestring_from_py']
polygon.geos_linearring_from_py = _orig['geos_linearring_from_py']
- affinity.affine_transform = _orig['affine_transform']
+ shapely.affinity.affine_transform = _orig['affine_transform']
_orig.clear()
diff --git a/tests/__init__.py b/tests/__init__.py
index b4cf8ab..057380d 100755
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,10 +1,17 @@
import sys
-from shapely.geos import geos_version_string, lgeos, WKTWriter
+from shapely.libgeos import geos_version_string
+from shapely.geos import lgeos, WKTWriter
from shapely import speedups
+from packaging.version import Version
+
+
+test_int_types = [int]
+
try:
import numpy
numpy_version = numpy.version.version
+ test_int_types.extend([int, numpy.int16, numpy.int32, numpy.int64])
except ImportError:
numpy = False
numpy_version = 'not available'
diff --git a/tests/test_default_impl.py b/tests/test_default_impl.py
new file mode 100644
index 0000000..7a33193
--- /dev/null
+++ b/tests/test_default_impl.py
@@ -0,0 +1,24 @@
+import pytest
+
+from shapely.geometry import Point
+from shapely.impl import delegated, ImplementationError
+
+
+def test_error():
+ with pytest.raises(ImplementationError):
+ Point(0, 0).impl['bogus']()
+ with pytest.raises(NotImplementedError):
+ Point(0, 0).impl['bogus']()
+ with pytest.raises(KeyError):
+ Point(0, 0).impl['bogus']()
+
+
+def test_delegated():
+ class Poynt(Point):
+ @delegated
+ def bogus(self):
+ return self.impl['bogus']()
+ with pytest.raises(ImplementationError):
+ Poynt(0, 0).bogus()
+ with pytest.raises(AttributeError):
+ Poynt(0, 0).bogus()
diff --git a/tests/test_dlls.py b/tests/test_dlls.py
index 8c82bcb..a887411 100644
--- a/tests/test_dlls.py
+++ b/tests/test_dlls.py
@@ -1,6 +1,6 @@
from . import unittest
-from shapely.geos import load_dll
+from shapely.libgeos import load_dll
class LoadingTestCase(unittest.TestCase):
diff --git a/tests/test_multi.py b/tests/test_multi.py
new file mode 100644
index 0000000..49fd4e3
--- /dev/null
+++ b/tests/test_multi.py
@@ -0,0 +1,8 @@
+from . import unittest, test_int_types
+
+class MultiGeometryTestCase(unittest.TestCase):
+ def subgeom_access_test(self, cls, geoms):
+ geom = cls(geoms)
+ for t in test_int_types:
+ for i, g in enumerate(geoms):
+ self.assertEqual(geom[t(i)], geoms[i])
diff --git a/tests/test_multilinestring.py b/tests/test_multilinestring.py
index 61b6bc3..be3f4ab 100644
--- a/tests/test_multilinestring.py
+++ b/tests/test_multilinestring.py
@@ -1,10 +1,11 @@
-from . import unittest, numpy
+from . import unittest, numpy, test_int_types
+from .test_multi import MultiGeometryTestCase
from shapely.geos import lgeos
from shapely.geometry import LineString, MultiLineString, asMultiLineString
from shapely.geometry.base import dump_coords
-class MultiLineStringTestCase(unittest.TestCase):
+class MultiLineStringTestCase(MultiGeometryTestCase):
def test_multilinestring(self):
@@ -73,6 +74,11 @@ class MultiLineStringTestCase(unittest.TestCase):
# TODO: is there an inverse?
+ def test_subgeom_access(self):
+ line0 = LineString([(0.0, 1.0), (2.0, 3.0)])
+ line1 = LineString([(4.0, 5.0), (6.0, 7.0)])
+ self.subgeom_access_test(MultiLineString, [line0, line1])
+
def test_suite():
return unittest.TestLoader().loadTestsFromTestCase(MultiLineStringTestCase)
diff --git a/tests/test_multipoint.py b/tests/test_multipoint.py
index 013ba0d..c84443c 100644
--- a/tests/test_multipoint.py
+++ b/tests/test_multipoint.py
@@ -1,9 +1,10 @@
-from . import unittest, numpy
+from . import unittest, numpy, test_int_types
+from .test_multi import MultiGeometryTestCase
from shapely.geometry import Point, MultiPoint, asMultiPoint
from shapely.geometry.base import dump_coords
-class MultiPointTestCase(unittest.TestCase):
+class MultiPointTestCase(MultiGeometryTestCase):
def test_multipoint(self):
@@ -68,6 +69,10 @@ class MultiPointTestCase(unittest.TestCase):
pas = asarray(geoma)
assert_array_equal(pas, array([[1., 2.], [3., 4.]]))
+ def test_subgeom_access(self):
+ p0 = Point(1.0, 2.0)
+ p1 = Point(3.0, 4.0)
+ self.subgeom_access_test(MultiPoint, [p0, p1])
def test_suite():
return unittest.TestLoader().loadTestsFromTestCase(MultiPointTestCase)
diff --git a/tests/test_multipolygon.py b/tests/test_multipolygon.py
index 9e7d257..188beb9 100644
--- a/tests/test_multipolygon.py
+++ b/tests/test_multipolygon.py
@@ -1,9 +1,10 @@
-from . import unittest
+from . import unittest, numpy, test_int_types
+from .test_multi import MultiGeometryTestCase
from shapely.geometry import Polygon, MultiPolygon, asMultiPolygon
from shapely.geometry.base import dump_coords
-class MultiPolygonTestCase(unittest.TestCase):
+class MultiPolygonTestCase(MultiGeometryTestCase):
def test_multipolygon(self):
@@ -67,6 +68,11 @@ class MultiPolygonTestCase(unittest.TestCase):
self.assertEqual(len(mpa.geoms[0].interiors), 1)
self.assertEqual(len(mpa.geoms[0].interiors[0].coords), 5)
+ def test_subgeom_access(self):
+ poly0 = Polygon([(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)])
+ poly1 = Polygon([(0.25, 0.25), (0.25, 0.5), (0.5, 0.5), (0.5, 0.25)])
+ self.subgeom_access_test(MultiPolygon, [poly0, poly1])
+
def test_suite():
return unittest.TestLoader().loadTestsFromTestCase(MultiPolygonTestCase)
diff --git a/tests/test_operations.py b/tests/test_operations.py
index bdea03a..3377646 100644
--- a/tests/test_operations.py
+++ b/tests/test_operations.py
@@ -1,7 +1,8 @@
from . import unittest
+import pytest
from shapely.geometry import Point, Polygon, MultiPoint, GeometryCollection
from shapely.wkt import loads
-
+from shapely.geos import TopologicalError
class OperationsTestCase(unittest.TestCase):
@@ -62,9 +63,15 @@ class OperationsTestCase(unittest.TestCase):
self.assertIsInstance(point.centroid, Point)
+ def test_relate(self):
# Relate
- self.assertEqual(point.relate(Point(-1, -1)), 'FF0FFF0F2')
+ self.assertEqual(Point(0, 0).relate(Point(-1, -1)), 'FF0FFF0F2')
+ # issue #294: should raise TopologicalError on exception
+ invalid_polygon = loads('POLYGON ((40 100, 80 100, 80 60, 40 60, 40 100), (60 60, 80 60, 80 40, 60 40, 60 60))')
+ assert(not invalid_polygon.is_valid)
+ with pytest.raises(TopologicalError):
+ invalid_polygon.relate(invalid_polygon)
def test_suite():
return unittest.TestLoader().loadTestsFromTestCase(OperationsTestCase)
diff --git a/tests/test_parallel_offset.py b/tests/test_parallel_offset.py
new file mode 100644
index 0000000..ae7a2f1
--- /dev/null
+++ b/tests/test_parallel_offset.py
@@ -0,0 +1,33 @@
+from . import unittest
+from shapely.geos import geos_version
+from shapely.geometry import LineString, LinearRing
+from shapely.wkt import loads
+
+class OperationsTestCase(unittest.TestCase):
+ @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required')
+ def test_parallel_offset_linestring(self):
+ line1 = LineString([(0, 0), (10, 0)])
+ left = line1.parallel_offset(5, 'left')
+ self.assertEqual(left, LineString([(0, 5), (10, 5)]))
+ right = line1.parallel_offset(5, 'right')
+ self.assertEqual(right, LineString([(10, -5), (0, -5)]))
+ right = line1.parallel_offset(-5, 'left')
+ self.assertEqual(right, LineString([(10, -5), (0, -5)]))
+ left = line1.parallel_offset(-5, 'right')
+ self.assertEqual(left, LineString([(0, 5), (10, 5)]))
+
+ # by default, parallel_offset is right-handed
+ self.assertEqual(line1.parallel_offset(5), right)
+
+ line2 = LineString([(0, 0), (5, 0), (5, -5)])
+ self.assertEqual(line2.parallel_offset(2, 'left', resolution=1),
+ LineString([(0, 2), (5, 2), (7, 0), (7, -5)]))
+ self.assertEqual(line2.parallel_offset(2, 'left', join_style=2,
+ resolution=1),
+ LineString([(0, 2), (7, 2), (7, -5)]))
+
+ @unittest.skipIf(geos_version < (3, 2, 0), 'GEOS 3.2.0 required')
+ def test_parallel_offset_linear_ring(self):
+ lr1 = LinearRing([(0, 0), (5, 0), (5, 5), (0, 5), (0, 0)])
+ self.assertEqual(lr1.parallel_offset(2, 'left', resolution=1),
+ LineString([(2, 2), (3, 2), (3, 3), (2, 3), (2, 2)]))
diff --git a/tests/test_predicates.py b/tests/test_predicates.py
index 3062eac..c3c1cb4 100644
--- a/tests/test_predicates.py
+++ b/tests/test_predicates.py
@@ -2,8 +2,8 @@
"""
from . import unittest
from shapely.geometry import Point, Polygon
-from shapely.geos import TopologicalError
-
+from shapely.geos import TopologicalError, PredicateError
+import pytest
class PredicatesTestCase(unittest.TestCase):
@@ -41,6 +41,25 @@ class PredicatesTestCase(unittest.TestCase):
(399, 450), (339, 207)]
self.assertRaises(TopologicalError, Polygon(p1).within, Polygon(p2))
+ def test_relate_pattern(self):
+
+ # a pair of partially overlapping polygons, and a nearby point
+ g1 = Polygon([(0, 0), (0, 1), (3, 1), (3, 0), (0, 0)])
+ g2 = Polygon([(1, -1), (1, 2), (2, 2), (2, -1), (1, -1)])
+ g3 = Point(5, 5)
+
+ assert(g1.relate(g2) == '212101212')
+ assert(g1.relate_pattern(g2, '212101212'))
+ assert(g1.relate_pattern(g2, '*********'))
+ assert(g1.relate_pattern(g2, '2********'))
+ assert(g1.relate_pattern(g2, 'T********'))
+ assert(not g1.relate_pattern(g2, '112101212'))
+ assert(not g1.relate_pattern(g2, '1********'))
+ assert(g1.relate_pattern(g3, 'FF2FF10F2'))
+
+ # an invalid pattern should raise an exception
+ with pytest.raises(PredicateError):
+ g1.relate_pattern(g2, 'fail')
def test_suite():
return unittest.TestLoader().loadTestsFromTestCase(PredicatesTestCase)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-shapely.git
More information about the Pkg-grass-devel
mailing list