| """ |
| This module houses the Point, LineString, LinearRing, and Polygon OGC |
| geometry classes. All geometry classes in this module inherit from |
| GEOSGeometry. |
| """ |
| from ctypes import c_uint, byref |
| from django.contrib.gis.geos.base import GEOSGeometry |
| from django.contrib.gis.geos.coordseq import GEOSCoordSeq |
| from django.contrib.gis.geos.error import GEOSException, GEOSIndexError |
| from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY |
| from django.contrib.gis.geos.prototypes import * |
| if HAS_NUMPY: from numpy import ndarray, array |
| |
| class Point(GEOSGeometry): |
| |
| def __init__(self, x, y=None, z=None, srid=None): |
| """ |
| The Point object may be initialized with either a tuple, or individual |
| parameters. |
| |
| For Example: |
| >>> p = Point((5, 23)) # 2D point, passed in as a tuple |
| >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters |
| """ |
| |
| if isinstance(x, (tuple, list)): |
| # Here a tuple or list was passed in under the `x` parameter. |
| ndim = len(x) |
| if ndim < 2 or ndim > 3: |
| raise TypeError('Invalid sequence parameter: %s' % str(x)) |
| coords = x |
| elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)): |
| # Here X, Y, and (optionally) Z were passed in individually, as parameters. |
| if isinstance(z, (int, float, long)): |
| ndim = 3 |
| coords = [x, y, z] |
| else: |
| ndim = 2 |
| coords = [x, y] |
| else: |
| raise TypeError('Invalid parameters given for Point initialization.') |
| |
| # Creating the coordinate sequence, and setting X, Y, [Z] |
| cs = create_cs(c_uint(1), c_uint(ndim)) |
| cs_setx(cs, 0, coords[0]) |
| cs_sety(cs, 0, coords[1]) |
| if ndim == 3: cs_setz(cs, 0, coords[2]) |
| |
| # Initializing using the address returned from the GEOS |
| # createPoint factory. |
| super(Point, self).__init__(create_point(cs), srid=srid) |
| |
| def __len__(self): |
| "Returns the number of dimensions for this Point (either 0, 2 or 3)." |
| if self.empty: return 0 |
| if self.hasz: return 3 |
| else: return 2 |
| |
| def get_x(self): |
| "Returns the X component of the Point." |
| return self._cs.getOrdinate(0, 0) |
| |
| def set_x(self, value): |
| "Sets the X component of the Point." |
| self._cs.setOrdinate(0, 0, value) |
| |
| def get_y(self): |
| "Returns the Y component of the Point." |
| return self._cs.getOrdinate(1, 0) |
| |
| def set_y(self, value): |
| "Sets the Y component of the Point." |
| self._cs.setOrdinate(1, 0, value) |
| |
| def get_z(self): |
| "Returns the Z component of the Point." |
| if self.hasz: |
| return self._cs.getOrdinate(2, 0) |
| else: |
| return None |
| |
| def set_z(self, value): |
| "Sets the Z component of the Point." |
| if self.hasz: |
| self._cs.setOrdinate(2, 0, value) |
| else: |
| raise GEOSException('Cannot set Z on 2D Point.') |
| |
| # X, Y, Z properties |
| x = property(get_x, set_x) |
| y = property(get_y, set_y) |
| z = property(get_z, set_z) |
| |
| ### Tuple setting and retrieval routines. ### |
| def get_coords(self): |
| "Returns a tuple of the point." |
| return self._cs.tuple |
| |
| def set_coords(self, tup): |
| "Sets the coordinates of the point with the given tuple." |
| self._cs[0] = tup |
| |
| # The tuple and coords properties |
| tuple = property(get_coords, set_coords) |
| coords = tuple |
| |
| class LineString(GEOSGeometry): |
| |
| #### Python 'magic' routines #### |
| def __init__(self, *args, **kwargs): |
| """ |
| Initializes on the given sequence -- may take lists, tuples, NumPy arrays |
| of X,Y pairs, or Point objects. If Point objects are used, ownership is |
| _not_ transferred to the LineString object. |
| |
| Examples: |
| ls = LineString((1, 1), (2, 2)) |
| ls = LineString([(1, 1), (2, 2)]) |
| ls = LineString(array([(1, 1), (2, 2)])) |
| ls = LineString(Point(1, 1), Point(2, 2)) |
| """ |
| # If only one argument provided, set the coords array appropriately |
| if len(args) == 1: coords = args[0] |
| else: coords = args |
| |
| if isinstance(coords, (tuple, list)): |
| # Getting the number of coords and the number of dimensions -- which |
| # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)). |
| ncoords = len(coords) |
| if coords: ndim = len(coords[0]) |
| else: raise TypeError('Cannot initialize on empty sequence.') |
| self._checkdim(ndim) |
| # Incrementing through each of the coordinates and verifying |
| for i in xrange(1, ncoords): |
| if not isinstance(coords[i], (tuple, list, Point)): |
| raise TypeError('each coordinate should be a sequence (list or tuple)') |
| if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.') |
| numpy_coords = False |
| elif HAS_NUMPY and isinstance(coords, ndarray): |
| shape = coords.shape # Using numpy's shape. |
| if len(shape) != 2: raise TypeError('Too many dimensions.') |
| self._checkdim(shape[1]) |
| ncoords = shape[0] |
| ndim = shape[1] |
| numpy_coords = True |
| else: |
| raise TypeError('Invalid initialization input for LineStrings.') |
| |
| # Creating a coordinate sequence object because it is easier to |
| # set the points using GEOSCoordSeq.__setitem__(). |
| cs = GEOSCoordSeq(create_cs(ncoords, ndim), z=bool(ndim==3)) |
| for i in xrange(ncoords): |
| if numpy_coords: cs[i] = coords[i,:] |
| elif isinstance(coords[i], Point): cs[i] = coords[i].tuple |
| else: cs[i] = coords[i] |
| |
| # Getting the correct initialization function |
| if kwargs.get('ring', False): |
| func = create_linearring |
| else: |
| func = create_linestring |
| |
| # If SRID was passed in with the keyword arguments |
| srid = kwargs.get('srid', None) |
| |
| # Calling the base geometry initialization with the returned pointer |
| # from the function. |
| super(LineString, self).__init__(func(cs.ptr), srid=srid) |
| |
| def __getitem__(self, index): |
| "Gets the point at the specified index." |
| return self._cs[index] |
| |
| def __setitem__(self, index, value): |
| "Sets the point at the specified index, e.g., line_str[0] = (1, 2)." |
| self._cs[index] = value |
| |
| def __iter__(self): |
| "Allows iteration over this LineString." |
| for i in xrange(len(self)): |
| yield self[i] |
| |
| def __len__(self): |
| "Returns the number of points in this LineString." |
| return len(self._cs) |
| |
| def _checkdim(self, dim): |
| if dim not in (2, 3): raise TypeError('Dimension mismatch.') |
| |
| #### Sequence Properties #### |
| @property |
| def tuple(self): |
| "Returns a tuple version of the geometry from the coordinate sequence." |
| return self._cs.tuple |
| coords = tuple |
| |
| def _listarr(self, func): |
| """ |
| Internal routine that returns a sequence (list) corresponding with |
| the given function. Will return a numpy array if possible. |
| """ |
| lst = [func(i) for i in xrange(len(self))] |
| if HAS_NUMPY: return array(lst) # ARRRR! |
| else: return lst |
| |
| @property |
| def array(self): |
| "Returns a numpy array for the LineString." |
| return self._listarr(self._cs.__getitem__) |
| |
| @property |
| def x(self): |
| "Returns a list or numpy array of the X variable." |
| return self._listarr(self._cs.getX) |
| |
| @property |
| def y(self): |
| "Returns a list or numpy array of the Y variable." |
| return self._listarr(self._cs.getY) |
| |
| @property |
| def z(self): |
| "Returns a list or numpy array of the Z variable." |
| if not self.hasz: return None |
| else: return self._listarr(self._cs.getZ) |
| |
| # LinearRings are LineStrings used within Polygons. |
| class LinearRing(LineString): |
| def __init__(self, *args, **kwargs): |
| "Overriding the initialization function to set the ring keyword." |
| kwargs['ring'] = True # Setting the ring keyword argument to True |
| super(LinearRing, self).__init__(*args, **kwargs) |
| |
| class Polygon(GEOSGeometry): |
| |
| def __init__(self, *args, **kwargs): |
| """ |
| Initializes on an exterior ring and a sequence of holes (both |
| instances may be either LinearRing instances, or a tuple/list |
| that may be constructed into a LinearRing). |
| |
| Examples of initialization, where shell, hole1, and hole2 are |
| valid LinearRing geometries: |
| >>> poly = Polygon(shell, hole1, hole2) |
| >>> poly = Polygon(shell, (hole1, hole2)) |
| |
| Example where a tuple parameters are used: |
| >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), |
| ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4))) |
| """ |
| if not args: |
| raise TypeError('Must provide at list one LinearRing instance to initialize Polygon.') |
| |
| # Getting the ext_ring and init_holes parameters from the argument list |
| ext_ring = args[0] |
| init_holes = args[1:] |
| n_holes = len(init_holes) |
| |
| # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility] |
| if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \ |
| (len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)): |
| init_holes = init_holes[0] |
| n_holes = len(init_holes) |
| |
| # Ensuring the exterior ring and holes parameters are LinearRing objects |
| # or may be instantiated into LinearRings. |
| ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.') |
| holes_list = [] # Create new list, cause init_holes is a tuple. |
| for i in xrange(n_holes): |
| holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings')) |
| |
| # Why another loop? Because if a TypeError is raised, cloned pointers will |
| # be around that can't be cleaned up. |
| holes = get_pointer_arr(n_holes) |
| for i in xrange(n_holes): holes[i] = geom_clone(holes_list[i].ptr) |
| |
| # Getting the shell pointer address. |
| shell = geom_clone(ext_ring.ptr) |
| |
| # Calling with the GEOS createPolygon factory. |
| super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs) |
| |
| def __getitem__(self, index): |
| """ |
| Returns the ring at the specified index. The first index, 0, will |
| always return the exterior ring. Indices > 0 will return the |
| interior ring at the given index (e.g., poly[1] and poly[2] would |
| return the first and second interior ring, respectively). |
| """ |
| if index == 0: |
| return self.exterior_ring |
| else: |
| # Getting the interior ring, have to subtract 1 from the index. |
| return self.get_interior_ring(index-1) |
| |
| def __setitem__(self, index, ring): |
| "Sets the ring at the specified index with the given ring." |
| # Checking the index and ring parameters. |
| self._checkindex(index) |
| if not isinstance(ring, LinearRing): |
| raise TypeError('must set Polygon index with a LinearRing object') |
| |
| # Getting the shell |
| if index == 0: |
| shell = geom_clone(ring.ptr) |
| else: |
| shell = geom_clone(get_extring(self.ptr)) |
| |
| # Getting the interior rings (holes) |
| nholes = len(self)-1 |
| if nholes > 0: |
| holes = get_pointer_arr(nholes) |
| for i in xrange(nholes): |
| if i == (index-1): |
| holes[i] = geom_clone(ring.ptr) |
| else: |
| holes[i] = geom_clone(get_intring(self.ptr, i)) |
| holes_param = byref(holes) |
| else: |
| holes_param = None |
| |
| # Getting the current pointer, replacing with the newly constructed |
| # geometry, and destroying the old geometry. |
| prev_ptr = self.ptr |
| srid = self.srid |
| self._ptr = create_polygon(shell, holes_param, c_uint(nholes)) |
| if srid: self.srid = srid |
| destroy_geom(prev_ptr) |
| |
| def __iter__(self): |
| "Iterates over each ring in the polygon." |
| for i in xrange(len(self)): |
| yield self[i] |
| |
| def __len__(self): |
| "Returns the number of rings in this Polygon." |
| return self.num_interior_rings + 1 |
| |
| def _checkindex(self, index): |
| "Internal routine for checking the given ring index." |
| if index < 0 or index >= len(self): |
| raise GEOSIndexError('invalid Polygon ring index: %s' % index) |
| |
| def _construct_ring(self, param, msg=''): |
| "Helper routine for trying to construct a ring from the given parameter." |
| if isinstance(param, LinearRing): return param |
| try: |
| ring = LinearRing(param) |
| return ring |
| except TypeError: |
| raise TypeError(msg) |
| |
| def get_interior_ring(self, ring_i): |
| """ |
| Gets the interior ring at the specified index, 0 is for the first |
| interior ring, not the exterior ring. |
| """ |
| self._checkindex(ring_i+1) |
| return GEOSGeometry(geom_clone(get_intring(self.ptr, ring_i)), srid=self.srid) |
| |
| #### Polygon Properties #### |
| @property |
| def num_interior_rings(self): |
| "Returns the number of interior rings." |
| # Getting the number of rings |
| return get_nrings(self.ptr) |
| |
| def get_ext_ring(self): |
| "Gets the exterior ring of the Polygon." |
| return GEOSGeometry(geom_clone(get_extring(self.ptr)), srid=self.srid) |
| |
| def set_ext_ring(self, ring): |
| "Sets the exterior ring of the Polygon." |
| self[0] = ring |
| |
| # properties for the exterior ring/shell |
| exterior_ring = property(get_ext_ring, set_ext_ring) |
| shell = exterior_ring |
| |
| @property |
| def tuple(self): |
| "Gets the tuple for each ring in this Polygon." |
| return tuple([self[i].tuple for i in xrange(len(self))]) |
| coords = tuple |
| |
| @property |
| def kml(self): |
| "Returns the KML representation of this Polygon." |
| inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml |
| for i in xrange(self.num_interior_rings)]) |
| return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml) |