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
8 class Polygon(GEOSGeometry):
10 def __init__(self, *args, **kwargs):
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).
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))
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)))
26 raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.')
28 # Getting the ext_ring and init_holes parameters from the argument list
31 n_holes = len(init_holes)
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)
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'))
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)
51 # Getting the shell pointer address.
52 shell = capi.geom_clone(ext_ring.ptr)
54 # Calling with the GEOS createPolygon factory.
55 super(Polygon, self).__init__(capi.create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
57 def __getitem__(self, index):
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).
65 return self.exterior_ring
67 # Getting the interior ring, have to subtract 1 from the index.
68 return self.get_interior_ring(index-1)
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')
79 shell = capi.geom_clone(ring.ptr)
81 shell = capi.geom_clone(capi.get_extring(self.ptr))
83 # Getting the interior rings (holes)
86 holes = get_pointer_arr(nholes)
87 for i in xrange(nholes):
89 holes[i] = capi.geom_clone(ring.ptr)
91 holes[i] = capi.geom_clone(capi.get_intring(self.ptr, i))
92 holes_param = byref(holes)
96 # Getting the current pointer, replacing with the newly constructed
97 # geometry, and destroying the old geometry.
100 self.ptr = capi.create_polygon(shell, holes_param, c_uint(nholes))
101 if srid: self.srid = srid
102 capi.destroy_geom(prev_ptr)
105 "Iterates over each ring in the polygon."
106 for i in xrange(len(self)):
110 "Returns the number of rings in this Polygon."
111 return self.num_interior_rings + 1
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
117 ring = LinearRing(param)
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) )
129 def get_interior_ring(self, ring_i):
131 Gets the interior ring at the specified index, 0 is for the first
132 interior ring, not the exterior ring.
134 self._checkindex(ring_i+1)
135 return GEOSGeometry(capi.geom_clone(capi.get_intring(self.ptr, ring_i)), srid=self.srid)
137 #### Polygon Properties ####
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)
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)
148 def set_ext_ring(self, ring):
149 "Sets the exterior ring of the Polygon."
152 # properties for the exterior ring/shell
153 exterior_ring = property(get_ext_ring, set_ext_ring)
154 shell = exterior_ring
158 "Gets the tuple for each ring in this Polygon."
159 return tuple([self[i].tuple for i in xrange(len(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)