""" This module houses the GEOSCoordSeq object, which is used internally by GEOSGeometry to house the actual coordinates of the Point, LineString, and LinearRing geometries. """ from ctypes import byref, c_byte, c_double, c_uint from django.contrib.gis.geos import prototypes as capi from django.contrib.gis.geos.base import GEOSBase from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.libgeos import CS_PTR, geos_version_tuple from django.contrib.gis.shortcuts import numpy class GEOSCoordSeq(GEOSBase): "The internal representation of a list of coordinates inside a Geometry." ptr_type = CS_PTR def __init__(self, ptr, z=False): "Initialize from a GEOS pointer." if not isinstance(ptr, CS_PTR): raise TypeError('Coordinate sequence should initialize with a CS_PTR.') self._ptr = ptr self._z = z def __iter__(self): "Iterate over each point in the coordinate sequence." for i in range(self.size): yield self[i] def __len__(self): "Return the number of points in the coordinate sequence." return self.size def __str__(self): "Return the string representation of the coordinate sequence." return str(self.tuple) def __getitem__(self, index): "Return the coordinate sequence value at the given index." self._checkindex(index) return self._point_getter(index) def __setitem__(self, index, value): "Set the coordinate sequence value at the given index." # Checking the input value if isinstance(value, (list, tuple)): pass elif numpy and isinstance(value, numpy.ndarray): pass else: raise TypeError('Must set coordinate with a sequence (list, tuple, or numpy array).') # Checking the dims of the input if self.dims == 3 and self._z: n_args = 3 point_setter = self._set_point_3d else: n_args = 2 point_setter = self._set_point_2d if len(value) != n_args: raise TypeError('Dimension of value does not match.') self._checkindex(index) point_setter(index, value) # #### Internal Routines #### def _checkindex(self, index): "Check the given index." if not (0 <= index < self.size): raise IndexError('invalid GEOS Geometry index: %s' % index) def _checkdim(self, dim): "Check the given dimension." if dim < 0 or dim > 2: raise GEOSException('invalid ordinate dimension "%d"' % dim) def _get_x(self, index): return capi.cs_getx(self.ptr, index, byref(c_double())) def _get_y(self, index): return capi.cs_gety(self.ptr, index, byref(c_double())) def _get_z(self, index): return capi.cs_getz(self.ptr, index, byref(c_double())) def _set_x(self, index, value): capi.cs_setx(self.ptr, index, value) def _set_y(self, index, value): capi.cs_sety(self.ptr, index, value) def _set_z(self, index, value): capi.cs_setz(self.ptr, index, value) @property def _point_getter(self): return self._get_point_3d if self.dims == 3 and self._z else self._get_point_2d def _get_point_2d(self, index): return (self._get_x(index), self._get_y(index)) def _get_point_3d(self, index): return (self._get_x(index), self._get_y(index), self._get_z(index)) def _set_point_2d(self, index, value): x, y = value self._set_x(index, x) self._set_y(index, y) def _set_point_3d(self, index, value): x, y, z = value self._set_x(index, x) self._set_y(index, y) self._set_z(index, z) # #### Ordinate getting and setting routines #### def getOrdinate(self, dimension, index): "Return the value for the given dimension and index." self._checkindex(index) self._checkdim(dimension) return capi.cs_getordinate(self.ptr, index, dimension, byref(c_double())) def setOrdinate(self, dimension, index, value): "Set the value for the given dimension and index." self._checkindex(index) self._checkdim(dimension) capi.cs_setordinate(self.ptr, index, dimension, value) def getX(self, index): "Get the X value at the index." return self.getOrdinate(0, index) def setX(self, index, value): "Set X with the value at the given index." self.setOrdinate(0, index, value) def getY(self, index): "Get the Y value at the given index." return self.getOrdinate(1, index) def setY(self, index, value): "Set Y with the value at the given index." self.setOrdinate(1, index, value) def getZ(self, index): "Get Z with the value at the given index." return self.getOrdinate(2, index) def setZ(self, index, value): "Set Z with the value at the given index." self.setOrdinate(2, index, value) # ### Dimensions ### @property def size(self): "Return the size of this coordinate sequence." return capi.cs_getsize(self.ptr, byref(c_uint())) @property def dims(self): "Return the dimensions of this coordinate sequence." return capi.cs_getdims(self.ptr, byref(c_uint())) @property def hasz(self): """ Return whether this coordinate sequence is 3D. This property value is inherited from the parent Geometry. """ return self._z # ### Other Methods ### def clone(self): "Clone this coordinate sequence." return GEOSCoordSeq(capi.cs_clone(self.ptr), self.hasz) @property def kml(self): "Return the KML representation for the coordinates." # Getting the substitution string depending on whether the coordinates have # a Z dimension. if self.hasz: substr = '%s,%s,%s ' else: substr = '%s,%s,0 ' return '%s' % \ ''.join(substr % self[i] for i in range(len(self))).strip() @property def tuple(self): "Return a tuple version of this coordinate sequence." n = self.size get_point = self._point_getter if n == 1: return get_point(0) return tuple(get_point(i) for i in range(n)) @property def is_counterclockwise(self): """Return whether this coordinate sequence is counterclockwise.""" if geos_version_tuple() < (3, 7): # A modified shoelace algorithm to determine polygon orientation. # See https://en.wikipedia.org/wiki/Shoelace_formula. area = 0.0 n = len(self) for i in range(n): j = (i + 1) % n area += self[i][0] * self[j][1] area -= self[j][0] * self[i][1] return area > 0.0 ret = c_byte() if not capi.cs_is_ccw(self.ptr, byref(ret)): raise GEOSException( 'Error encountered in GEOS C function "%s".' % capi.cs_is_ccw.func_name ) return ret.value == 1