|
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 _minlength = 1 |
|
10 |
|
11 def __init__(self, *args, **kwargs): |
|
12 """ |
|
13 Initializes on an exterior ring and a sequence of holes (both |
|
14 instances may be either LinearRing instances, or a tuple/list |
|
15 that may be constructed into a LinearRing). |
|
16 |
|
17 Examples of initialization, where shell, hole1, and hole2 are |
|
18 valid LinearRing geometries: |
|
19 >>> poly = Polygon(shell, hole1, hole2) |
|
20 >>> poly = Polygon(shell, (hole1, hole2)) |
|
21 |
|
22 Example where a tuple parameters are used: |
|
23 >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), |
|
24 ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4))) |
|
25 """ |
|
26 if not args: |
|
27 raise TypeError('Must provide at least one LinearRing, or a tuple, to initialize a Polygon.') |
|
28 |
|
29 # Getting the ext_ring and init_holes parameters from the argument list |
|
30 ext_ring = args[0] |
|
31 init_holes = args[1:] |
|
32 n_holes = len(init_holes) |
|
33 |
|
34 # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility] |
|
35 if n_holes == 1 and isinstance(init_holes[0], (tuple, list)): |
|
36 if len(init_holes[0]) == 0: |
|
37 init_holes = () |
|
38 n_holes = 0 |
|
39 elif isinstance(init_holes[0][0], LinearRing): |
|
40 init_holes = init_holes[0] |
|
41 n_holes = len(init_holes) |
|
42 |
|
43 polygon = self._create_polygon(n_holes + 1, (ext_ring,) + init_holes) |
|
44 super(Polygon, self).__init__(polygon, **kwargs) |
|
45 |
|
46 def __iter__(self): |
|
47 "Iterates over each ring in the polygon." |
|
48 for i in xrange(len(self)): |
|
49 yield self[i] |
|
50 |
|
51 def __len__(self): |
|
52 "Returns the number of rings in this Polygon." |
|
53 return self.num_interior_rings + 1 |
|
54 |
|
55 @classmethod |
|
56 def from_bbox(cls, bbox): |
|
57 "Constructs a Polygon from a bounding box (4-tuple)." |
|
58 x0, y0, x1, y1 = bbox |
|
59 return GEOSGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( |
|
60 x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) |
|
61 |
|
62 ### These routines are needed for list-like operation w/ListMixin ### |
|
63 def _create_polygon(self, length, items): |
|
64 # Instantiate LinearRing objects if necessary, but don't clone them yet |
|
65 # _construct_ring will throw a TypeError if a parameter isn't a valid ring |
|
66 # If we cloned the pointers here, we wouldn't be able to clean up |
|
67 # in case of error. |
|
68 rings = [] |
|
69 for r in items: |
|
70 if isinstance(r, GEOM_PTR): |
|
71 rings.append(r) |
|
72 else: |
|
73 rings.append(self._construct_ring(r)) |
|
74 |
|
75 shell = self._clone(rings.pop(0)) |
|
76 |
|
77 n_holes = length - 1 |
|
78 if n_holes: |
|
79 holes = get_pointer_arr(n_holes) |
|
80 for i, r in enumerate(rings): |
|
81 holes[i] = self._clone(r) |
|
82 holes_param = byref(holes) |
|
83 else: |
|
84 holes_param = None |
|
85 |
|
86 return capi.create_polygon(shell, holes_param, c_uint(n_holes)) |
|
87 |
|
88 def _clone(self, g): |
|
89 if isinstance(g, GEOM_PTR): |
|
90 return capi.geom_clone(g) |
|
91 else: |
|
92 return capi.geom_clone(g.ptr) |
|
93 |
|
94 def _construct_ring(self, param, msg='Parameter must be a sequence of LinearRings or objects that can initialize to LinearRings'): |
|
95 "Helper routine for trying to construct a ring from the given parameter." |
|
96 if isinstance(param, LinearRing): return param |
|
97 try: |
|
98 ring = LinearRing(param) |
|
99 return ring |
|
100 except TypeError: |
|
101 raise TypeError(msg) |
|
102 |
|
103 def _set_list(self, length, items): |
|
104 # Getting the current pointer, replacing with the newly constructed |
|
105 # geometry, and destroying the old geometry. |
|
106 prev_ptr = self.ptr |
|
107 srid = self.srid |
|
108 self.ptr = self._create_polygon(length, items) |
|
109 if srid: self.srid = srid |
|
110 capi.destroy_geom(prev_ptr) |
|
111 |
|
112 def _get_single_internal(self, index): |
|
113 """ |
|
114 Returns the ring at the specified index. The first index, 0, will |
|
115 always return the exterior ring. Indices > 0 will return the |
|
116 interior ring at the given index (e.g., poly[1] and poly[2] would |
|
117 return the first and second interior ring, respectively). |
|
118 |
|
119 CAREFUL: Internal/External are not the same as Interior/Exterior! |
|
120 _get_single_internal returns a pointer from the existing geometries for use |
|
121 internally by the object's methods. _get_single_external returns a clone |
|
122 of the same geometry for use by external code. |
|
123 """ |
|
124 if index == 0: |
|
125 return capi.get_extring(self.ptr) |
|
126 else: |
|
127 # Getting the interior ring, have to subtract 1 from the index. |
|
128 return capi.get_intring(self.ptr, index-1) |
|
129 |
|
130 def _get_single_external(self, index): |
|
131 return GEOSGeometry(capi.geom_clone(self._get_single_internal(index)), srid=self.srid) |
|
132 |
|
133 _set_single = GEOSGeometry._set_single_rebuild |
|
134 _assign_extended_slice = GEOSGeometry._assign_extended_slice_rebuild |
|
135 |
|
136 #### Polygon Properties #### |
|
137 @property |
|
138 def num_interior_rings(self): |
|
139 "Returns the number of interior rings." |
|
140 # Getting the number of rings |
|
141 return capi.get_nrings(self.ptr) |
|
142 |
|
143 def _get_ext_ring(self): |
|
144 "Gets the exterior ring of the Polygon." |
|
145 return self[0] |
|
146 |
|
147 def _set_ext_ring(self, ring): |
|
148 "Sets the exterior ring of the Polygon." |
|
149 self[0] = ring |
|
150 |
|
151 # Properties for the exterior ring/shell. |
|
152 exterior_ring = property(_get_ext_ring, _set_ext_ring) |
|
153 shell = exterior_ring |
|
154 |
|
155 @property |
|
156 def tuple(self): |
|
157 "Gets the tuple for each ring in this Polygon." |
|
158 return tuple([self[i].tuple for i in xrange(len(self))]) |
|
159 coords = tuple |
|
160 |
|
161 @property |
|
162 def kml(self): |
|
163 "Returns the KML representation of this Polygon." |
|
164 inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml |
|
165 for i in xrange(self.num_interior_rings)]) |
|
166 return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml) |