app/django/contrib/gis/geos/geometries.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     1 """
       
     2  This module houses the Point, LineString, LinearRing, and Polygon OGC
       
     3  geometry classes.  All geometry classes in this module inherit from 
       
     4  GEOSGeometry.
       
     5 """
       
     6 from ctypes import c_uint, byref
       
     7 from django.contrib.gis.geos.base import GEOSGeometry
       
     8 from django.contrib.gis.geos.coordseq import GEOSCoordSeq
       
     9 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
       
    10 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY
       
    11 from django.contrib.gis.geos.prototypes import *
       
    12 if HAS_NUMPY: from numpy import ndarray, array
       
    13 
       
    14 class Point(GEOSGeometry):
       
    15 
       
    16     def __init__(self, x, y=None, z=None, srid=None):
       
    17         """
       
    18         The Point object may be initialized with either a tuple, or individual
       
    19         parameters.
       
    20         
       
    21         For Example:
       
    22         >>> p = Point((5, 23)) # 2D point, passed in as a tuple
       
    23         >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters
       
    24         """
       
    25 
       
    26         if isinstance(x, (tuple, list)):
       
    27             # Here a tuple or list was passed in under the `x` parameter.
       
    28             ndim = len(x)
       
    29             if ndim < 2 or ndim > 3:
       
    30                 raise TypeError('Invalid sequence parameter: %s' % str(x))
       
    31             coords = x
       
    32         elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)):
       
    33             # Here X, Y, and (optionally) Z were passed in individually, as parameters.
       
    34             if isinstance(z, (int, float, long)):
       
    35                 ndim = 3
       
    36                 coords = [x, y, z]
       
    37             else:
       
    38                 ndim = 2
       
    39                 coords = [x, y]
       
    40         else:
       
    41             raise TypeError('Invalid parameters given for Point initialization.')
       
    42 
       
    43         # Creating the coordinate sequence, and setting X, Y, [Z]
       
    44         cs = create_cs(c_uint(1), c_uint(ndim))
       
    45         cs_setx(cs, 0, coords[0])
       
    46         cs_sety(cs, 0, coords[1])
       
    47         if ndim == 3: cs_setz(cs, 0, coords[2])
       
    48 
       
    49         # Initializing using the address returned from the GEOS 
       
    50         #  createPoint factory.
       
    51         super(Point, self).__init__(create_point(cs), srid=srid)
       
    52 
       
    53     def __len__(self):
       
    54         "Returns the number of dimensions for this Point (either 0, 2 or 3)."
       
    55         if self.empty: return 0
       
    56         if self.hasz: return 3
       
    57         else: return 2
       
    58         
       
    59     def get_x(self):
       
    60         "Returns the X component of the Point."
       
    61         return self._cs.getOrdinate(0, 0)
       
    62 
       
    63     def set_x(self, value):
       
    64         "Sets the X component of the Point."
       
    65         self._cs.setOrdinate(0, 0, value)
       
    66 
       
    67     def get_y(self):
       
    68         "Returns the Y component of the Point."
       
    69         return self._cs.getOrdinate(1, 0)
       
    70 
       
    71     def set_y(self, value):
       
    72         "Sets the Y component of the Point."
       
    73         self._cs.setOrdinate(1, 0, value)
       
    74 
       
    75     def get_z(self):
       
    76         "Returns the Z component of the Point."
       
    77         if self.hasz:
       
    78             return self._cs.getOrdinate(2, 0)
       
    79         else:
       
    80             return None
       
    81 
       
    82     def set_z(self, value):
       
    83         "Sets the Z component of the Point."
       
    84         if self.hasz:
       
    85             self._cs.setOrdinate(2, 0, value)
       
    86         else:
       
    87             raise GEOSException('Cannot set Z on 2D Point.')
       
    88 
       
    89     # X, Y, Z properties
       
    90     x = property(get_x, set_x)
       
    91     y = property(get_y, set_y)
       
    92     z = property(get_z, set_z)
       
    93 
       
    94     ### Tuple setting and retrieval routines. ###
       
    95     def get_coords(self):
       
    96         "Returns a tuple of the point."
       
    97         return self._cs.tuple
       
    98 
       
    99     def set_coords(self, tup):
       
   100         "Sets the coordinates of the point with the given tuple."
       
   101         self._cs[0] = tup
       
   102     
       
   103     # The tuple and coords properties
       
   104     tuple = property(get_coords, set_coords)
       
   105     coords = tuple
       
   106 
       
   107 class LineString(GEOSGeometry):
       
   108 
       
   109     #### Python 'magic' routines ####
       
   110     def __init__(self, *args, **kwargs):
       
   111         """
       
   112         Initializes on the given sequence -- may take lists, tuples, NumPy arrays
       
   113         of X,Y pairs, or Point objects.  If Point objects are used, ownership is
       
   114         _not_ transferred to the LineString object.
       
   115 
       
   116         Examples:
       
   117          ls = LineString((1, 1), (2, 2))
       
   118          ls = LineString([(1, 1), (2, 2)])
       
   119          ls = LineString(array([(1, 1), (2, 2)]))
       
   120          ls = LineString(Point(1, 1), Point(2, 2))
       
   121         """
       
   122         # If only one argument provided, set the coords array appropriately
       
   123         if len(args) == 1: coords = args[0]
       
   124         else: coords = args
       
   125 
       
   126         if isinstance(coords, (tuple, list)):
       
   127             # Getting the number of coords and the number of dimensions -- which
       
   128             #  must stay the same, e.g., no LineString((1, 2), (1, 2, 3)).
       
   129             ncoords = len(coords)
       
   130             if coords: ndim = len(coords[0])
       
   131             else: raise TypeError('Cannot initialize on empty sequence.')
       
   132             self._checkdim(ndim)
       
   133             # Incrementing through each of the coordinates and verifying
       
   134             for i in xrange(1, ncoords):
       
   135                 if not isinstance(coords[i], (tuple, list, Point)):
       
   136                     raise TypeError('each coordinate should be a sequence (list or tuple)')
       
   137                 if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.')
       
   138             numpy_coords = False
       
   139         elif HAS_NUMPY and isinstance(coords, ndarray):
       
   140             shape = coords.shape # Using numpy's shape.
       
   141             if len(shape) != 2: raise TypeError('Too many dimensions.')
       
   142             self._checkdim(shape[1])
       
   143             ncoords = shape[0]
       
   144             ndim = shape[1]
       
   145             numpy_coords = True
       
   146         else:
       
   147             raise TypeError('Invalid initialization input for LineStrings.')
       
   148 
       
   149         # Creating a coordinate sequence object because it is easier to 
       
   150         #  set the points using GEOSCoordSeq.__setitem__().
       
   151         cs = GEOSCoordSeq(create_cs(ncoords, ndim), z=bool(ndim==3))
       
   152         for i in xrange(ncoords):
       
   153             if numpy_coords: cs[i] = coords[i,:]
       
   154             elif isinstance(coords[i], Point): cs[i] = coords[i].tuple
       
   155             else: cs[i] = coords[i]        
       
   156 
       
   157         # Getting the correct initialization function
       
   158         if kwargs.get('ring', False):
       
   159             func = create_linearring
       
   160         else:
       
   161             func = create_linestring
       
   162 
       
   163         # If SRID was passed in with the keyword arguments
       
   164         srid = kwargs.get('srid', None)
       
   165        
       
   166         # Calling the base geometry initialization with the returned pointer 
       
   167         #  from the function.
       
   168         super(LineString, self).__init__(func(cs.ptr), srid=srid)
       
   169 
       
   170     def __getitem__(self, index):
       
   171         "Gets the point at the specified index."
       
   172         return self._cs[index]
       
   173 
       
   174     def __setitem__(self, index, value):
       
   175         "Sets the point at the specified index, e.g., line_str[0] = (1, 2)."
       
   176         self._cs[index] = value
       
   177 
       
   178     def __iter__(self):
       
   179         "Allows iteration over this LineString."
       
   180         for i in xrange(len(self)):
       
   181             yield self[i]
       
   182 
       
   183     def __len__(self):
       
   184         "Returns the number of points in this LineString."
       
   185         return len(self._cs)
       
   186 
       
   187     def _checkdim(self, dim):
       
   188         if dim not in (2, 3): raise TypeError('Dimension mismatch.')
       
   189 
       
   190     #### Sequence Properties ####
       
   191     @property
       
   192     def tuple(self):
       
   193         "Returns a tuple version of the geometry from the coordinate sequence."
       
   194         return self._cs.tuple
       
   195     coords = tuple
       
   196 
       
   197     def _listarr(self, func):
       
   198         """
       
   199         Internal routine that returns a sequence (list) corresponding with
       
   200         the given function.  Will return a numpy array if possible.
       
   201         """
       
   202         lst = [func(i) for i in xrange(len(self))]
       
   203         if HAS_NUMPY: return array(lst) # ARRRR!
       
   204         else: return lst
       
   205 
       
   206     @property
       
   207     def array(self):
       
   208         "Returns a numpy array for the LineString."
       
   209         return self._listarr(self._cs.__getitem__)
       
   210 
       
   211     @property
       
   212     def x(self):
       
   213         "Returns a list or numpy array of the X variable."
       
   214         return self._listarr(self._cs.getX)
       
   215     
       
   216     @property
       
   217     def y(self):
       
   218         "Returns a list or numpy array of the Y variable."
       
   219         return self._listarr(self._cs.getY)
       
   220 
       
   221     @property
       
   222     def z(self):
       
   223         "Returns a list or numpy array of the Z variable."
       
   224         if not self.hasz: return None
       
   225         else: return self._listarr(self._cs.getZ)
       
   226 
       
   227 # LinearRings are LineStrings used within Polygons.
       
   228 class LinearRing(LineString):
       
   229     def __init__(self, *args, **kwargs):
       
   230         "Overriding the initialization function to set the ring keyword."
       
   231         kwargs['ring'] = True # Setting the ring keyword argument to True
       
   232         super(LinearRing, self).__init__(*args, **kwargs)
       
   233 
       
   234 class Polygon(GEOSGeometry):
       
   235 
       
   236     def __init__(self, *args, **kwargs):
       
   237         """
       
   238         Initializes on an exterior ring and a sequence of holes (both
       
   239         instances may be either LinearRing instances, or a tuple/list
       
   240         that may be constructed into a LinearRing).
       
   241         
       
   242         Examples of initialization, where shell, hole1, and hole2 are 
       
   243         valid LinearRing geometries:
       
   244         >>> poly = Polygon(shell, hole1, hole2)
       
   245         >>> poly = Polygon(shell, (hole1, hole2))
       
   246 
       
   247         Example where a tuple parameters are used:
       
   248         >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), 
       
   249                            ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
       
   250         """
       
   251         if not args:
       
   252             raise TypeError('Must provide at list one LinearRing instance to initialize Polygon.')
       
   253 
       
   254         # Getting the ext_ring and init_holes parameters from the argument list
       
   255         ext_ring = args[0]
       
   256         init_holes = args[1:]
       
   257         n_holes = len(init_holes)
       
   258 
       
   259         # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
       
   260         if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \
       
   261                 (len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)): 
       
   262             init_holes = init_holes[0]
       
   263             n_holes = len(init_holes)
       
   264 
       
   265         # Ensuring the exterior ring and holes parameters are LinearRing objects
       
   266         # or may be instantiated into LinearRings.
       
   267         ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.')
       
   268         holes_list = [] # Create new list, cause init_holes is a tuple.
       
   269         for i in xrange(n_holes):
       
   270             holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'))
       
   271 
       
   272         # Why another loop?  Because if a TypeError is raised, cloned pointers will
       
   273         # be around that can't be cleaned up.
       
   274         holes = get_pointer_arr(n_holes)
       
   275         for i in xrange(n_holes): holes[i] = geom_clone(holes_list[i].ptr)
       
   276                       
       
   277         # Getting the shell pointer address.
       
   278         shell = geom_clone(ext_ring.ptr)
       
   279 
       
   280         # Calling with the GEOS createPolygon factory.
       
   281         super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
       
   282 
       
   283     def __getitem__(self, index):
       
   284         """
       
   285         Returns the ring at the specified index.  The first index, 0, will 
       
   286         always return the exterior ring.  Indices > 0 will return the 
       
   287         interior ring at the given index (e.g., poly[1] and poly[2] would
       
   288         return the first and second interior ring, respectively).
       
   289         """
       
   290         if index == 0:
       
   291             return self.exterior_ring
       
   292         else:
       
   293             # Getting the interior ring, have to subtract 1 from the index.
       
   294             return self.get_interior_ring(index-1) 
       
   295 
       
   296     def __setitem__(self, index, ring):
       
   297         "Sets the ring at the specified index with the given ring."
       
   298         # Checking the index and ring parameters.
       
   299         self._checkindex(index)
       
   300         if not isinstance(ring, LinearRing):
       
   301             raise TypeError('must set Polygon index with a LinearRing object')
       
   302 
       
   303         # Getting the shell
       
   304         if index == 0:
       
   305             shell = geom_clone(ring.ptr)
       
   306         else:
       
   307             shell = geom_clone(get_extring(self.ptr))
       
   308 
       
   309         # Getting the interior rings (holes)
       
   310         nholes = len(self)-1
       
   311         if nholes > 0:
       
   312             holes = get_pointer_arr(nholes)
       
   313             for i in xrange(nholes):
       
   314                 if i == (index-1):
       
   315                     holes[i] = geom_clone(ring.ptr)
       
   316                 else:
       
   317                     holes[i] = geom_clone(get_intring(self.ptr, i))
       
   318             holes_param = byref(holes)
       
   319         else:
       
   320             holes_param = None
       
   321          
       
   322         # Getting the current pointer, replacing with the newly constructed
       
   323         # geometry, and destroying the old geometry.
       
   324         prev_ptr = self.ptr
       
   325         srid = self.srid
       
   326         self._ptr = create_polygon(shell, holes_param, c_uint(nholes))
       
   327         if srid: self.srid = srid
       
   328         destroy_geom(prev_ptr)
       
   329 
       
   330     def __iter__(self):
       
   331         "Iterates over each ring in the polygon."
       
   332         for i in xrange(len(self)):
       
   333             yield self[i]
       
   334 
       
   335     def __len__(self):
       
   336         "Returns the number of rings in this Polygon."
       
   337         return self.num_interior_rings + 1
       
   338 
       
   339     def _checkindex(self, index):
       
   340         "Internal routine for checking the given ring index."
       
   341         if index < 0 or index >= len(self):
       
   342             raise GEOSIndexError('invalid Polygon ring index: %s' % index)
       
   343 
       
   344     def _construct_ring(self, param, msg=''):
       
   345         "Helper routine for trying to construct a ring from the given parameter."
       
   346         if isinstance(param, LinearRing): return param
       
   347         try:
       
   348             ring = LinearRing(param)
       
   349             return ring
       
   350         except TypeError:
       
   351             raise TypeError(msg)
       
   352 
       
   353     def get_interior_ring(self, ring_i):
       
   354         """
       
   355         Gets the interior ring at the specified index, 0 is for the first 
       
   356         interior ring, not the exterior ring.
       
   357         """
       
   358         self._checkindex(ring_i+1)
       
   359         return GEOSGeometry(geom_clone(get_intring(self.ptr, ring_i)), srid=self.srid)
       
   360                                                         
       
   361     #### Polygon Properties ####
       
   362     @property
       
   363     def num_interior_rings(self):
       
   364         "Returns the number of interior rings."
       
   365         # Getting the number of rings
       
   366         return get_nrings(self.ptr)
       
   367 
       
   368     def get_ext_ring(self):
       
   369         "Gets the exterior ring of the Polygon."
       
   370         return GEOSGeometry(geom_clone(get_extring(self.ptr)), srid=self.srid)
       
   371 
       
   372     def set_ext_ring(self, ring):
       
   373         "Sets the exterior ring of the Polygon."
       
   374         self[0] = ring
       
   375 
       
   376     # properties for the exterior ring/shell
       
   377     exterior_ring = property(get_ext_ring, set_ext_ring)
       
   378     shell = exterior_ring
       
   379     
       
   380     @property
       
   381     def tuple(self):
       
   382         "Gets the tuple for each ring in this Polygon."
       
   383         return tuple([self[i].tuple for i in xrange(len(self))])
       
   384     coords = tuple
       
   385 
       
   386     @property
       
   387     def kml(self):
       
   388         "Returns the KML representation of this Polygon."
       
   389         inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml 
       
   390                              for i in xrange(self.num_interior_rings)])
       
   391         return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)