|
0
|
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) |