|
1 """ |
|
2 The OGRGeometry is a wrapper for using the OGR Geometry class |
|
3 (see http://www.gdal.org/ogr/classOGRGeometry.html). OGRGeometry |
|
4 may be instantiated when reading geometries from OGR Data Sources |
|
5 (e.g. SHP files), or when given OGC WKT (a string). |
|
6 |
|
7 While the 'full' API is not present yet, the API is "pythonic" unlike |
|
8 the traditional and "next-generation" OGR Python bindings. One major |
|
9 advantage OGR Geometries have over their GEOS counterparts is support |
|
10 for spatial reference systems and their transformation. |
|
11 |
|
12 Example: |
|
13 >>> from django.contrib.gis.gdal import OGRGeometry, OGRGeomType, SpatialReference |
|
14 >>> wkt1, wkt2 = 'POINT(-90 30)', 'POLYGON((0 0, 5 0, 5 5, 0 5)' |
|
15 >>> pnt = OGRGeometry(wkt1) |
|
16 >>> print pnt |
|
17 POINT (-90 30) |
|
18 >>> mpnt = OGRGeometry(OGRGeomType('MultiPoint'), SpatialReference('WGS84')) |
|
19 >>> mpnt.add(wkt1) |
|
20 >>> mpnt.add(wkt1) |
|
21 >>> print mpnt |
|
22 MULTIPOINT (-90 30,-90 30) |
|
23 >>> print mpnt.srs.name |
|
24 WGS 84 |
|
25 >>> print mpnt.srs.proj |
|
26 +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs |
|
27 >>> mpnt.transform_to(SpatialReference('NAD27')) |
|
28 >>> print mpnt.proj |
|
29 +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs |
|
30 >>> print mpnt |
|
31 MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641) |
|
32 |
|
33 The OGRGeomType class is to make it easy to specify an OGR geometry type: |
|
34 >>> from django.contrib.gis.gdal import OGRGeomType |
|
35 >>> gt1 = OGRGeomType(3) # Using an integer for the type |
|
36 >>> gt2 = OGRGeomType('Polygon') # Using a string |
|
37 >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive |
|
38 >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects |
|
39 True |
|
40 """ |
|
41 # Python library requisites. |
|
42 import re, sys |
|
43 from binascii import a2b_hex |
|
44 from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p |
|
45 |
|
46 # Getting GDAL prerequisites |
|
47 from django.contrib.gis.gdal.base import GDALBase |
|
48 from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope |
|
49 from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException |
|
50 from django.contrib.gis.gdal.geomtype import OGRGeomType |
|
51 from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform |
|
52 |
|
53 # Getting the ctypes prototype functions that interface w/the GDAL C library. |
|
54 from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api |
|
55 GEOJSON = capi.GEOJSON |
|
56 |
|
57 # For more information, see the OGR C API source code: |
|
58 # http://www.gdal.org/ogr/ogr__api_8h.html |
|
59 # |
|
60 # The OGR_G_* routines are relevant here. |
|
61 |
|
62 # Regular expressions for recognizing HEXEWKB and WKT. |
|
63 hex_regex = re.compile(r'^[0-9A-F]+$', re.I) |
|
64 wkt_regex = re.compile(r'^(?P<type>POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+$', re.I) |
|
65 json_regex = re.compile(r'^(\s+)?\{[\s\w,\[\]\{\}\-\."\':]+\}(\s+)?$') |
|
66 |
|
67 #### OGRGeometry Class #### |
|
68 class OGRGeometry(GDALBase): |
|
69 "Generally encapsulates an OGR geometry." |
|
70 |
|
71 def __init__(self, geom_input, srs=None): |
|
72 "Initializes Geometry on either WKT or an OGR pointer as input." |
|
73 |
|
74 str_instance = isinstance(geom_input, basestring) |
|
75 |
|
76 # If HEX, unpack input to to a binary buffer. |
|
77 if str_instance and hex_regex.match(geom_input): |
|
78 geom_input = buffer(a2b_hex(geom_input.upper())) |
|
79 str_instance = False |
|
80 |
|
81 # Constructing the geometry, |
|
82 if str_instance: |
|
83 # Checking if unicode |
|
84 if isinstance(geom_input, unicode): |
|
85 # Encoding to ASCII, WKT or HEX doesn't need any more. |
|
86 geom_input = geom_input.encode('ascii') |
|
87 |
|
88 wkt_m = wkt_regex.match(geom_input) |
|
89 json_m = json_regex.match(geom_input) |
|
90 if wkt_m: |
|
91 if wkt_m.group('type').upper() == 'LINEARRING': |
|
92 # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT. |
|
93 # See http://trac.osgeo.org/gdal/ticket/1992. |
|
94 g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num) |
|
95 capi.import_wkt(g, byref(c_char_p(geom_input))) |
|
96 else: |
|
97 g = capi.from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) |
|
98 elif json_m: |
|
99 if GEOJSON: |
|
100 g = capi.from_json(geom_input) |
|
101 else: |
|
102 raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') |
|
103 else: |
|
104 # Seeing if the input is a valid short-hand string |
|
105 # (e.g., 'Point', 'POLYGON'). |
|
106 ogr_t = OGRGeomType(geom_input) |
|
107 g = capi.create_geom(OGRGeomType(geom_input).num) |
|
108 elif isinstance(geom_input, buffer): |
|
109 # WKB was passed in |
|
110 g = capi.from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input)) |
|
111 elif isinstance(geom_input, OGRGeomType): |
|
112 # OGRGeomType was passed in, an empty geometry will be created. |
|
113 g = capi.create_geom(geom_input.num) |
|
114 elif isinstance(geom_input, self.ptr_type): |
|
115 # OGR pointer (c_void_p) was the input. |
|
116 g = geom_input |
|
117 else: |
|
118 raise OGRException('Invalid input type for OGR Geometry construction: %s' % type(geom_input)) |
|
119 |
|
120 # Now checking the Geometry pointer before finishing initialization |
|
121 # by setting the pointer for the object. |
|
122 if not g: |
|
123 raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input)) |
|
124 self.ptr = g |
|
125 |
|
126 # Assigning the SpatialReference object to the geometry, if valid. |
|
127 if bool(srs): self.srs = srs |
|
128 |
|
129 # Setting the class depending upon the OGR Geometry Type |
|
130 self.__class__ = GEO_CLASSES[self.geom_type.num] |
|
131 |
|
132 @classmethod |
|
133 def from_bbox(cls, bbox): |
|
134 "Constructs a Polygon from a bounding box (4-tuple)." |
|
135 x0, y0, x1, y1 = bbox |
|
136 return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( |
|
137 x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) |
|
138 |
|
139 def __del__(self): |
|
140 "Deletes this Geometry." |
|
141 if self._ptr: capi.destroy_geom(self._ptr) |
|
142 |
|
143 ### Geometry set-like operations ### |
|
144 # g = g1 | g2 |
|
145 def __or__(self, other): |
|
146 "Returns the union of the two geometries." |
|
147 return self.union(other) |
|
148 |
|
149 # g = g1 & g2 |
|
150 def __and__(self, other): |
|
151 "Returns the intersection of this Geometry and the other." |
|
152 return self.intersection(other) |
|
153 |
|
154 # g = g1 - g2 |
|
155 def __sub__(self, other): |
|
156 "Return the difference this Geometry and the other." |
|
157 return self.difference(other) |
|
158 |
|
159 # g = g1 ^ g2 |
|
160 def __xor__(self, other): |
|
161 "Return the symmetric difference of this Geometry and the other." |
|
162 return self.sym_difference(other) |
|
163 |
|
164 def __eq__(self, other): |
|
165 "Is this Geometry equal to the other?" |
|
166 return self.equals(other) |
|
167 |
|
168 def __ne__(self, other): |
|
169 "Tests for inequality." |
|
170 return not self.equals(other) |
|
171 |
|
172 def __str__(self): |
|
173 "WKT is used for the string representation." |
|
174 return self.wkt |
|
175 |
|
176 #### Geometry Properties #### |
|
177 @property |
|
178 def dimension(self): |
|
179 "Returns 0 for points, 1 for lines, and 2 for surfaces." |
|
180 return capi.get_dims(self.ptr) |
|
181 |
|
182 @property |
|
183 def coord_dim(self): |
|
184 "Returns the coordinate dimension of the Geometry." |
|
185 return capi.get_coord_dims(self.ptr) |
|
186 |
|
187 @property |
|
188 def geom_count(self): |
|
189 "The number of elements in this Geometry." |
|
190 return capi.get_geom_count(self.ptr) |
|
191 |
|
192 @property |
|
193 def point_count(self): |
|
194 "Returns the number of Points in this Geometry." |
|
195 return capi.get_point_count(self.ptr) |
|
196 |
|
197 @property |
|
198 def num_points(self): |
|
199 "Alias for `point_count` (same name method in GEOS API.)" |
|
200 return self.point_count |
|
201 |
|
202 @property |
|
203 def num_coords(self): |
|
204 "Alais for `point_count`." |
|
205 return self.point_count |
|
206 |
|
207 @property |
|
208 def geom_type(self): |
|
209 "Returns the Type for this Geometry." |
|
210 try: |
|
211 return OGRGeomType(capi.get_geom_type(self.ptr)) |
|
212 except OGRException: |
|
213 # VRT datasources return an invalid geometry type |
|
214 # number, but a valid name -- we'll try that instead. |
|
215 # See: http://trac.osgeo.org/gdal/ticket/2491 |
|
216 return OGRGeomType(capi.get_geom_name(self.ptr)) |
|
217 |
|
218 @property |
|
219 def geom_name(self): |
|
220 "Returns the Name of this Geometry." |
|
221 return capi.get_geom_name(self.ptr) |
|
222 |
|
223 @property |
|
224 def area(self): |
|
225 "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise." |
|
226 return capi.get_area(self.ptr) |
|
227 |
|
228 @property |
|
229 def envelope(self): |
|
230 "Returns the envelope for this Geometry." |
|
231 # TODO: Fix Envelope() for Point geometries. |
|
232 return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope()))) |
|
233 |
|
234 @property |
|
235 def extent(self): |
|
236 "Returns the envelope as a 4-tuple, instead of as an Envelope object." |
|
237 return self.envelope.tuple |
|
238 |
|
239 #### SpatialReference-related Properties #### |
|
240 |
|
241 # The SRS property |
|
242 def _get_srs(self): |
|
243 "Returns the Spatial Reference for this Geometry." |
|
244 try: |
|
245 srs_ptr = capi.get_geom_srs(self.ptr) |
|
246 return SpatialReference(srs_api.clone_srs(srs_ptr)) |
|
247 except SRSException: |
|
248 return None |
|
249 |
|
250 def _set_srs(self, srs): |
|
251 "Sets the SpatialReference for this geometry." |
|
252 if isinstance(srs, SpatialReference): |
|
253 srs_ptr = srs_api.clone_srs(srs.ptr) |
|
254 elif isinstance(srs, (int, long, basestring)): |
|
255 sr = SpatialReference(srs) |
|
256 srs_ptr = srs_api.clone_srs(sr.ptr) |
|
257 else: |
|
258 raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) |
|
259 capi.assign_srs(self.ptr, srs_ptr) |
|
260 |
|
261 srs = property(_get_srs, _set_srs) |
|
262 |
|
263 # The SRID property |
|
264 def _get_srid(self): |
|
265 srs = self.srs |
|
266 if srs: return srs.srid |
|
267 return None |
|
268 |
|
269 def _set_srid(self, srid): |
|
270 if isinstance(srid, (int, long)): |
|
271 self.srs = srid |
|
272 else: |
|
273 raise TypeError('SRID must be set with an integer.') |
|
274 |
|
275 srid = property(_get_srid, _set_srid) |
|
276 |
|
277 #### Output Methods #### |
|
278 @property |
|
279 def geos(self): |
|
280 "Returns a GEOSGeometry object from this OGRGeometry." |
|
281 from django.contrib.gis.geos import GEOSGeometry |
|
282 return GEOSGeometry(self.wkb, self.srid) |
|
283 |
|
284 @property |
|
285 def gml(self): |
|
286 "Returns the GML representation of the Geometry." |
|
287 return capi.to_gml(self.ptr) |
|
288 |
|
289 @property |
|
290 def hex(self): |
|
291 "Returns the hexadecimal representation of the WKB (a string)." |
|
292 return str(self.wkb).encode('hex').upper() |
|
293 #return b2a_hex(self.wkb).upper() |
|
294 |
|
295 @property |
|
296 def json(self): |
|
297 """ |
|
298 Returns the GeoJSON representation of this Geometry (requires |
|
299 GDAL 1.5+). |
|
300 """ |
|
301 if GEOJSON: |
|
302 return capi.to_json(self.ptr) |
|
303 else: |
|
304 raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.') |
|
305 geojson = json |
|
306 |
|
307 @property |
|
308 def kml(self): |
|
309 "Returns the KML representation of the Geometry." |
|
310 if GEOJSON: |
|
311 return capi.to_kml(self.ptr, None) |
|
312 else: |
|
313 raise NotImplementedError('KML output only supported on GDAL 1.5+.') |
|
314 |
|
315 @property |
|
316 def wkb_size(self): |
|
317 "Returns the size of the WKB buffer." |
|
318 return capi.get_wkbsize(self.ptr) |
|
319 |
|
320 @property |
|
321 def wkb(self): |
|
322 "Returns the WKB representation of the Geometry." |
|
323 if sys.byteorder == 'little': |
|
324 byteorder = 1 # wkbNDR (from ogr_core.h) |
|
325 else: |
|
326 byteorder = 0 # wkbXDR |
|
327 sz = self.wkb_size |
|
328 # Creating the unsigned character buffer, and passing it in by reference. |
|
329 buf = (c_ubyte * sz)() |
|
330 wkb = capi.to_wkb(self.ptr, byteorder, byref(buf)) |
|
331 # Returning a buffer of the string at the pointer. |
|
332 return buffer(string_at(buf, sz)) |
|
333 |
|
334 @property |
|
335 def wkt(self): |
|
336 "Returns the WKT representation of the Geometry." |
|
337 return capi.to_wkt(self.ptr, byref(c_char_p())) |
|
338 |
|
339 #### Geometry Methods #### |
|
340 def clone(self): |
|
341 "Clones this OGR Geometry." |
|
342 return OGRGeometry(capi.clone_geom(self.ptr), self.srs) |
|
343 |
|
344 def close_rings(self): |
|
345 """ |
|
346 If there are any rings within this geometry that have not been |
|
347 closed, this routine will do so by adding the starting point at the |
|
348 end. |
|
349 """ |
|
350 # Closing the open rings. |
|
351 capi.geom_close_rings(self.ptr) |
|
352 |
|
353 def transform(self, coord_trans, clone=False): |
|
354 """ |
|
355 Transforms this geometry to a different spatial reference system. |
|
356 May take a CoordTransform object, a SpatialReference object, string |
|
357 WKT or PROJ.4, and/or an integer SRID. By default nothing is returned |
|
358 and the geometry is transformed in-place. However, if the `clone` |
|
359 keyword is set, then a transformed clone of this geometry will be |
|
360 returned. |
|
361 """ |
|
362 if clone: |
|
363 klone = self.clone() |
|
364 klone.transform(coord_trans) |
|
365 return klone |
|
366 if isinstance(coord_trans, CoordTransform): |
|
367 capi.geom_transform(self.ptr, coord_trans.ptr) |
|
368 elif isinstance(coord_trans, SpatialReference): |
|
369 capi.geom_transform_to(self.ptr, coord_trans.ptr) |
|
370 elif isinstance(coord_trans, (int, long, basestring)): |
|
371 sr = SpatialReference(coord_trans) |
|
372 capi.geom_transform_to(self.ptr, sr.ptr) |
|
373 else: |
|
374 raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.') |
|
375 |
|
376 def transform_to(self, srs): |
|
377 "For backwards-compatibility." |
|
378 self.transform(srs) |
|
379 |
|
380 #### Topology Methods #### |
|
381 def _topology(self, func, other): |
|
382 """A generalized function for topology operations, takes a GDAL function and |
|
383 the other geometry to perform the operation on.""" |
|
384 if not isinstance(other, OGRGeometry): |
|
385 raise TypeError('Must use another OGRGeometry object for topology operations!') |
|
386 |
|
387 # Returning the output of the given function with the other geometry's |
|
388 # pointer. |
|
389 return func(self.ptr, other.ptr) |
|
390 |
|
391 def intersects(self, other): |
|
392 "Returns True if this geometry intersects with the other." |
|
393 return self._topology(capi.ogr_intersects, other) |
|
394 |
|
395 def equals(self, other): |
|
396 "Returns True if this geometry is equivalent to the other." |
|
397 return self._topology(capi.ogr_equals, other) |
|
398 |
|
399 def disjoint(self, other): |
|
400 "Returns True if this geometry and the other are spatially disjoint." |
|
401 return self._topology(capi.ogr_disjoint, other) |
|
402 |
|
403 def touches(self, other): |
|
404 "Returns True if this geometry touches the other." |
|
405 return self._topology(capi.ogr_touches, other) |
|
406 |
|
407 def crosses(self, other): |
|
408 "Returns True if this geometry crosses the other." |
|
409 return self._topology(capi.ogr_crosses, other) |
|
410 |
|
411 def within(self, other): |
|
412 "Returns True if this geometry is within the other." |
|
413 return self._topology(capi.ogr_within, other) |
|
414 |
|
415 def contains(self, other): |
|
416 "Returns True if this geometry contains the other." |
|
417 return self._topology(capi.ogr_contains, other) |
|
418 |
|
419 def overlaps(self, other): |
|
420 "Returns True if this geometry overlaps the other." |
|
421 return self._topology(capi.ogr_overlaps, other) |
|
422 |
|
423 #### Geometry-generation Methods #### |
|
424 def _geomgen(self, gen_func, other=None): |
|
425 "A helper routine for the OGR routines that generate geometries." |
|
426 if isinstance(other, OGRGeometry): |
|
427 return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs) |
|
428 else: |
|
429 return OGRGeometry(gen_func(self.ptr), self.srs) |
|
430 |
|
431 @property |
|
432 def boundary(self): |
|
433 "Returns the boundary of this geometry." |
|
434 return self._geomgen(capi.get_boundary) |
|
435 |
|
436 @property |
|
437 def convex_hull(self): |
|
438 """ |
|
439 Returns the smallest convex Polygon that contains all the points in |
|
440 this Geometry. |
|
441 """ |
|
442 return self._geomgen(capi.geom_convex_hull) |
|
443 |
|
444 def difference(self, other): |
|
445 """ |
|
446 Returns a new geometry consisting of the region which is the difference |
|
447 of this geometry and the other. |
|
448 """ |
|
449 return self._geomgen(capi.geom_diff, other) |
|
450 |
|
451 def intersection(self, other): |
|
452 """ |
|
453 Returns a new geometry consisting of the region of intersection of this |
|
454 geometry and the other. |
|
455 """ |
|
456 return self._geomgen(capi.geom_intersection, other) |
|
457 |
|
458 def sym_difference(self, other): |
|
459 """ |
|
460 Returns a new geometry which is the symmetric difference of this |
|
461 geometry and the other. |
|
462 """ |
|
463 return self._geomgen(capi.geom_sym_diff, other) |
|
464 |
|
465 def union(self, other): |
|
466 """ |
|
467 Returns a new geometry consisting of the region which is the union of |
|
468 this geometry and the other. |
|
469 """ |
|
470 return self._geomgen(capi.geom_union, other) |
|
471 |
|
472 # The subclasses for OGR Geometry. |
|
473 class Point(OGRGeometry): |
|
474 |
|
475 @property |
|
476 def x(self): |
|
477 "Returns the X coordinate for this Point." |
|
478 return capi.getx(self.ptr, 0) |
|
479 |
|
480 @property |
|
481 def y(self): |
|
482 "Returns the Y coordinate for this Point." |
|
483 return capi.gety(self.ptr, 0) |
|
484 |
|
485 @property |
|
486 def z(self): |
|
487 "Returns the Z coordinate for this Point." |
|
488 if self.coord_dim == 3: |
|
489 return capi.getz(self.ptr, 0) |
|
490 |
|
491 @property |
|
492 def tuple(self): |
|
493 "Returns the tuple of this point." |
|
494 if self.coord_dim == 2: |
|
495 return (self.x, self.y) |
|
496 elif self.coord_dim == 3: |
|
497 return (self.x, self.y, self.z) |
|
498 coords = tuple |
|
499 |
|
500 class LineString(OGRGeometry): |
|
501 |
|
502 def __getitem__(self, index): |
|
503 "Returns the Point at the given index." |
|
504 if index >= 0 and index < self.point_count: |
|
505 x, y, z = c_double(), c_double(), c_double() |
|
506 capi.get_point(self.ptr, index, byref(x), byref(y), byref(z)) |
|
507 dim = self.coord_dim |
|
508 if dim == 1: |
|
509 return (x.value,) |
|
510 elif dim == 2: |
|
511 return (x.value, y.value) |
|
512 elif dim == 3: |
|
513 return (x.value, y.value, z.value) |
|
514 else: |
|
515 raise OGRIndexError('index out of range: %s' % str(index)) |
|
516 |
|
517 def __iter__(self): |
|
518 "Iterates over each point in the LineString." |
|
519 for i in xrange(self.point_count): |
|
520 yield self[i] |
|
521 |
|
522 def __len__(self): |
|
523 "The length returns the number of points in the LineString." |
|
524 return self.point_count |
|
525 |
|
526 @property |
|
527 def tuple(self): |
|
528 "Returns the tuple representation of this LineString." |
|
529 return tuple([self[i] for i in xrange(len(self))]) |
|
530 coords = tuple |
|
531 |
|
532 def _listarr(self, func): |
|
533 """ |
|
534 Internal routine that returns a sequence (list) corresponding with |
|
535 the given function. |
|
536 """ |
|
537 return [func(self.ptr, i) for i in xrange(len(self))] |
|
538 |
|
539 @property |
|
540 def x(self): |
|
541 "Returns the X coordinates in a list." |
|
542 return self._listarr(capi.getx) |
|
543 |
|
544 @property |
|
545 def y(self): |
|
546 "Returns the Y coordinates in a list." |
|
547 return self._listarr(capi.gety) |
|
548 |
|
549 @property |
|
550 def z(self): |
|
551 "Returns the Z coordinates in a list." |
|
552 if self.coord_dim == 3: |
|
553 return self._listarr(capi.getz) |
|
554 |
|
555 # LinearRings are used in Polygons. |
|
556 class LinearRing(LineString): pass |
|
557 |
|
558 class Polygon(OGRGeometry): |
|
559 |
|
560 def __len__(self): |
|
561 "The number of interior rings in this Polygon." |
|
562 return self.geom_count |
|
563 |
|
564 def __iter__(self): |
|
565 "Iterates through each ring in the Polygon." |
|
566 for i in xrange(self.geom_count): |
|
567 yield self[i] |
|
568 |
|
569 def __getitem__(self, index): |
|
570 "Gets the ring at the specified index." |
|
571 if index < 0 or index >= self.geom_count: |
|
572 raise OGRIndexError('index out of range: %s' % index) |
|
573 else: |
|
574 return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs) |
|
575 |
|
576 # Polygon Properties |
|
577 @property |
|
578 def shell(self): |
|
579 "Returns the shell of this Polygon." |
|
580 return self[0] # First ring is the shell |
|
581 exterior_ring = shell |
|
582 |
|
583 @property |
|
584 def tuple(self): |
|
585 "Returns a tuple of LinearRing coordinate tuples." |
|
586 return tuple([self[i].tuple for i in xrange(self.geom_count)]) |
|
587 coords = tuple |
|
588 |
|
589 @property |
|
590 def point_count(self): |
|
591 "The number of Points in this Polygon." |
|
592 # Summing up the number of points in each ring of the Polygon. |
|
593 return sum([self[i].point_count for i in xrange(self.geom_count)]) |
|
594 |
|
595 @property |
|
596 def centroid(self): |
|
597 "Returns the centroid (a Point) of this Polygon." |
|
598 # The centroid is a Point, create a geometry for this. |
|
599 p = OGRGeometry(OGRGeomType('Point')) |
|
600 capi.get_centroid(self.ptr, p.ptr) |
|
601 return p |
|
602 |
|
603 # Geometry Collection base class. |
|
604 class GeometryCollection(OGRGeometry): |
|
605 "The Geometry Collection class." |
|
606 |
|
607 def __getitem__(self, index): |
|
608 "Gets the Geometry at the specified index." |
|
609 if index < 0 or index >= self.geom_count: |
|
610 raise OGRIndexError('index out of range: %s' % index) |
|
611 else: |
|
612 return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs) |
|
613 |
|
614 def __iter__(self): |
|
615 "Iterates over each Geometry." |
|
616 for i in xrange(self.geom_count): |
|
617 yield self[i] |
|
618 |
|
619 def __len__(self): |
|
620 "The number of geometries in this Geometry Collection." |
|
621 return self.geom_count |
|
622 |
|
623 def add(self, geom): |
|
624 "Add the geometry to this Geometry Collection." |
|
625 if isinstance(geom, OGRGeometry): |
|
626 if isinstance(geom, self.__class__): |
|
627 for g in geom: capi.add_geom(self.ptr, g.ptr) |
|
628 else: |
|
629 capi.add_geom(self.ptr, geom.ptr) |
|
630 elif isinstance(geom, basestring): |
|
631 tmp = OGRGeometry(geom) |
|
632 capi.add_geom(self.ptr, tmp.ptr) |
|
633 else: |
|
634 raise OGRException('Must add an OGRGeometry.') |
|
635 |
|
636 @property |
|
637 def point_count(self): |
|
638 "The number of Points in this Geometry Collection." |
|
639 # Summing up the number of points in each geometry in this collection |
|
640 return sum([self[i].point_count for i in xrange(self.geom_count)]) |
|
641 |
|
642 @property |
|
643 def tuple(self): |
|
644 "Returns a tuple representation of this Geometry Collection." |
|
645 return tuple([self[i].tuple for i in xrange(self.geom_count)]) |
|
646 coords = tuple |
|
647 |
|
648 # Multiple Geometry types. |
|
649 class MultiPoint(GeometryCollection): pass |
|
650 class MultiLineString(GeometryCollection): pass |
|
651 class MultiPolygon(GeometryCollection): pass |
|
652 |
|
653 # Class mapping dictionary (using the OGRwkbGeometryType as the key) |
|
654 GEO_CLASSES = {1 : Point, |
|
655 2 : LineString, |
|
656 3 : Polygon, |
|
657 4 : MultiPoint, |
|
658 5 : MultiLineString, |
|
659 6 : MultiPolygon, |
|
660 7 : GeometryCollection, |
|
661 101: LinearRing, |
|
662 } |