This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository python-shapely.
commit 2f464bb56d88be7005304856db6be8b25f566534 Author: Bas Couwenberg <[email protected]> Date: Mon Dec 12 18:34:38 2016 +0100 Imported Upstream version 1.6~b1 --- CHANGES.txt | 18 ++++ README.rst | 78 +++++++++-------- setup.py | 21 ++--- shapely/__init__.py | 2 +- shapely/coords.py | 21 ----- shapely/geometry/linestring.py | 154 +++++++++++---------------------- shapely/geometry/multipoint.py | 67 +++------------ shapely/geometry/point.py | 74 +++++----------- shapely/geometry/polygon.py | 190 +++++++++++++++-------------------------- shapely/speedups/_speedups.pyx | 12 ++- tests/test_linestring.py | 5 ++ tests/test_ndarrays.py | 8 +- tests/test_point.py | 5 ++ tests/test_vectorized.py | 13 +-- 14 files changed, 250 insertions(+), 418 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 922c051..ffe9922 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,24 @@ Changes ======= +1.6b1 (2016-12-12) +------------------ + +Bug fixes: + +- Implemented __array_interface__ for empty Points and LineStrings (#403). + +1.6a3 (2016-12-01) +------------------ + +Bug fixes: + +- Remove accidental hard requirement of Numpy (#431). + +Packaging: + +- Put Numpy in an optional requirement set named "vectorized" (#431). + 1.6a2 (2016-11-09) ------------------ diff --git a/README.rst b/README.rst index 8bbbff9..28d8bfd 100644 --- a/README.rst +++ b/README.rst @@ -2,17 +2,16 @@ Shapely ======= -Manipulation and analysis of geometric objects in the Cartesian plane. - .. image:: https://travis-ci.org/Toblerity/Shapely.png?branch=master :target: https://travis-ci.org/Toblerity/Shapely -.. image:: https://coveralls.io/repos/github/Toblerity/Shapely/badge.svg?branch=master :target: https://coveralls.io/github/Toblerity/Shapely?branch=master -.. image:: http://farm3.staticflickr.com/2738/4511827859_b5822043b7_o_d.png +Manipulation and analysis of geometric objects in the Cartesian plane. + +.. image:: https://c2.staticflickr.com/6/5560/31301790086_b3472ea4e9_c.jpg :width: 800 - :height: 400 + :height: 378 Shapely is a BSD-licensed Python package for manipulation and analysis of planar geometric objects. It is based on the widely deployed `GEOS @@ -28,46 +27,61 @@ but can be readily integrated with packages that are. For more details, see: Requirements ============ -Shapely 1.5.x requires +Shapely 1.6.x requires * Python >=2.6 (including Python 3.x) -* GEOS >=3.3 (Shapely 1.2.x requires only GEOS 3.1 but YMMV) +* GEOS >=3.3 -Installing Shapely -================== +Installing Shapely (1.6b1) +========================== -Windows users should download an executable installer from -http://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely or PyPI (if available). +Windows users have two good installation options: the wheels at +http://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely and the +Anaconda platform's [conda-forge](https://conda-forge.github.io/) +channel. -On other systems, acquire the GEOS by any means (`brew install geos` on OS X or -`apt-get install libgeos-dev` on Debian/Ubuntu), make sure that it is on the -system library path, and install Shapely from the Python package index. +OS X users can get Shapely wheels with GEOS included from the +Python Package Index with a recent version of pip (8+): .. code-block:: console - $ pip install shapely + $ pip install shapely==1.6b1 -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.: +A few extra speedups that require Numpy can be had by running .. code-block:: console - $ GEOS_CONFIG=/path/to/geos-config pip install shapely + $ pip install shapely[vectorized]==1.6b1 -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. +If you want to build Shapely from source for compatibility with +other modules that depend on GEOS (such as cartopy or osgeo.ogr) +you may ignore the binary wheels. .. code-block:: console - $ pip install shapely<1.3 + $ pip install shapely --no-binary shapely -Or, if you're using pip 6+ +Binary wheels are also available for Linux. To get them, use `pip shapely +shapely==1.6b1`. To avoid them, use `pip install shapely --no-binary +shapely==1.6b1`. + +In other situations, install `geos_c` libs and headers by any means +(for example, `brew install geos` on OS X or +`apt-get install libgeos-dev` on Debian/Ubuntu) and install Shapely +from the Python package index. .. code-block:: console - $ pip install shapely~=1.2 + $ pip install shapely + +If you've installed GEOS to a standard location, the geos-config program +will be used to get compiler and linker options. If geos-config is not on +your executable, it can be specified with a GEOS_CONFIG environment +variable, e.g.: + +.. code-block:: console + $ GEOS_CONFIG=/path/to/geos-config pip install shapely Shapely is also provided by popular Python distributions like Canopy (Enthought) and Anaconda (Continuum Analytics). @@ -133,21 +147,13 @@ We use py.test to run Shapely's suite of unittests and doctests. .. code-block:: console - (env)$ py.test tests - -Roadmap and Maintenance -======================= - -Shapely 1.2.x is a maintenance-only branch which supports Python 2.4-2.6, but -not Python 3+. There will be no new features in Shapely 1.2.x and only fixes -for major bugs. - -Shapely 1.4.x is a maintenance-only branch supporting Pythons 2.7 and 3.3+. + (env)$ python -m pytest Support ======= -Please discuss Shapely with us at -http://lists.gispython.org/mailman/listinfo/community. +Questions about using Shapely may be asked on the `GIS StackExchange +<http://gis.stackexchange.com/questions/tagged/shapely>`__ using the "shapely" +tag. Bugs may be reported at https://github.com/Toblerity/Shapely/issues. diff --git a/setup.py b/setup.py index 814c22b..7418d5a 100755 --- a/setup.py +++ b/setup.py @@ -178,8 +178,9 @@ with open('CHANGES.txt', 'r', **open_kwds) as fp: long_description = readme + '\n\n' + credits + '\n\n' + changes extra_reqs = { - 'test': ['pytest', 'pytest-cov', 'numpy>=1.4.1', 'packaging'] -} + 'test': ['pytest', 'pytest-cov', 'packaging'], + 'vectorized': ['numpy']} + extra_reqs['all'] = list(it.chain.from_iterable(extra_reqs.values())) # Make a dict of setup arguments. Some items will be updated as @@ -302,8 +303,8 @@ def construct_build_ext(build_ext): build_ext.build_extension(self, ext) except ext_errors as x: raise BuildFailed(x) - return WrappedBuildExt + return WrappedBuildExt if (hasattr(platform, 'python_implementation') and platform.python_implementation() == 'PyPy'): @@ -311,7 +312,6 @@ if (hasattr(platform, 'python_implementation') ext_modules = [] libraries = [] - if os.path.exists("MANIFEST.in"): pyx_file = "shapely/speedups/_speedups.pyx" c_file = "shapely/speedups/_speedups.c" @@ -333,15 +333,9 @@ if os.path.exists("MANIFEST.in"): 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, - extra_link_args=extra_link_args, - ), -] + Extension("shapely.speedups._speedups", ["shapely/speedups/_speedups.c"], + include_dirs=include_dirs, library_dirs=library_dirs, + libraries=libraries, extra_link_args=extra_link_args)] cmd_classes = setup_args.setdefault('cmdclass', {}) @@ -370,7 +364,6 @@ except ImportError: log.info("Numpy or Cython not available, shapely.vectorized submodule " "not being built.") - try: # try building with speedups existing_build_ext = setup_args['cmdclass'].\ diff --git a/shapely/__init__.py b/shapely/__init__.py index 909cbfd..8f4c864 100644 --- a/shapely/__init__.py +++ b/shapely/__init__.py @@ -1 +1 @@ -__version__ = "1.6a2" +__version__ = "1.6b1" diff --git a/shapely/coords.py b/shapely/coords.py index bfac400..427d171 100644 --- a/shapely/coords.py +++ b/shapely/coords.py @@ -11,27 +11,6 @@ from shapely.topology import Validating if sys.version_info[0] < 3: range = xrange -try: - import numpy - has_numpy = True -except ImportError: - has_numpy = False - -def required(ob): - """Return an object that meets Shapely requirements for self-owned - C-continguous data, copying if necessary, or just return the original - object.""" - if hasattr(ob, '__array_interface__'): - if ob.__array_interface__.get('strides') and not has_numpy: - # raise an error if strided. See issue #52. - raise ValueError("C-contiguous data is required") - else: - # numpy.require will just return (ob) if it is already - # float64 and well-behaved. - return numpy.require(ob, numpy.float64, ["C", "OWNDATA"]) - else: - return ob - class CoordinateSequence(object): """ diff --git a/shapely/geometry/linestring.py b/shapely/geometry/linestring.py index 8be43bb..9bba6cf 100644 --- a/shapely/geometry/linestring.py +++ b/shapely/geometry/linestring.py @@ -8,7 +8,6 @@ if sys.version_info[0] < 3: from ctypes import c_double, cast, POINTER -from shapely.coords import required from shapely.geos import lgeos, TopologicalError from shapely.geometry.base import ( BaseGeometry, geom_factory, JOIN_STYLE, geos_geom_from_py @@ -84,7 +83,11 @@ class LineString(BaseGeometry): def array_interface(self): """Provide the Numpy array protocol.""" - return self.coords.array_interface() + if self.is_empty: + ai = {'version': 3, 'typestr': '<f8', 'shape': (0,), 'data': (c_double * 0)()} + else: + ai = self.coords.array_interface() + return ai __array_interface__ = property(array_interface) @@ -187,112 +190,62 @@ def asLineString(context): def geos_linestring_from_py(ob, update_geom=None, update_ndim=0): # If a LineString is passed in, clone it and return - # If a LinearRing is passed in, clone the coord seq and return a LineString + # If a LinearRing is passed in, clone the coord seq and return a + # LineString. + # + # NB: access to coordinates using the array protocol has been moved + # entirely to the speedups module. + if isinstance(ob, LineString): if type(ob) == LineString: return geos_geom_from_py(ob) else: return geos_geom_from_py(ob, lgeos.GEOSGeom_createLineString) - # If numpy is present, we use numpy.require to ensure that we have a - # C-continguous array that owns its data. View data will be copied. - ob = required(ob) try: - # From array protocol - array = ob.__array_interface__ - assert len(array['shape']) == 2 - m = array['shape'][0] - if m < 2: - raise ValueError( - "LineStrings must have at least 2 coordinate tuples") - try: - n = array['shape'][1] - except IndexError: - raise ValueError( - "Input %s is the wrong shape for a LineString" % str(ob)) - assert n == 2 or n == 3 + m = len(ob) + except TypeError: # Iterators, e.g. Python 3 zip + ob = list(ob) + m = len(ob) - # Make pointer to the coordinate array - if isinstance(array['data'], tuple): - # numpy tuple (addr, read-only) - cp = cast(array['data'][0], POINTER(c_double)) - else: - cp = array['data'] - - # Create a coordinate sequence - if update_geom is not None: - cs = lgeos.GEOSGeom_getCoordSeq(update_geom) - if n != update_ndim: - raise ValueError( - "Wrong coordinate dimensions; this geometry has " - "dimensions: %d" % update_ndim) - else: - cs = lgeos.GEOSCoordSeq_create(m, n) - - # add to coordinate sequence - for i in range(m): - dx = c_double(cp[n*i]) - dy = c_double(cp[n*i+1]) - dz = None - if n == 3: - try: - dz = c_double(cp[n*i+2]) - except IndexError: - raise ValueError("Inconsistent coordinate dimensionality") - - # Because of a bug in the GEOS C API, - # always set X before Y - lgeos.GEOSCoordSeq_setX(cs, i, dx) - lgeos.GEOSCoordSeq_setY(cs, i, dy) - if n == 3: - lgeos.GEOSCoordSeq_setZ(cs, i, dz) - - except AttributeError: - # Fall back on list - try: - m = len(ob) - except TypeError: # Iterators, e.g. Python 3 zip - ob = list(ob) - m = len(ob) + if m == 0: + return None - if m == 0: - return None + def _coords(o): + if isinstance(o, Point): + return o.coords[0] + else: + return o - def _coords(o): - if isinstance(o, Point): - return o.coords[0] - else: - return o + try: + n = len(_coords(ob[0])) + except TypeError: + raise ValueError( + "Input %s is the wrong shape for a LineString" % str(ob)) + assert n == 2 or n == 3 - try: - n = len(_coords(ob[0])) - except TypeError: + # Create a coordinate sequence + if update_geom is not None: + cs = lgeos.GEOSGeom_getCoordSeq(update_geom) + if n != update_ndim: raise ValueError( - "Input %s is the wrong shape for a LineString" % str(ob)) - assert n == 2 or n == 3 - - # Create a coordinate sequence - if update_geom is not None: - cs = lgeos.GEOSGeom_getCoordSeq(update_geom) - if n != update_ndim: - raise ValueError( - "Wrong coordinate dimensions; this geometry has " - "dimensions: %d" % update_ndim) - else: - cs = lgeos.GEOSCoordSeq_create(m, n) - - # add to coordinate sequence - for i in range(m): - coords = _coords(ob[i]) - # Because of a bug in the GEOS C API, - # always set X before Y - lgeos.GEOSCoordSeq_setX(cs, i, coords[0]) - lgeos.GEOSCoordSeq_setY(cs, i, coords[1]) - if n == 3: - try: - lgeos.GEOSCoordSeq_setZ(cs, i, coords[2]) - except IndexError: - raise ValueError("Inconsistent coordinate dimensionality") + "Wrong coordinate dimensions; this geometry has " + "dimensions: %d" % update_ndim) + else: + cs = lgeos.GEOSCoordSeq_create(m, n) + + # add to coordinate sequence + for i in range(m): + coords = _coords(ob[i]) + # Because of a bug in the GEOS C API, + # always set X before Y + lgeos.GEOSCoordSeq_setX(cs, i, coords[0]) + lgeos.GEOSCoordSeq_setY(cs, i, coords[1]) + if n == 3: + try: + lgeos.GEOSCoordSeq_setZ(cs, i, coords[2]) + except IndexError: + raise ValueError("Inconsistent coordinate dimensionality") if update_geom is not None: return None @@ -302,12 +255,3 @@ def geos_linestring_from_py(ob, update_geom=None, update_ndim=0): def update_linestring_from_py(geom, ob): geos_linestring_from_py(ob, geom._geom, geom._ndim) - - -# Test runner -def _test(): - import doctest - doctest.testmod() - -if __name__ == "__main__": - _test() diff --git a/shapely/geometry/multipoint.py b/shapely/geometry/multipoint.py index d9f8908..55c5d65 100644 --- a/shapely/geometry/multipoint.py +++ b/shapely/geometry/multipoint.py @@ -9,11 +9,9 @@ if sys.version_info[0] < 3: from ctypes import byref, c_double, c_void_p, cast, POINTER from ctypes import ArgumentError -from shapely.coords import required from shapely.geos import lgeos from shapely.geometry.base import ( - BaseMultipartGeometry, exceptNull, geos_geom_from_py -) + BaseMultipartGeometry, exceptNull, geos_geom_from_py) from shapely.geometry import point from shapely.geometry.proxy import CachingGeometryProxy @@ -98,7 +96,7 @@ class MultiPoint(BaseMultipartGeometry): array_type = c_double * (m * n) data = array_type() for i in range(m): - g = self.geoms[i]._geom + g = self.geoms[i]._geom cs = lgeos.GEOSGeom_getCoordSeq(g) lgeos.GEOSCoordSeq_getX(cs, 0, byref(temp)) data[n*i] = temp.value @@ -158,57 +156,20 @@ def geos_multipoint_from_py(ob): if isinstance(ob, MultiPoint): return geos_geom_from_py(ob) - # If numpy is present, we use numpy.require to ensure that we have a - # C-continguous array that owns its data. View data will be copied. - ob = required(ob) + m = len(ob) try: - # From array protocol - array = ob.__array_interface__ - assert len(array['shape']) == 2 - m = array['shape'][0] - n = array['shape'][1] - assert m >= 1 - assert n == 2 or n == 3 - - # Make pointer to the coordinate array - if isinstance(array['data'], tuple): - # numpy tuple (addr, read-only) - cp = cast(array['data'][0], POINTER(c_double)) - else: - cp = array['data'] + n = len(ob[0]) + except TypeError: + n = ob[0]._ndim + assert n == 2 or n == 3 - # Array of pointers to sub-geometries - subs = (c_void_p * m)() + # Array of pointers to point geometries + subs = (c_void_p * m)() - for i in range(m): - geom, ndims = point.geos_point_from_py(cp[n*i:n*i+2]) - subs[i] = cast(geom, c_void_p) + # add to coordinate sequence + for i in range(m): + coords = ob[i] + geom, ndims = point.geos_point_from_py(coords) + subs[i] = cast(geom, c_void_p) - except AttributeError: - # Fall back on list - m = len(ob) - try: - n = len(ob[0]) - except TypeError: - n = ob[0]._ndim - assert n == 2 or n == 3 - - # Array of pointers to point geometries - subs = (c_void_p * m)() - - # add to coordinate sequence - for i in range(m): - coords = ob[i] - geom, ndims = point.geos_point_from_py(coords) - subs[i] = cast(geom, c_void_p) - return lgeos.GEOSGeom_createCollection(4, subs, m), n - -# Test runner -def _test(): - import doctest - doctest.testmod() - - -if __name__ == "__main__": - _test() diff --git a/shapely/geometry/point.py b/shapely/geometry/point.py index 0b8720b..70d0eeb 100644 --- a/shapely/geometry/point.py +++ b/shapely/geometry/point.py @@ -4,7 +4,6 @@ from ctypes import c_double from ctypes import cast, POINTER -from shapely.coords import required from shapely.errors import DimensionError from shapely.geos import lgeos from shapely.geometry.base import BaseGeometry, geos_geom_from_py @@ -110,8 +109,11 @@ class Point(BaseGeometry): def array_interface(self): """Provide the Numpy array protocol.""" - ai = self.array_interface_base - ai.update({'shape': (self._ndim,)}) + if self.is_empty: + ai = {'version': 3, 'typestr': '<f8', 'shape': (0,), 'data': (c_double * 0)()} + else: + ai = self.array_interface_base + ai.update({'shape': (self._ndim,)}) return ai __array_interface__ = property(array_interface) @@ -195,49 +197,20 @@ def geos_point_from_py(ob, update_geom=None, update_ndim=0): if isinstance(ob, Point): return geos_geom_from_py(ob) - # If numpy is present, we use numpy.require to ensure that we have a - # C-continguous array that owns its data. View data will be copied. - ob = required(ob) - try: - # From array protocol - array = ob.__array_interface__ - assert len(array['shape']) == 1 - n = array['shape'][0] - assert n == 2 or n == 3 - - dz = None - da = array['data'] - if isinstance(da, tuple): - cdata = da[0] - # If we had numpy, we would do - # from numpy.ctypeslib import as_ctypes - # cp = as_ctypes(ob) - check that code? - cp = cast(cdata, POINTER(c_double)) - dx = c_double(cp[0]) - dy = c_double(cp[1]) - if n == 3: - dz = c_double(cp[2]) - else: - dx, dy = da[0:2] - if n == 3: - dz = da[2] - - except AttributeError: - # Fall back on the case of Python sequence data - # Accept either (x, y) or [(x, y)] - if not hasattr(ob, '__getitem__'): # Iterators, e.g. Python 3 zip - ob = list(ob) - - if isinstance(ob[0], tuple): - coords = ob[0] - else: - coords = ob - n = len(coords) - dx = c_double(coords[0]) - dy = c_double(coords[1]) - dz = None - if n == 3: - dz = c_double(coords[2]) + # Accept either (x, y) or [(x, y)] + if not hasattr(ob, '__getitem__'): # Iterators, e.g. Python 3 zip + ob = list(ob) + + if isinstance(ob[0], tuple): + coords = ob[0] + else: + coords = ob + n = len(coords) + dx = c_double(coords[0]) + dy = c_double(coords[1]) + dz = None + if n == 3: + dz = c_double(coords[2]) if update_geom: cs = lgeos.GEOSGeom_getCoordSeq(update_geom) @@ -262,12 +235,3 @@ def geos_point_from_py(ob, update_geom=None, update_ndim=0): def update_point_from_py(geom, ob): geos_point_from_py(ob, geom._geom, geom._ndim) - - -# Test runner -def _test(): - import doctest - doctest.testmod() - -if __name__ == "__main__": - _test() diff --git a/shapely/geometry/polygon.py b/shapely/geometry/polygon.py index e492fae..9602df5 100644 --- a/shapely/geometry/polygon.py +++ b/shapely/geometry/polygon.py @@ -11,7 +11,7 @@ from ctypes import ArgumentError import weakref from shapely.algorithms.cga import signed_area -from shapely.coords import required +#from shapely.coords import required from shapely.geos import lgeos from shapely.geometry.base import BaseGeometry, geos_geom_from_py from shapely.geometry.linestring import LineString, LineStringAdapter @@ -374,6 +374,7 @@ def asPolygon(shell, holes=None): """Adapt objects to the Polygon interface""" return PolygonAdapter(shell, holes) + def orient(polygon, sign=1.0): s = float(sign) rings = [] @@ -389,136 +390,92 @@ def orient(polygon, sign=1.0): rings.append(list(ring.coords)[::-1]) return Polygon(rings[0], rings[1:]) + def geos_linearring_from_py(ob, update_geom=None, update_ndim=0): # If a LinearRing is passed in, clone it and return - # If a LineString is passed in, clone the coord seq and return a LinearRing + # If a LineString is passed in, clone the coord seq and return a + # LinearRing. + # + # NB: access to coordinates using the array protocol has been moved + # entirely to the speedups module. + if isinstance(ob, LineString): if type(ob) == LinearRing: return geos_geom_from_py(ob) + elif ob.is_closed and len(ob.coords) >= 4: + return geos_geom_from_py(ob, lgeos.GEOSGeom_createLinearRing) else: - if ob.is_closed and len(ob.coords) >= 4: - return geos_geom_from_py(ob, lgeos.GEOSGeom_createLinearRing) + ob = list(ob.coords) - # If numpy is present, we use numpy.require to ensure that we have a - # C-continguous array that owns its data. View data will be copied. - ob = required(ob) try: - # From array protocol - array = ob.__array_interface__ - assert len(array['shape']) == 2 - m = array['shape'][0] - n = array['shape'][1] - if m < 3: - raise ValueError( - "A LinearRing must have at least 3 coordinate tuples") - assert n == 2 or n == 3 + m = len(ob) + except TypeError: # Iterators, e.g. Python 3 zip + ob = list(ob) + m = len(ob) - # Make pointer to the coordinate array - if isinstance(array['data'], tuple): - # numpy tuple (addr, read-only) - cp = cast(array['data'][0], POINTER(c_double)) - else: - cp = array['data'] + if m == 0: + return None - # Add closing coordinates to sequence? - if cp[0] != cp[m*n-n] or cp[1] != cp[m*n-n+1]: - M = m + 1 - else: - M = m - - # Create a coordinate sequence - if update_geom is not None: - cs = lgeos.GEOSGeom_getCoordSeq(update_geom) - if n != update_ndim: - raise ValueError( - "Wrong coordinate dimensions; this geometry has dimensions: %d" \ - % update_ndim) - else: - cs = lgeos.GEOSCoordSeq_create(M, n) - - # add to coordinate sequence - for i in range(m): - # Because of a bug in the GEOS C API, - # always set X before Y - lgeos.GEOSCoordSeq_setX(cs, i, cp[n*i]) - lgeos.GEOSCoordSeq_setY(cs, i, cp[n*i+1]) - if n == 3: - lgeos.GEOSCoordSeq_setZ(cs, i, cp[n*i+2]) - - # Add closing coordinates to sequence? - if M > m: - # Because of a bug in the GEOS C API, - # always set X before Y - lgeos.GEOSCoordSeq_setX(cs, M-1, cp[0]) - lgeos.GEOSCoordSeq_setY(cs, M-1, cp[1]) - if n == 3: - lgeos.GEOSCoordSeq_setZ(cs, M-1, cp[2]) - - except AttributeError: - # Fall back on list - try: - m = len(ob) - except TypeError: # Iterators, e.g. Python 3 zip - ob = list(ob) - m = len(ob) + n = len(ob[0]) + if m < 3: + raise ValueError( + "A LinearRing must have at least 3 coordinate tuples") + assert (n == 2 or n == 3) - if m == 0: - return None + # Add closing coordinates if not provided + if m == 3 or ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]: + M = m + 1 + else: + M = m - n = len(ob[0]) - if m < 3: + # Create a coordinate sequence + if update_geom is not None: + if n != update_ndim: raise ValueError( - "A LinearRing must have at least 3 coordinate tuples") - assert (n == 2 or n == 3) - - # Add closing coordinates if not provided - if m == 3 or ob[0][0] != ob[-1][0] or ob[0][1] != ob[-1][1]: - M = m + 1 - else: - M = m - - # Create a coordinate sequence - if update_geom is not None: - cs = lgeos.GEOSGeom_getCoordSeq(update_geom) - if n != update_ndim: - raise ValueError( - "Wrong coordinate dimensions; this geometry has dimensions: %d" \ - % update_ndim) - else: - cs = lgeos.GEOSCoordSeq_create(M, n) - - # add to coordinate sequence - for i in range(m): - coords = ob[i] - # Because of a bug in the GEOS C API, - # always set X before Y - lgeos.GEOSCoordSeq_setX(cs, i, coords[0]) - lgeos.GEOSCoordSeq_setY(cs, i, coords[1]) - if n == 3: - try: - lgeos.GEOSCoordSeq_setZ(cs, i, coords[2]) - except IndexError: - raise ValueError("Inconsistent coordinate dimensionality") - - # Add closing coordinates to sequence? - if M > m: - coords = ob[0] - # Because of a bug in the GEOS C API, - # always set X before Y - lgeos.GEOSCoordSeq_setX(cs, M-1, coords[0]) - lgeos.GEOSCoordSeq_setY(cs, M-1, coords[1]) - if n == 3: - lgeos.GEOSCoordSeq_setZ(cs, M-1, coords[2]) + "Coordinate dimensions mismatch: target geom has {} dims, " + "update geom has {} dims".format(n, update_ndim)) + cs = lgeos.GEOSGeom_getCoordSeq(update_geom) + else: + cs = lgeos.GEOSCoordSeq_create(M, n) + + # add to coordinate sequence + for i in range(m): + coords = ob[i] + # Because of a bug in the GEOS C API, + # always set X before Y + lgeos.GEOSCoordSeq_setX(cs, i, coords[0]) + lgeos.GEOSCoordSeq_setY(cs, i, coords[1]) + if n == 3: + try: + lgeos.GEOSCoordSeq_setZ(cs, i, coords[2]) + except IndexError: + raise ValueError("Inconsistent coordinate dimensionality") + + # Add closing coordinates to sequence? + if M > m: + coords = ob[0] + # Because of a bug in the GEOS C API, + # always set X before Y + lgeos.GEOSCoordSeq_setX(cs, M-1, coords[0]) + lgeos.GEOSCoordSeq_setY(cs, M-1, coords[1]) + if n == 3: + lgeos.GEOSCoordSeq_setZ(cs, M-1, coords[2]) if update_geom is not None: return None else: return lgeos.GEOSGeom_createLinearRing(cs), n + def update_linearring_from_py(geom, ob): geos_linearring_from_py(ob, geom._geom, geom._ndim) + def geos_polygon_from_py(shell, holes=None): + + if shell is None: + return None + if isinstance(shell, Polygon): return geos_geom_from_py(shell) @@ -526,6 +483,7 @@ def geos_polygon_from_py(shell, holes=None): ret = geos_linearring_from_py(shell) if ret is None: return None + geos_shell, ndim = ret if holes is not None and len(holes) > 0: ob = holes @@ -550,19 +508,7 @@ def geos_polygon_from_py(shell, holes=None): else: geos_holes = POINTER(c_void_p)() L = 0 + return ( lgeos.GEOSGeom_createPolygon( - c_void_p(geos_shell), - geos_holes, - L - ), - ndim - ) - -# Test runner -def _test(): - import doctest - doctest.testmod() - -if __name__ == "__main__": - _test() + c_void_p(geos_shell), geos_holes, L), ndim) diff --git a/shapely/speedups/_speedups.pyx b/shapely/speedups/_speedups.pyx index 458b135..72da797 100644 --- a/shapely/speedups/_speedups.pyx +++ b/shapely/speedups/_speedups.pyx @@ -6,8 +6,8 @@ # Transcription to cython: Copyright (c) 2011, Oliver Tonnhofer import ctypes +import numpy -from shapely.coords import required from shapely.geos import lgeos from shapely.geometry import Point, LineString, LinearRing from shapely.geometry.base import geom_factory @@ -32,6 +32,16 @@ def destroy(geom): GEOSGeom_destroy_r(cast_handle(lgeos.geos_handle), cast_geom(geom)) +def required(ob): + """Return an object that meets Shapely requirements for self-owned + C-continguous data, copying if necessary, or just return the original + object.""" + if hasattr(ob, '__array_interface__'): + return numpy.require(ob, numpy.float64, ["C", "OWNDATA"]) + else: + return ob + + def geos_linestring_from_py(ob, update_geom=None, update_ndim=0): cdef double *cp cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) diff --git a/tests/test_linestring.py b/tests/test_linestring.py index 2c50a5b..90c5fa8 100644 --- a/tests/test_linestring.py +++ b/tests/test_linestring.py @@ -154,6 +154,11 @@ class LineStringTestCase(unittest.TestCase): b = asarray(line) assert_array_equal(b, array([[0., 0.], [2., 2.], [1., 1.]])) + # Test array interface of empty linestring + le = LineString() + a = asarray(le) + assert(a.shape[0], 0) + def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LineStringTestCase) diff --git a/tests/test_ndarrays.py b/tests/test_ndarrays.py index e9b9f6d..f8db1dd 100644 --- a/tests/test_ndarrays.py +++ b/tests/test_ndarrays.py @@ -20,10 +20,10 @@ class TransposeTestCase(unittest.TestCase): @unittest.skipIf(not numpy, 'numpy not installed') def test_multipoint(self): - a = numpy.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]]) - t = a.T - s = geometry.asMultiPoint(t) - coords = reduce(lambda x, y: x + y, [list(g.coords) for g in s]) + arr = numpy.array([[1.0, 1.0, 2.0, 2.0, 1.0], [3.0, 4.0, 4.0, 3.0, 3.0]]) + tarr = arr.T + shape = geometry.asMultiPoint(tarr) + coords = reduce(lambda x, y: x + y, [list(g.coords) for g in shape]) self.assertEqual( coords, [(1.0, 3.0), (1.0, 4.0), (2.0, 4.0), (2.0, 3.0), (1.0, 3.0)] diff --git a/tests/test_point.py b/tests/test_point.py index d481a3a..452d034 100644 --- a/tests/test_point.py +++ b/tests/test_point.py @@ -134,6 +134,11 @@ class LineStringTestCase(unittest.TestCase): p = Point(*list(a)) self.assertEqual(p.coords[:], [(1.0, 1.0, 0.0)]) + # Test array interface of empty geometry + pe = Point() + a = asarray(pe) + self.assertEqual(a.shape[0], 0) + def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(LineStringTestCase) diff --git a/tests/test_vectorized.py b/tests/test_vectorized.py index ea1d956..549b354 100644 --- a/tests/test_vectorized.py +++ b/tests/test_vectorized.py @@ -1,16 +1,18 @@ from . import unittest, numpy from shapely.geometry import Point, box, MultiPolygon -from shapely.vectorized import contains, touches + try: import numpy as np + has_numpy = True except ImportError: - pass + has_numpy = False [email protected](not numpy, 'numpy required') [email protected](not has_numpy, 'numpy required') class VectorizedContainsTestCase(unittest.TestCase): def assertContainsResults(self, geom, x, y): + from shapely.vectorized import contains result = contains(geom, x, y) x = np.asanyarray(x) y = np.asanyarray(y) @@ -85,9 +87,10 @@ class VectorizedContainsTestCase(unittest.TestCase): self.assertContainsResults(self.construct_torus(), *g.exterior.xy) [email protected](not numpy, 'numpy required') [email protected](not has_numpy, 'numpy required') class VectorizedTouchesTestCase(unittest.TestCase): def test_touches(self): + from shapely.vectorized import touches y, x = np.mgrid[-2:3:6j, -1:3:5j] geom = box(0, -1, 2, 2) result = touches(geom, x, y) @@ -99,8 +102,6 @@ class VectorizedTouchesTestCase(unittest.TestCase): [False, False, False, False, False]], dtype=bool) from numpy.testing import assert_array_equal assert_array_equal(result, expected) - - def test_suite(): -- 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

