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