# geos_linestring_from_py was transcribed from shapely.geometry.linestring # geos_linearring_from_py was transcribed from shapely.geometry.polygon # coordseq_ctypes was transcribed from shapely.coords.CoordinateSequence.ctypes # # Copyright (c) 2007, Sean C. Gillies # Transcription to cython: Copyright (c) 2011, Oliver Tonnhofer import ctypes import logging from shapely.geos import lgeos from shapely.geometry import Point, LineString, LinearRing from shapely.geometry.base import geom_factory from shapely.errors import TopologicalError include "../_geos.pxi" from libc.stdint cimport uintptr_t log = logging.getLogger(__name__) try: import numpy has_numpy = True except ImportError: log.info("Numpy was not imported, continuing without requires()") has_numpy = False numpy = None cdef inline GEOSGeometry *cast_geom(uintptr_t geom_addr): return geom_addr cdef inline GEOSContextHandle_t cast_handle(uintptr_t handle_addr): return handle_addr cdef inline GEOSCoordSequence *cast_seq(uintptr_t handle_addr): return handle_addr 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 has_numpy and 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) cdef GEOSCoordSequence *cs cdef GEOSGeometry *g cdef double dx, dy, dz cdef int i, n, m, sm, sn # If a LineString is passed in, just clone it and return # If a LinearRing is passed in, clone the coord seq and return a LineString if isinstance(ob, LineString): g = cast_geom(ob._geom) if GEOSHasZ_r(handle, g): n = 3 else: n = 2 if type(ob) == LineString: return GEOSGeom_clone_r(handle, g), n else: cs = GEOSGeom_getCoordSeq_r(handle, g) cs = GEOSCoordSeq_clone_r(handle, cs) return GEOSGeom_createLineString_r(handle, cs), n # 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 # Make pointer to the coordinate array if isinstance(array['data'], ctypes.Array): cp = ctypes.addressof(array['data']) else: cp = array['data'][0] # Use strides to properly index into cp # ob[i, j] == cp[sm*i + sn*j] # Just to avoid a referenced before assignment warning. dx = 0 if array.get('strides', None): sm = array['strides'][0]/sizeof(dx) sn = array['strides'][1]/sizeof(dx) else: sm = n sn = 1 # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, m, n) # add to coordinate sequence for i in xrange(m): dx = cp[sm*i] dy = cp[sm*i+sn] dz = 0 if n == 3: dz = cp[sm*i+2*sn] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, 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 elif m < 2: raise ValueError( "LineStrings must have at least 2 coordinate tuples") if m < 2: raise ValueError( "LineStrings must have at least 2 coordinate tuples") 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 # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, m, n) # add to coordinate sequence for i in xrange(m): coords = _coords(ob[i]) dx = coords[0] dy = coords[1] dz = 0 if n == 3: try: dz = coords[2] except IndexError: raise ValueError("Inconsistent coordinate dimensionality") # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) if update_geom is not None: return None else: g = GEOSGeom_createLineString_r(handle, cs); if not g: raise ValueError("GEOSGeom_createLineString_r returned a NULL pointer") return g, n def geos_linearring_from_py(ob, update_geom=None, update_ndim=0): cdef double *cp cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSGeometry *g cdef GEOSCoordSequence *cs cdef double dx, dy, dz cdef unsigned int m cdef int i, n, M, sm, sn # If a LinearRing is passed in, just clone it and return # If a valid LineString is passed in, clone the coord seq and return a LinearRing if isinstance(ob, LineString): g = cast_geom(ob._geom) if GEOSHasZ_r(handle, g): n = 3 else: n = 2 if type(ob) == LinearRing: return GEOSGeom_clone_r(handle, g), n else: cs = GEOSGeom_getCoordSeq_r(handle, g) GEOSCoordSeq_getSize_r(handle, cs, &m) if not GEOSisValid_r(handle, g): raise TopologicalError("A LineString must be valid.") elif GEOSisClosed_r(handle, g) and m >= 4: cs = GEOSCoordSeq_clone_r(handle, cs) return GEOSGeom_createLinearRing_r(handle, cs), n # else continue below. # 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 # Make pointer to the coordinate array if isinstance(array['data'], ctypes.Array): cp = ctypes.addressof(array['data']) else: cp = array['data'][0] # Use strides to properly index into cp # ob[i, j] == cp[sm*i + sn*j] dx = 0 # Just to avoid a referenced before assignment warning. if array.get('strides', None): sm = array['strides'][0]/sizeof(dx) sn = array['strides'][1]/sizeof(dx) else: sm = n sn = 1 # Add closing coordinates to sequence? # Check whether the first set of coordinates matches the last. # If not, we'll have to close the ring later if (cp[0] != cp[sm*(m-1)] or cp[sn] != cp[sm*(m-1)+sn] or (n == 3 and cp[2*sn] != cp[sm*(m-1)+2*sn]) or m == 3): M = m + 1 else: M = m # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, M, n) # add to coordinate sequence for i in xrange(m): dx = cp[sm*i] dy = cp[sm*i+sn] dz = 0 if n == 3: dz = cp[sm*i+2*sn] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) # Add closing coordinates to sequence? if M > m: dx = cp[0] dy = cp[sn] dz = 0 if n == 3: dz = cp[2*sn] # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, M-1, dx) GEOSCoordSeq_setY_r(handle, cs, M-1, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, M-1, 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 def _coords(o): if isinstance(o, Point): return o.coords[0] else: return o n = len(_coords(ob[0])) if m < 3: 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 _coords(ob[0])[0] != _coords(ob[-1])[0] or _coords(ob[0])[1] != _coords(ob[-1])[1] ): M = m + 1 else: M = m # Create a coordinate sequence if update_geom is not None: cs = GEOSGeom_getCoordSeq_r(handle, cast_geom(update_geom)) if n != update_ndim: raise ValueError( "Wrong coordinate dimensions; this geometry has dimensions: %d" \ % update_ndim) else: cs = GEOSCoordSeq_create_r(handle, M, n) # add to coordinate sequence for i in xrange(m): coords = _coords(ob[i]) dx = coords[0] dy = coords[1] dz = 0 if n == 3: try: dz = coords[2] except IndexError: raise ValueError("Inconsistent coordinate dimensionality") # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, i, dx) GEOSCoordSeq_setY_r(handle, cs, i, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, i, dz) # Add closing coordinates to sequence? if M > m: coords = _coords(ob[0]) dx = coords[0] dy = coords[1] dz = 0 if n == 3: try: dz = coords[2] except IndexError: raise ValueError("Inconsistent coordinate dimensionality") # Because of a bug in the GEOS C API, # always set X before Y GEOSCoordSeq_setX_r(handle, cs, M-1, dx) GEOSCoordSeq_setY_r(handle, cs, M-1, dy) if n == 3: GEOSCoordSeq_setZ_r(handle, cs, M-1, dz) if update_geom is not None: return None else: g = GEOSGeom_createLinearRing_r(handle, cs) if not g: raise ValueError("GEOSGeom_createLinearRing_r returned a NULL pointer") return g, n def coordseq_ctypes(self): cdef int i, n, m cdef double temp = 0 cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cdef double *data_p self._update() n = self._ndim m = self.__len__() array_type = ctypes.c_double * (m * n) data = array_type() cs = cast_seq(self._cseq) data_p = ctypes.addressof(data) for i in xrange(m): GEOSCoordSeq_getX_r(handle, cs, i, &temp) data_p[n*i] = temp GEOSCoordSeq_getY_r(handle, cs, i, &temp) data_p[n*i+1] = temp if n == 3: # TODO: use hasz GEOSCoordSeq_getZ_r(handle, cs, i, &temp) data_p[n*i+2] = temp return data def coordseq_iter(self): cdef int i cdef double dx cdef double dy cdef double dz cdef int has_z self._update() cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cs = cast_seq(self._cseq) has_z = self._ndim == 3 for i in range(self.__len__()): GEOSCoordSeq_getX_r(handle, cs, i, &dx) GEOSCoordSeq_getY_r(handle, cs, i, &dy) if has_z == 1: GEOSCoordSeq_getZ_r(handle, cs, i, &dz) yield (dx, dy, dz) else: yield (dx, dy) cdef GEOSCoordSequence* transform(GEOSCoordSequence* cs, int ndim, double a, double b, double c, double d, double e, double f, double g, double h, double i, double xoff, double yoff, double zoff): """Performs an affine transformation on a GEOSCoordSequence Returns the transformed coordinate sequence """ cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef unsigned int m cdef GEOSCoordSequence *cs_t cdef double x, y, z cdef double x_t, y_t, z_t # create a new coordinate sequence with the same size and dimensions GEOSCoordSeq_getSize_r(handle, cs, &m) cs_t = GEOSCoordSeq_create_r(handle, m, ndim) # perform the transform if ndim == 2: for n in range(0, m): GEOSCoordSeq_getX_r(handle, cs, n, &x) GEOSCoordSeq_getY_r(handle, cs, n, &y) x_t = a * x + b * y + xoff y_t = d * x + e * y + yoff GEOSCoordSeq_setX_r(handle, cs_t, n, x_t) GEOSCoordSeq_setY_r(handle, cs_t, n, y_t) if ndim == 3: for n in range(0, m): GEOSCoordSeq_getX_r(handle, cs, n, &x) GEOSCoordSeq_getY_r(handle, cs, n, &y) GEOSCoordSeq_getZ_r(handle, cs, n, &z) x_t = a * x + b * y + c * z + xoff y_t = d * x + e * y + f * z + yoff z_t = g * x + h * y + i * z + zoff GEOSCoordSeq_setX_r(handle, cs_t, n, x_t) GEOSCoordSeq_setY_r(handle, cs_t, n, y_t) GEOSCoordSeq_setZ_r(handle, cs_t, n, z_t) return cs_t cpdef affine_transform(geom, matrix): cdef double a, b, c, d, e, f, g, h, i, xoff, yoff, zoff if geom.is_empty: return geom if len(matrix) == 6: ndim = 2 a, b, d, e, xoff, yoff = matrix if geom.has_z: ndim = 3 i = 1.0 c = f = g = h = zoff = 0.0 matrix = a, b, c, d, e, f, g, h, i, xoff, yoff, zoff elif len(matrix) == 12: ndim = 3 a, b, c, d, e, f, g, h, i, xoff, yoff, zoff = matrix if not geom.has_z: ndim = 2 matrix = a, b, d, e, xoff, yoff else: raise ValueError("'matrix' expects either 6 or 12 coefficients") cdef GEOSContextHandle_t handle = cast_handle(lgeos.geos_handle) cdef GEOSCoordSequence *cs cdef GEOSCoordSequence *cs_t cdef GEOSGeometry *the_geom cdef GEOSGeometry *the_geom_t cdef int m, n cdef double x, y, z cdef double x_t, y_t, z_t # Process coordinates from each supported geometry type if geom.type in ('Point', 'LineString', 'LinearRing'): the_geom = cast_geom(geom._geom) cs = GEOSGeom_getCoordSeq_r(handle, the_geom) # perform the transformation cs_t = transform(cs, ndim, a, b, c, d, e, f, g, h, i, xoff, yoff, zoff) # create a new geometry from the coordinate sequence if geom.type == 'Point': the_geom_t = GEOSGeom_createPoint_r(handle, cs_t) elif geom.type == 'LineString': the_geom_t = GEOSGeom_createLineString_r(handle, cs_t) elif geom.type == 'LinearRing': the_geom_t = GEOSGeom_createLinearRing_r(handle, cs_t) # return the geometry as a Python object return geom_factory(the_geom_t) elif geom.type == 'Polygon': ring = geom.exterior shell = affine_transform(ring, matrix) holes = list(geom.interiors) for pos, ring in enumerate(holes): holes[pos] = affine_transform(ring, matrix) return type(geom)(shell, holes) elif geom.type.startswith('Multi') or geom.type == 'GeometryCollection': return type(geom)([affine_transform(part, matrix) for part in geom.geoms]) else: raise ValueError('Type %r not recognized' % geom.type)