This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository python-shapely.
commit 6a71eda8077b03244a9327c73ece5336b1ef9c50 Author: Bas Couwenberg <[email protected]> Date: Mon Mar 28 11:37:18 2016 +0200 Imported Upstream version 1.5.14 --- CHANGES.txt | 17 +++++++++ CREDITS.txt | 74 ++++++++++++++++++++------------------ shapely/__init__.py | 2 +- shapely/ctypes_declarations.py | 3 ++ shapely/geometry/base.py | 4 +-- shapely/geometry/multipolygon.py | 37 +++++++++++-------- shapely/geometry/polygon.py | 5 +++ shapely/geos.py | 76 ++++++++++++++-------------------------- shapely/strtree.py | 2 ++ tests/test_empty_polygons.py | 22 ++++++++++++ tests/test_linestring.py | 22 ++++++++++++ tests/test_polygon.py | 15 ++++++++ tests/test_strtree.py | 29 ++++++++++++++- tests/test_wkt.py | 34 ++++++++++++++++++ 14 files changed, 239 insertions(+), 103 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e353755..274267f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,23 @@ Changes ======= +1.5.14 (2016-03-27) +------------------- +- Bug fix: use ``type()`` instead of ``isinstance()`` when evaluating geometry + equality, preventing instances of base and derived classes from + being mistaken for equals (#317). +- Bug fix: ensure that empty geometries are created when constructors have no + args (#332, #333). +- Bug fix: support app "freezing" better on Windows by not relying on the + ``__file__`` attribute (#342, #377). +- Bug fix: ensure that empty polygons evaluate to be ``==`` (#355). +- Bug fix: use uintptr_t to store pointers instead of long in _geos.pxi, + preventing an overflow error (#372, #373). +- Bug fix: filter out empty geometries that can cause segfaults when creating + and loading STRtrees (#345, #348). +- Bug fix: no longer attempt to reuse GEOS DLLs already loaded by Rasterio + or Fiona on OS X (#374, #375). + 1.5.13 (2015-10-09) ------------------- - Restore setup and runtime discovery and loading of GEOS shared library to diff --git a/CREDITS.txt b/CREDITS.txt index b328cf5..853c953 100644 --- a/CREDITS.txt +++ b/CREDITS.txt @@ -3,40 +3,46 @@ Credits Shapely is written by: -* Sean Gillies -* Aron Bierbaum -* Kai Lautaportti -* Oliver Tonnhofer - -Patches contributed by: - -* Allan Adair (https://github.com/allanadair) -* Joshua Arnott (https://github.com/snorfalorpagus) -* David Baumgold (https://github.com/singingwolfboy) -* Howard Butler -* Gabi Davar (https://github.com/mindw) -* Phil Elson (https://github.com/pelson) -* Johan Euphrosine (https://github.com/proppy) -* Bertrand Gervais (https://github.com/BertrandGervais) -* Marc Jansen (https://github.com/marcjansen) -* Kelsey Jordahl (https://github.com/kjordahl) -* Frédéric Junod -* Thomas Kluyver (https://github.com/takluyver) -* William Kyngesburye (https://github.com/kyngchaos) -* Eric Lemoine -* Naveen Michaud-Agrawal (https://github.com/nmichaud) -* om-henners (https://github.com/om-henners) -* psagers https://github.com/psagers -* Jeethu Rao (https://github.com/jeethu) -* Benjamin Root (https://github.com/WeatherGod) -* Jason Sanford (https://github.com/JasonSanford) -* Johannes Schönberger (https://github.com/ahojnnes) -* Jonathan Tartley -* Kristian Thy -* Mike Toews (https://github.com/mwtoews) -* Maarten Vermeyen (https://github.com/maarten-vermeyen) -* Jacob Wasserman (https://github.com/jwass) -* Brandon Wood (https://github.com/woodb) +* Sean Gillies <[email protected]> +* Oliver Tonnhofer <[email protected]> +* Mike Toews <[email protected]> +* Joshua Arnott <[email protected]> +* Jacob Wasserman <[email protected]> +* Aron Bierbaum <[email protected]> +* Johannes Schönberger <[email protected]> +* Phil Elson <[email protected]> +* Howard Butler <[email protected]> +* dokai <dokai@b426a367-1105-0410-b9ff-cdf4ab011145> +* Gabi Davar <[email protected]> +* Kelsey Jordahl <[email protected]> +* Dave Collins <[email protected]> +* Jinkun Wang <[email protected]> +* Marc Jansen <[email protected]> +* Henry Walshaw <[email protected]> +* David Baumgold <[email protected]> +* Sampo Syrjanen <[email protected]> +* Steve M. Kim <[email protected]> +* Thomas Kluyver <[email protected]> +* Brad Hards <[email protected]> +* Allan Adair <[email protected]> +* fredj <[email protected]> +* Naveen Michaud-Agrawal <[email protected]> +* Peter Sagerson <[email protected]> +* BertrandGervais <[email protected]> +* Jeethu Rao <[email protected]> +* Jason Sanford <[email protected]> +* Brandon Wood <[email protected]> +* Stephan Hügel <[email protected]> +* Johan Euphrosine <[email protected]> +* mindw <[email protected]> +* James Spencer <[email protected]> +* Benjamin Root <[email protected]> +* Leandro Lima <[email protected]> +* Maarten Vermeyen <[email protected]> +* Tobias Sauerwein <[email protected]> +* James Douglass <[email protected]> +* Morris Tweed <[email protected]>] +* WANG Aiyong <[email protected]> See also: https://github.com/Toblerity/Shapely/graphs/contributors. diff --git a/shapely/__init__.py b/shapely/__init__.py index 1d51ed9..0240f96 100644 --- a/shapely/__init__.py +++ b/shapely/__init__.py @@ -1 +1 @@ -__version__ = "1.5.13" +__version__ = "1.5.14" diff --git a/shapely/ctypes_declarations.py b/shapely/ctypes_declarations.py index ac38fc5..4716212 100644 --- a/shapely/ctypes_declarations.py +++ b/shapely/ctypes_declarations.py @@ -159,6 +159,9 @@ def prototype(lgeos, geos_version): lgeos.GEOSGeom_createCollection.restype = c_void_p lgeos.GEOSGeom_createCollection.argtypes = [c_int, c_void_p, c_uint] + lgeos.GEOSGeom_createEmptyCollection.restype = c_void_p + lgeos.GEOSGeom_createEmptyCollection.argtypes = [c_int] + lgeos.GEOSGeom_clone.restype = c_void_p lgeos.GEOSGeom_clone.argtypes = [c_void_p] diff --git a/shapely/geometry/base.py b/shapely/geometry/base.py index 42d821f..2696a3b 100644 --- a/shapely/geometry/base.py +++ b/shapely/geometry/base.py @@ -263,7 +263,7 @@ class BaseGeometry(object): def __eq__(self, other): return ( - isinstance(other, self.__class__) and + type(other) == type(self) and tuple(self.coords) == tuple(other.coords) ) @@ -765,7 +765,7 @@ class BaseMultipartGeometry(BaseGeometry): def __eq__(self, other): return ( - isinstance(other, self.__class__) and + type(other) == type(self) and len(self) == len(other) and all(x == y for x, y in zip(self, other)) ) diff --git a/shapely/geometry/multipolygon.py b/shapely/geometry/multipolygon.py index eedd410..55e69ee 100644 --- a/shapely/geometry/multipolygon.py +++ b/shapely/geometry/multipolygon.py @@ -146,19 +146,24 @@ def geos_multipolygon_from_py(ob): return (lgeos.GEOSGeom_createCollection(6, subs, L), N) -def geos_multipolygon_from_polygons(ob): +def geos_multipolygon_from_polygons(arg): """ ob must be either a MultiPolygon, sequence or array of sequences or arrays. """ - if isinstance(ob, MultiPolygon): - return geos_geom_from_py(ob) + if isinstance(arg, MultiPolygon): + return geos_geom_from_py(arg) - obs = getattr(ob, 'geoms', None) or ob + obs = getattr(arg, 'geoms', arg) + obs = [ob for ob in obs + if ob and not (isinstance(ob, polygon.Polygon) and ob.is_empty)] L = len(obs) - assert L >= 1 - + + # Bail immediately if we have no input points. + if L <= 0: + return (lgeos.GEOSGeom_createEmptyCollection(6), 3) + exemplar = obs[0] try: N = len(exemplar[0][0]) @@ -168,16 +173,18 @@ def geos_multipolygon_from_polygons(ob): assert N == 2 or N == 3 subs = (c_void_p * L)() - for l in range(L): - shell = getattr(obs[l], 'exterior', None) - if shell is None: - shell = obs[l][0] - holes = getattr(obs[l], 'interiors', None) - if holes is None: - holes = obs[l][1] + + for i, ob in enumerate(obs): + if isinstance(ob, polygon.Polygon): + shell = ob.exterior + holes = ob.interiors + else: + shell = ob[0] + holes = ob[1] + geom, ndims = polygon.geos_polygon_from_py(shell, holes) - subs[l] = cast(geom, c_void_p) - + subs[i] = cast(geom, c_void_p) + return (lgeos.GEOSGeom_createCollection(6, subs, L), N) # Test runner diff --git a/shapely/geometry/polygon.py b/shapely/geometry/polygon.py index 945a108..91f33fc 100644 --- a/shapely/geometry/polygon.py +++ b/shapely/geometry/polygon.py @@ -251,6 +251,11 @@ class Polygon(BaseGeometry): def __eq__(self, other): if not isinstance(other, Polygon): return False + check_empty = (self.is_empty, other.is_empty) + if all(check_empty): + return True + elif any(check_empty): + return False my_coords = [ tuple(self.exterior.coords), [tuple(interior.coords) for interior in self.interiors] diff --git a/shapely/geos.py b/shapely/geos.py index 976173b..f4b7cf9 100644 --- a/shapely/geos.py +++ b/shapely/geos.py @@ -69,59 +69,31 @@ if sys.platform.startswith('linux'): free.restype = None elif sys.platform == 'darwin': - # First: have we already loaded GEOS through a Fiona or Rasterio? - # If so, let's obtain a handle to it instead of loading this module's - # copy to side step the mysterious issue described at - # https://github.com/Toblerity/Shapely/issues/324. - geos_mod = sys.modules.get('fiona') or sys.modules.get('rasterio') - if geos_mod: - dll_path = os.path.join( - os.path.dirname(geos_mod.__file__), '.dylibs', - 'libgeos_c.1.dylib') - try: - _lgeos = CDLL(dll_path, mode=(DEFAULT_MODE | 16)) - except OSError: - LOG.debug("GEOS DLL in fiona or rasterio .dylibs not found.") + # Test to see if we have 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) + LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) + + 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, - mode=(DEFAULT_MODE | 16)) - if _lgeos: - LOG.debug("Found %r already loaded, using it.", _lgeos) - - # If neither fiona nor rasterio have been imported, or if the block - # above failed to assign _lgeos, we will locate and load the GEOS - # DLL. - if not _lgeos: - # Test to see if we have 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) - LOG.debug("Found GEOS DLL: %r, using it.", _lgeos) - - 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) + _lgeos = load_dll('geos_c', fallbacks=alt_paths) free = load_dll('c').free free.argtypes = [c_void_p] @@ -129,9 +101,13 @@ elif sys.platform == 'darwin': 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") + egg_dlls = os.path.abspath( + os.path.join(os.path.dirname(__file__), 'DLLs')) + if hasattr(sys, "frozen"): + wininst_dlls = os.path.normpath( + os.path.abspath(sys.executable+'../../DLLS')) + else: + 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) diff --git a/shapely/strtree.py b/shapely/strtree.py index 3c0235e..7abe685 100644 --- a/shapely/strtree.py +++ b/shapely/strtree.py @@ -31,6 +31,8 @@ class STRtree: """ def __init__(self, geoms): + # filter empty geometries out of the input + geoms = [geom for geom in geoms if not geom.is_empty] self._n_geoms = len(geoms) # GEOS STRtree capacity has to be > 1 self._tree_handle = lgeos.GEOSSTRtree_create(max(2, len(geoms))) diff --git a/tests/test_empty_polygons.py b/tests/test_empty_polygons.py new file mode 100644 index 0000000..89f42e7 --- /dev/null +++ b/tests/test_empty_polygons.py @@ -0,0 +1,22 @@ +from shapely.geometry import MultiPolygon, Point, Polygon + + +def test_empty_polygon(): + """No constructor arg makes an empty polygon geometry.""" + assert Polygon().is_empty + + +def test_empty_multipolygon(): + """No constructor arg makes an empty multipolygon geometry.""" + assert MultiPolygon().is_empty + + +def test_multipolygon_empty_polygon(): + """An empty polygon passed to MultiPolygon() makes an empty + multipolygon geometry.""" + assert MultiPolygon([Polygon()]).is_empty + + +def test_multipolygon_empty_among_polygon(): + """An empty polygon passed to MultiPolygon() is ignored.""" + assert len(MultiPolygon([Point(0,0).buffer(1.0), Polygon()])) == 1 diff --git a/tests/test_linestring.py b/tests/test_linestring.py index d7c7d4e..2c50a5b 100644 --- a/tests/test_linestring.py +++ b/tests/test_linestring.py @@ -57,6 +57,28 @@ class LineStringTestCase(unittest.TestCase): l_null.coords = [(0, 0), (1, 1)] self.assertAlmostEqual(l_null.length, 1.4142135623730951) + def test_equals_argument_order(self): + """ + Test equals predicate functions correctly regardless of the order + of the inputs. See issue #317. + """ + coords = ((0, 0), (1, 0), (1, 1), (0, 0)) + ls = LineString(coords) + lr = LinearRing(coords) + + self.assertFalse(ls.__eq__(lr)) # previously incorrectly returned True + self.assertFalse(lr.__eq__(ls)) + self.assertFalse(ls == lr) + self.assertFalse(lr == ls) + + ls_clone = LineString(coords) + lr_clone = LinearRing(coords) + + self.assertTrue(ls.__eq__(ls_clone)) + self.assertTrue(lr.__eq__(lr_clone)) + self.assertTrue(ls == ls_clone) + self.assertTrue(lr == lr_clone) + def test_from_linestring(self): line = LineString(((1.0, 2.0), (3.0, 4.0))) diff --git a/tests/test_polygon.py b/tests/test_polygon.py index 2d07a3a..84532b1 100644 --- a/tests/test_polygon.py +++ b/tests/test_polygon.py @@ -210,6 +210,21 @@ class PolygonTestCase(unittest.TestCase): ec = list(p.buffer(1).boundary.coords) self.assertIsInstance(ec, list) # TODO: this is a poor test + def test_empty_equality(self): + # Test equals operator, including empty geometries + # see issue #338 + + point1 = Point(0, 0) + polygon1 = Polygon(((0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0))) + polygon2 = Polygon(((0.0, 0.0), (0.0, 1.0), (-1.0, 1.0), (-1.0, 0.0))) + polygon_empty1 = Polygon() + polygon_empty2 = Polygon() + + self.assertNotEqual(point1, polygon1) + self.assertEqual(polygon_empty1, polygon_empty2) + self.assertNotEqual(polygon1, polygon_empty1) + self.assertEqual(polygon1, polygon2) + self.assertNotEqual(None, polygon_empty1) def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(PolygonTestCase) diff --git a/tests/test_strtree.py b/tests/test_strtree.py index 7de3921..372a987 100644 --- a/tests/test_strtree.py +++ b/tests/test_strtree.py @@ -1,7 +1,7 @@ from . import unittest from shapely.strtree import STRtree -from shapely.geometry import Point +from shapely.geometry import Point, Polygon from shapely.geos import geos_version @@ -15,6 +15,33 @@ class STRTestCase(unittest.TestCase): results = tree.query(Point(2,2).buffer(1.0)) self.assertEqual(len(results), 3) + def test_insert_empty_geometry(self): + """ + Passing nothing but empty geometries results in an empty strtree. + The query segfaults if the empty geometry was actually inserted. + """ + empty = Polygon() + geoms = [empty] + tree = STRtree(geoms) + assert(tree._n_geoms == 0) + query = Polygon([(0,0),(1,1),(2,0),(0,0)]) + results = tree.query(query) + + def test_query_empty_geometry(self): + """ + Empty geometries should be filtered out. + The query segfaults if the empty geometry was actually inserted. + """ + empty = Polygon() + point = Point(1, 0.5) + geoms = [empty, point] + tree = STRtree(geoms) + assert(tree._n_geoms == 1) + query = Polygon([(0,0),(1,1),(2,0),(0,0)]) + results = tree.query(query) + self.assertEqual(len(results), 1) + self.assertEqual(results[0], point) + def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(STRTestCase) diff --git a/tests/test_wkt.py b/tests/test_wkt.py new file mode 100644 index 0000000..945cb14 --- /dev/null +++ b/tests/test_wkt.py @@ -0,0 +1,34 @@ +from math import pi + +import pytest + +from shapely.geometry import LineString, Point +from shapely.wkt import dumps + + [email protected](scope="module") +def pipi(): + return Point((pi, -pi)) + + [email protected](scope="module") +def pipi4(): + return Point((pi*4, -pi*4)) + + +def test_wkt(pipi): + """.wkt and wkt.dumps() both do not trim by default.""" + assert pipi.wkt == "POINT ({0:.16f} {1:.16f})".format(pi, -pi) + + +def test_wkt(pipi4): + """.wkt and wkt.dumps() both do not trim by default.""" + assert pipi4.wkt == "POINT ({0:.16f} {1:.16f})".format(pi*4, -pi*4) + + +def test_dumps(pipi4): + assert dumps(pipi4) == "POINT ({0:.16f} {1:.16f})".format(pi*4, -pi*4) + + +def test_dumps_precision(pipi4): + assert dumps(pipi4, rounding_precision=4) == "POINT ({0:.4f} {1:.4f})".format(pi*4, -pi*4) -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/python-shapely.git _______________________________________________ Pkg-grass-devel mailing list [email protected] http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

