app/django/contrib/gis/geos/geometries.py
changeset 323 ff1a9aa48cfd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/django/contrib/gis/geos/geometries.py	Tue Oct 14 16:00:59 2008 +0000
@@ -0,0 +1,391 @@
+"""
+ 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)