""" 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." # TODO when dropping support for GEOS 3.13 the z argument can be # deprecated in favor of using the GEOS function GEOSCoordSeq_hasZ. 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 elif self.dims == 3 and self.hasm: n_args = 3 point_setter = self._set_point_3d_m elif self.dims == 4 and self._z and self.hasm: n_args = 4 point_setter = self._set_point_4d 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(f"Invalid GEOS Geometry index: {index}") def _checkdim(self, dim): "Check the given dimension." if dim < 0 or dim > 3: raise GEOSException(f'Invalid ordinate dimension: "{dim:d}"') 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 _get_m(self, index): return capi.cs_getm(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) def _set_m(self, index, value): capi.cs_setm(self.ptr, index, value) @property def _point_getter(self): if self.dims == 3 and self._z: return self._get_point_3d elif self.dims == 3 and self.hasm: return self._get_point_3d_m elif self.dims == 4 and self._z and self.hasm: return self._get_point_4d return 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 _get_point_3d_m(self, index): return (self._get_x(index), self._get_y(index), self._get_m(index)) def _get_point_4d(self, index): return ( self._get_x(index), self._get_y(index), self._get_z(index), self._get_m(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) def _set_point_3d_m(self, index, value): x, y, m = value self._set_x(index, x) self._set_y(index, y) self._set_m(index, m) def _set_point_4d(self, index, value): x, y, z, m = value self._set_x(index, x) self._set_y(index, y) self._set_z(index, z) self._set_m(index, m) # #### 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) def getM(self, index): "Get M with the value at the given index." return self.getOrdinate(3, index) def setM(self, index, value): "Set M with the value at the given index." self.setOrdinate(3, 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 @property def hasm(self): """ Return whether this coordinate sequence has M dimension. """ if geos_version_tuple() >= (3, 14): return capi.cs_hasm(self._ptr) else: raise NotImplementedError( "GEOSCoordSeq with an M dimension requires GEOS 3.14+." ) # ### 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." if self.hasz: coords = [f"{coord[0]},{coord[1]},{coord[2]}" for coord in self] else: coords = [f"{coord[0]},{coord[1]},0" for coord in self] coordinate_string = " ".join(coords) return f"{coordinate_string}" @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.""" 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