|
1 """ |
|
2 This module houses the Point, LineString, LinearRing, and Polygon OGC |
|
3 geometry classes. All geometry classes in this module inherit from |
|
4 GEOSGeometry. |
|
5 """ |
|
6 from ctypes import c_uint, byref |
|
7 from django.contrib.gis.geos.base import GEOSGeometry |
|
8 from django.contrib.gis.geos.coordseq import GEOSCoordSeq |
|
9 from django.contrib.gis.geos.error import GEOSException, GEOSIndexError |
|
10 from django.contrib.gis.geos.libgeos import get_pointer_arr, GEOM_PTR, HAS_NUMPY |
|
11 from django.contrib.gis.geos.prototypes import * |
|
12 if HAS_NUMPY: from numpy import ndarray, array |
|
13 |
|
14 class Point(GEOSGeometry): |
|
15 |
|
16 def __init__(self, x, y=None, z=None, srid=None): |
|
17 """ |
|
18 The Point object may be initialized with either a tuple, or individual |
|
19 parameters. |
|
20 |
|
21 For Example: |
|
22 >>> p = Point((5, 23)) # 2D point, passed in as a tuple |
|
23 >>> p = Point(5, 23, 8) # 3D point, passed in with individual parameters |
|
24 """ |
|
25 |
|
26 if isinstance(x, (tuple, list)): |
|
27 # Here a tuple or list was passed in under the `x` parameter. |
|
28 ndim = len(x) |
|
29 if ndim < 2 or ndim > 3: |
|
30 raise TypeError('Invalid sequence parameter: %s' % str(x)) |
|
31 coords = x |
|
32 elif isinstance(x, (int, float, long)) and isinstance(y, (int, float, long)): |
|
33 # Here X, Y, and (optionally) Z were passed in individually, as parameters. |
|
34 if isinstance(z, (int, float, long)): |
|
35 ndim = 3 |
|
36 coords = [x, y, z] |
|
37 else: |
|
38 ndim = 2 |
|
39 coords = [x, y] |
|
40 else: |
|
41 raise TypeError('Invalid parameters given for Point initialization.') |
|
42 |
|
43 # Creating the coordinate sequence, and setting X, Y, [Z] |
|
44 cs = create_cs(c_uint(1), c_uint(ndim)) |
|
45 cs_setx(cs, 0, coords[0]) |
|
46 cs_sety(cs, 0, coords[1]) |
|
47 if ndim == 3: cs_setz(cs, 0, coords[2]) |
|
48 |
|
49 # Initializing using the address returned from the GEOS |
|
50 # createPoint factory. |
|
51 super(Point, self).__init__(create_point(cs), srid=srid) |
|
52 |
|
53 def __len__(self): |
|
54 "Returns the number of dimensions for this Point (either 0, 2 or 3)." |
|
55 if self.empty: return 0 |
|
56 if self.hasz: return 3 |
|
57 else: return 2 |
|
58 |
|
59 def get_x(self): |
|
60 "Returns the X component of the Point." |
|
61 return self._cs.getOrdinate(0, 0) |
|
62 |
|
63 def set_x(self, value): |
|
64 "Sets the X component of the Point." |
|
65 self._cs.setOrdinate(0, 0, value) |
|
66 |
|
67 def get_y(self): |
|
68 "Returns the Y component of the Point." |
|
69 return self._cs.getOrdinate(1, 0) |
|
70 |
|
71 def set_y(self, value): |
|
72 "Sets the Y component of the Point." |
|
73 self._cs.setOrdinate(1, 0, value) |
|
74 |
|
75 def get_z(self): |
|
76 "Returns the Z component of the Point." |
|
77 if self.hasz: |
|
78 return self._cs.getOrdinate(2, 0) |
|
79 else: |
|
80 return None |
|
81 |
|
82 def set_z(self, value): |
|
83 "Sets the Z component of the Point." |
|
84 if self.hasz: |
|
85 self._cs.setOrdinate(2, 0, value) |
|
86 else: |
|
87 raise GEOSException('Cannot set Z on 2D Point.') |
|
88 |
|
89 # X, Y, Z properties |
|
90 x = property(get_x, set_x) |
|
91 y = property(get_y, set_y) |
|
92 z = property(get_z, set_z) |
|
93 |
|
94 ### Tuple setting and retrieval routines. ### |
|
95 def get_coords(self): |
|
96 "Returns a tuple of the point." |
|
97 return self._cs.tuple |
|
98 |
|
99 def set_coords(self, tup): |
|
100 "Sets the coordinates of the point with the given tuple." |
|
101 self._cs[0] = tup |
|
102 |
|
103 # The tuple and coords properties |
|
104 tuple = property(get_coords, set_coords) |
|
105 coords = tuple |
|
106 |
|
107 class LineString(GEOSGeometry): |
|
108 |
|
109 #### Python 'magic' routines #### |
|
110 def __init__(self, *args, **kwargs): |
|
111 """ |
|
112 Initializes on the given sequence -- may take lists, tuples, NumPy arrays |
|
113 of X,Y pairs, or Point objects. If Point objects are used, ownership is |
|
114 _not_ transferred to the LineString object. |
|
115 |
|
116 Examples: |
|
117 ls = LineString((1, 1), (2, 2)) |
|
118 ls = LineString([(1, 1), (2, 2)]) |
|
119 ls = LineString(array([(1, 1), (2, 2)])) |
|
120 ls = LineString(Point(1, 1), Point(2, 2)) |
|
121 """ |
|
122 # If only one argument provided, set the coords array appropriately |
|
123 if len(args) == 1: coords = args[0] |
|
124 else: coords = args |
|
125 |
|
126 if isinstance(coords, (tuple, list)): |
|
127 # Getting the number of coords and the number of dimensions -- which |
|
128 # must stay the same, e.g., no LineString((1, 2), (1, 2, 3)). |
|
129 ncoords = len(coords) |
|
130 if coords: ndim = len(coords[0]) |
|
131 else: raise TypeError('Cannot initialize on empty sequence.') |
|
132 self._checkdim(ndim) |
|
133 # Incrementing through each of the coordinates and verifying |
|
134 for i in xrange(1, ncoords): |
|
135 if not isinstance(coords[i], (tuple, list, Point)): |
|
136 raise TypeError('each coordinate should be a sequence (list or tuple)') |
|
137 if len(coords[i]) != ndim: raise TypeError('Dimension mismatch.') |
|
138 numpy_coords = False |
|
139 elif HAS_NUMPY and isinstance(coords, ndarray): |
|
140 shape = coords.shape # Using numpy's shape. |
|
141 if len(shape) != 2: raise TypeError('Too many dimensions.') |
|
142 self._checkdim(shape[1]) |
|
143 ncoords = shape[0] |
|
144 ndim = shape[1] |
|
145 numpy_coords = True |
|
146 else: |
|
147 raise TypeError('Invalid initialization input for LineStrings.') |
|
148 |
|
149 # Creating a coordinate sequence object because it is easier to |
|
150 # set the points using GEOSCoordSeq.__setitem__(). |
|
151 cs = GEOSCoordSeq(create_cs(ncoords, ndim), z=bool(ndim==3)) |
|
152 for i in xrange(ncoords): |
|
153 if numpy_coords: cs[i] = coords[i,:] |
|
154 elif isinstance(coords[i], Point): cs[i] = coords[i].tuple |
|
155 else: cs[i] = coords[i] |
|
156 |
|
157 # Getting the correct initialization function |
|
158 if kwargs.get('ring', False): |
|
159 func = create_linearring |
|
160 else: |
|
161 func = create_linestring |
|
162 |
|
163 # If SRID was passed in with the keyword arguments |
|
164 srid = kwargs.get('srid', None) |
|
165 |
|
166 # Calling the base geometry initialization with the returned pointer |
|
167 # from the function. |
|
168 super(LineString, self).__init__(func(cs.ptr), srid=srid) |
|
169 |
|
170 def __getitem__(self, index): |
|
171 "Gets the point at the specified index." |
|
172 return self._cs[index] |
|
173 |
|
174 def __setitem__(self, index, value): |
|
175 "Sets the point at the specified index, e.g., line_str[0] = (1, 2)." |
|
176 self._cs[index] = value |
|
177 |
|
178 def __iter__(self): |
|
179 "Allows iteration over this LineString." |
|
180 for i in xrange(len(self)): |
|
181 yield self[i] |
|
182 |
|
183 def __len__(self): |
|
184 "Returns the number of points in this LineString." |
|
185 return len(self._cs) |
|
186 |
|
187 def _checkdim(self, dim): |
|
188 if dim not in (2, 3): raise TypeError('Dimension mismatch.') |
|
189 |
|
190 #### Sequence Properties #### |
|
191 @property |
|
192 def tuple(self): |
|
193 "Returns a tuple version of the geometry from the coordinate sequence." |
|
194 return self._cs.tuple |
|
195 coords = tuple |
|
196 |
|
197 def _listarr(self, func): |
|
198 """ |
|
199 Internal routine that returns a sequence (list) corresponding with |
|
200 the given function. Will return a numpy array if possible. |
|
201 """ |
|
202 lst = [func(i) for i in xrange(len(self))] |
|
203 if HAS_NUMPY: return array(lst) # ARRRR! |
|
204 else: return lst |
|
205 |
|
206 @property |
|
207 def array(self): |
|
208 "Returns a numpy array for the LineString." |
|
209 return self._listarr(self._cs.__getitem__) |
|
210 |
|
211 @property |
|
212 def x(self): |
|
213 "Returns a list or numpy array of the X variable." |
|
214 return self._listarr(self._cs.getX) |
|
215 |
|
216 @property |
|
217 def y(self): |
|
218 "Returns a list or numpy array of the Y variable." |
|
219 return self._listarr(self._cs.getY) |
|
220 |
|
221 @property |
|
222 def z(self): |
|
223 "Returns a list or numpy array of the Z variable." |
|
224 if not self.hasz: return None |
|
225 else: return self._listarr(self._cs.getZ) |
|
226 |
|
227 # LinearRings are LineStrings used within Polygons. |
|
228 class LinearRing(LineString): |
|
229 def __init__(self, *args, **kwargs): |
|
230 "Overriding the initialization function to set the ring keyword." |
|
231 kwargs['ring'] = True # Setting the ring keyword argument to True |
|
232 super(LinearRing, self).__init__(*args, **kwargs) |
|
233 |
|
234 class Polygon(GEOSGeometry): |
|
235 |
|
236 def __init__(self, *args, **kwargs): |
|
237 """ |
|
238 Initializes on an exterior ring and a sequence of holes (both |
|
239 instances may be either LinearRing instances, or a tuple/list |
|
240 that may be constructed into a LinearRing). |
|
241 |
|
242 Examples of initialization, where shell, hole1, and hole2 are |
|
243 valid LinearRing geometries: |
|
244 >>> poly = Polygon(shell, hole1, hole2) |
|
245 >>> poly = Polygon(shell, (hole1, hole2)) |
|
246 |
|
247 Example where a tuple parameters are used: |
|
248 >>> poly = Polygon(((0, 0), (0, 10), (10, 10), (0, 10), (0, 0)), |
|
249 ((4, 4), (4, 6), (6, 6), (6, 4), (4, 4))) |
|
250 """ |
|
251 if not args: |
|
252 raise TypeError('Must provide at list one LinearRing instance to initialize Polygon.') |
|
253 |
|
254 # Getting the ext_ring and init_holes parameters from the argument list |
|
255 ext_ring = args[0] |
|
256 init_holes = args[1:] |
|
257 n_holes = len(init_holes) |
|
258 |
|
259 # If initialized as Polygon(shell, (LinearRing, LinearRing)) [for backward-compatibility] |
|
260 if n_holes == 1 and isinstance(init_holes[0], (tuple, list)) and \ |
|
261 (len(init_holes[0]) == 0 or isinstance(init_holes[0][0], LinearRing)): |
|
262 init_holes = init_holes[0] |
|
263 n_holes = len(init_holes) |
|
264 |
|
265 # Ensuring the exterior ring and holes parameters are LinearRing objects |
|
266 # or may be instantiated into LinearRings. |
|
267 ext_ring = self._construct_ring(ext_ring, 'Exterior parameter must be a LinearRing or an object that can initialize a LinearRing.') |
|
268 holes_list = [] # Create new list, cause init_holes is a tuple. |
|
269 for i in xrange(n_holes): |
|
270 holes_list.append(self._construct_ring(init_holes[i], 'Holes parameter must be a sequence of LinearRings or objects that can initialize to LinearRings')) |
|
271 |
|
272 # Why another loop? Because if a TypeError is raised, cloned pointers will |
|
273 # be around that can't be cleaned up. |
|
274 holes = get_pointer_arr(n_holes) |
|
275 for i in xrange(n_holes): holes[i] = geom_clone(holes_list[i].ptr) |
|
276 |
|
277 # Getting the shell pointer address. |
|
278 shell = geom_clone(ext_ring.ptr) |
|
279 |
|
280 # Calling with the GEOS createPolygon factory. |
|
281 super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs) |
|
282 |
|
283 def __getitem__(self, index): |
|
284 """ |
|
285 Returns the ring at the specified index. The first index, 0, will |
|
286 always return the exterior ring. Indices > 0 will return the |
|
287 interior ring at the given index (e.g., poly[1] and poly[2] would |
|
288 return the first and second interior ring, respectively). |
|
289 """ |
|
290 if index == 0: |
|
291 return self.exterior_ring |
|
292 else: |
|
293 # Getting the interior ring, have to subtract 1 from the index. |
|
294 return self.get_interior_ring(index-1) |
|
295 |
|
296 def __setitem__(self, index, ring): |
|
297 "Sets the ring at the specified index with the given ring." |
|
298 # Checking the index and ring parameters. |
|
299 self._checkindex(index) |
|
300 if not isinstance(ring, LinearRing): |
|
301 raise TypeError('must set Polygon index with a LinearRing object') |
|
302 |
|
303 # Getting the shell |
|
304 if index == 0: |
|
305 shell = geom_clone(ring.ptr) |
|
306 else: |
|
307 shell = geom_clone(get_extring(self.ptr)) |
|
308 |
|
309 # Getting the interior rings (holes) |
|
310 nholes = len(self)-1 |
|
311 if nholes > 0: |
|
312 holes = get_pointer_arr(nholes) |
|
313 for i in xrange(nholes): |
|
314 if i == (index-1): |
|
315 holes[i] = geom_clone(ring.ptr) |
|
316 else: |
|
317 holes[i] = geom_clone(get_intring(self.ptr, i)) |
|
318 holes_param = byref(holes) |
|
319 else: |
|
320 holes_param = None |
|
321 |
|
322 # Getting the current pointer, replacing with the newly constructed |
|
323 # geometry, and destroying the old geometry. |
|
324 prev_ptr = self.ptr |
|
325 srid = self.srid |
|
326 self._ptr = create_polygon(shell, holes_param, c_uint(nholes)) |
|
327 if srid: self.srid = srid |
|
328 destroy_geom(prev_ptr) |
|
329 |
|
330 def __iter__(self): |
|
331 "Iterates over each ring in the polygon." |
|
332 for i in xrange(len(self)): |
|
333 yield self[i] |
|
334 |
|
335 def __len__(self): |
|
336 "Returns the number of rings in this Polygon." |
|
337 return self.num_interior_rings + 1 |
|
338 |
|
339 def _checkindex(self, index): |
|
340 "Internal routine for checking the given ring index." |
|
341 if index < 0 or index >= len(self): |
|
342 raise GEOSIndexError('invalid Polygon ring index: %s' % index) |
|
343 |
|
344 def _construct_ring(self, param, msg=''): |
|
345 "Helper routine for trying to construct a ring from the given parameter." |
|
346 if isinstance(param, LinearRing): return param |
|
347 try: |
|
348 ring = LinearRing(param) |
|
349 return ring |
|
350 except TypeError: |
|
351 raise TypeError(msg) |
|
352 |
|
353 def get_interior_ring(self, ring_i): |
|
354 """ |
|
355 Gets the interior ring at the specified index, 0 is for the first |
|
356 interior ring, not the exterior ring. |
|
357 """ |
|
358 self._checkindex(ring_i+1) |
|
359 return GEOSGeometry(geom_clone(get_intring(self.ptr, ring_i)), srid=self.srid) |
|
360 |
|
361 #### Polygon Properties #### |
|
362 @property |
|
363 def num_interior_rings(self): |
|
364 "Returns the number of interior rings." |
|
365 # Getting the number of rings |
|
366 return get_nrings(self.ptr) |
|
367 |
|
368 def get_ext_ring(self): |
|
369 "Gets the exterior ring of the Polygon." |
|
370 return GEOSGeometry(geom_clone(get_extring(self.ptr)), srid=self.srid) |
|
371 |
|
372 def set_ext_ring(self, ring): |
|
373 "Sets the exterior ring of the Polygon." |
|
374 self[0] = ring |
|
375 |
|
376 # properties for the exterior ring/shell |
|
377 exterior_ring = property(get_ext_ring, set_ext_ring) |
|
378 shell = exterior_ring |
|
379 |
|
380 @property |
|
381 def tuple(self): |
|
382 "Gets the tuple for each ring in this Polygon." |
|
383 return tuple([self[i].tuple for i in xrange(len(self))]) |
|
384 coords = tuple |
|
385 |
|
386 @property |
|
387 def kml(self): |
|
388 "Returns the KML representation of this Polygon." |
|
389 inner_kml = ''.join(["<innerBoundaryIs>%s</innerBoundaryIs>" % self[i+1].kml |
|
390 for i in xrange(self.num_interior_rings)]) |
|
391 return "<Polygon><outerBoundaryIs>%s</outerBoundaryIs>%s</Polygon>" % (self[0].kml, inner_kml) |