django/contrib/gis/geos/polygon.py
author Justin Bronn <jbronn@geodjango.org>
Sat Jan 31 15:18:50 2009 -0600 (3 years ago)
branchtrunk
changeset 138 466bece04a15
parent 95 233dbb50fdf2
child 352 ed67864049e9
permissions -rw-r--r--
Merged in patch from Aryeh Leib Taurog for #9877, adapting as necessary.
     1 from ctypes import c_uint, byref
     2 from django.contrib.gis.geos.error import GEOSIndexError
     3 from django.contrib.gis.geos.geometry import GEOSGeometry
     4 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR
     5 from django.contrib.gis.geos.linestring import LinearRing
     6 from django.contrib.gis.geos import prototypes as capi
     7 
     8 class Polygon(GEOSGeometry):
     9 
    10     def __init__(self, *args, **kwargs):
    11         """
    12         Initializes on an exterior ring and a sequence of holes (both
    13         instances may be either LinearRing instances, or a tuple/list
    14         that may be constructed into a LinearRing).
    15         
    16         Examples of initialization, where shell, hole1, and hole2 are 
    17         valid LinearRing geometries:
    18         >>> poly = Polygon(shell, hole1, hole2)
    19         >>> poly = Polygon(shell, (hole1, hole2))
    20 
    21         Example where a tuple parameters are used:
    22         >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), 
    23                            ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4)))
    24         """
    25         if not args:
    26             raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.')
    27 
    28         # Getting the ext_ring and init_holes parameters from the argument list
    29         ext_ring = args[0]
    30         init_holes = args[1:]
    31         n_holes = len(init_holes)
    32 
    33         # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility]
    34         if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \
    35                 (len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)): 
    36             init_holes = init_holes[0]
    37             n_holes = len(init_holes)
    38 
    39         # Ensuring the exterior ring and holes parameters are LinearRing objects
    40         # or may be instantiated into LinearRings.
    41         ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.')
    42         holes_list = [] # Create new list, cause init_holes is a tuple.
    43         for i in xrange(n_holes):
    44             holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'))
    45 
    46         # Why another loop?  Because if a TypeError is raised, cloned pointers will
    47         # be around that can't be cleaned up.
    48         holes = get_pointer_arr(n_holes)
    49         for i in xrange(n_holes): holes[i] = capi.geom_clone(holes_list[i].ptr)
    50                       
    51         # Getting the shell pointer address.
    52         shell = capi.geom_clone(ext_ring.ptr)
    53 
    54         # Calling with the GEOS createPolygon factory.
    55         super(Polygon, self).__init__(capi.create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
    56 
    57     def __getitem__(self, index):
    58         """
    59         Returns the ring at the specified index.  The first index, 0, will 
    60         always return the exterior ring.  Indices > 0 will return the 
    61         interior ring at the given index (e.g., poly[1] and poly[2] would
    62         return the first and second interior ring, respectively).
    63         """
    64         if index == 0:
    65             return self.exterior_ring
    66         else:
    67             # Getting the interior ring, have to subtract 1 from the index.
    68             return self.get_interior_ring(index-1) 
    69 
    70     def __setitem__(self, index, ring):
    71         "Sets the ring at the specified index with the given ring."
    72         # Checking the index and ring parameters.
    73         self._checkindex(index)
    74         if not isinstance(ring, LinearRing):
    75             raise TypeError('must set Polygon index with a LinearRing object')
    76 
    77         # Getting the shell
    78         if index == 0:
    79             shell = capi.geom_clone(ring.ptr)
    80         else:
    81             shell = capi.geom_clone(capi.get_extring(self.ptr))
    82 
    83         # Getting the interior rings (holes)
    84         nholes = len(self)-1
    85         if nholes > 0:
    86             holes = get_pointer_arr(nholes)
    87             for i in xrange(nholes):
    88                 if i == (index-1):
    89                     holes[i] = capi.geom_clone(ring.ptr)
    90                 else:
    91                     holes[i] = capi.geom_clone(capi.get_intring(self.ptr, i))
    92             holes_param = byref(holes)
    93         else:
    94             holes_param = None
    95          
    96         # Getting the current pointer, replacing with the newly constructed
    97         # geometry, and destroying the old geometry.
    98         prev_ptr = self.ptr
    99         srid = self.srid
   100         self.ptr = capi.create_polygon(shell, holes_param, c_uint(nholes))
   101         if srid: self.srid = srid
   102         capi.destroy_geom(prev_ptr)
   103 
   104     def __iter__(self):
   105         "Iterates over each ring in the polygon."
   106         for i in xrange(len(self)):
   107             yield self[i]
   108 
   109     def __len__(self):
   110         "Returns the number of rings in this Polygon."
   111         return self.num_interior_rings + 1
   112 
   113     def _construct_ring(self, param, msg=''):
   114         "Helper routine for trying to construct a ring from the given parameter."
   115         if isinstance(param, LinearRing): return param
   116         try:
   117             ring = LinearRing(param)
   118             return ring
   119         except TypeError:
   120             raise TypeError(msg)
   121 
   122     @classmethod
   123     def from_bbox(cls, bbox):
   124         "Constructs a Polygon from a bounding box (4-tuple)."
   125         x0, y0, x1, y1 = bbox
   126         return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %  (
   127                 x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
   128 
   129     def get_interior_ring(self, ring_i):
   130         """
   131         Gets the interior ring at the specified index, 0 is for the first 
   132         interior ring, not the exterior ring.
   133         """
   134         self._checkindex(ring_i+1)
   135         return GEOSGeometry(capi.geom_clone(capi.get_intring(self.ptr, ring_i)), srid=self.srid)
   136                                                         
   137     #### Polygon Properties ####
   138     @property
   139     def num_interior_rings(self):
   140         "Returns the number of interior rings."
   141         # Getting the number of rings
   142         return capi.get_nrings(self.ptr)
   143 
   144     def get_ext_ring(self):
   145         "Gets the exterior ring of the Polygon."
   146         return GEOSGeometry(capi.geom_clone(capi.get_extring(self.ptr)), srid=self.srid)
   147 
   148     def set_ext_ring(self, ring):
   149         "Sets the exterior ring of the Polygon."
   150         self[0] = ring
   151 
   152     # properties for the exterior ring/shell
   153     exterior_ring = property(get_ext_ring, set_ext_ring)
   154     shell = exterior_ring
   155     
   156     @property
   157     def tuple(self):
   158         "Gets the tuple for each ring in this Polygon."
   159         return tuple([self[i].tuple for i in xrange(len(self))])
   160     coords = tuple
   161 
   162     @property
   163     def kml(self):
   164         "Returns the KML representation of this Polygon."
   165         inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml 
   166                              for i in xrange(self.num_interior_rings)])
   167         return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml)