27 >>> mpnt.transform_to(SpatialReference('NAD27')) |
27 >>> mpnt.transform_to(SpatialReference('NAD27')) |
28 >>> print mpnt.proj |
28 >>> print mpnt.proj |
29 +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs |
29 +proj=longlat +ellps=clrk66 +datum=NAD27 +no_defs |
30 >>> print mpnt |
30 >>> print mpnt |
31 MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641) |
31 MULTIPOINT (-89.999930378602485 29.999797886557641,-89.999930378602485 29.999797886557641) |
32 |
32 |
33 The OGRGeomType class is to make it easy to specify an OGR geometry type: |
33 The OGRGeomType class is to make it easy to specify an OGR geometry type: |
34 >>> from django.contrib.gis.gdal import OGRGeomType |
34 >>> from django.contrib.gis.gdal import OGRGeomType |
35 >>> gt1 = OGRGeomType(3) # Using an integer for the type |
35 >>> gt1 = OGRGeomType(3) # Using an integer for the type |
36 >>> gt2 = OGRGeomType('Polygon') # Using a string |
36 >>> gt2 = OGRGeomType('Polygon') # Using a string |
37 >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive |
37 >>> gt3 = OGRGeomType('POLYGON') # It's case-insensitive |
38 >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects |
38 >>> print gt1 == 3, gt1 == 'Polygon' # Equivalence works w/non-OGRGeomType objects |
39 True |
39 True |
40 """ |
40 """ |
41 # Python library requisites. |
41 # Python library requisites. |
42 import re, sys |
42 import sys |
43 from binascii import a2b_hex |
43 from binascii import a2b_hex |
44 from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p |
44 from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p |
45 |
45 |
46 # Getting GDAL prerequisites |
46 # Getting GDAL prerequisites |
47 from django.contrib.gis.gdal.base import GDALBase |
47 from django.contrib.gis.gdal.base import GDALBase |
48 from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope |
48 from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope |
49 from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException |
49 from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException |
50 from django.contrib.gis.gdal.geomtype import OGRGeomType |
50 from django.contrib.gis.gdal.geomtype import OGRGeomType |
|
51 from django.contrib.gis.gdal.libgdal import GEOJSON, GDAL_VERSION |
51 from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform |
52 from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform |
52 |
53 |
53 # Getting the ctypes prototype functions that interface w/the GDAL C library. |
54 # 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 from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api |
55 GEOJSON = capi.GEOJSON |
56 |
|
57 # For recognizing geometry input. |
|
58 from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex |
56 |
59 |
57 # For more information, see the OGR C API source code: |
60 # For more information, see the OGR C API source code: |
58 # http://www.gdal.org/ogr/ogr__api_8h.html |
61 # http://www.gdal.org/ogr/ogr__api_8h.html |
59 # |
62 # |
60 # The OGR_G_* routines are relevant here. |
63 # The OGR_G_* routines are relevant here. |
61 |
64 |
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 #### |
65 #### OGRGeometry Class #### |
68 class OGRGeometry(GDALBase): |
66 class OGRGeometry(GDALBase): |
69 "Generally encapsulates an OGR geometry." |
67 "Generally encapsulates an OGR geometry." |
70 |
68 |
71 def __init__(self, geom_input, srs=None): |
69 def __init__(self, geom_input, srs=None): |
76 # If HEX, unpack input to to a binary buffer. |
74 # If HEX, unpack input to to a binary buffer. |
77 if str_instance and hex_regex.match(geom_input): |
75 if str_instance and hex_regex.match(geom_input): |
78 geom_input = buffer(a2b_hex(geom_input.upper())) |
76 geom_input = buffer(a2b_hex(geom_input.upper())) |
79 str_instance = False |
77 str_instance = False |
80 |
78 |
81 # Constructing the geometry, |
79 # Constructing the geometry, |
82 if str_instance: |
80 if str_instance: |
83 # Checking if unicode |
81 # Checking if unicode |
84 if isinstance(geom_input, unicode): |
82 if isinstance(geom_input, unicode): |
85 # Encoding to ASCII, WKT or HEX doesn't need any more. |
83 # Encoding to ASCII, WKT or HEX doesn't need any more. |
86 geom_input = geom_input.encode('ascii') |
84 geom_input = geom_input.encode('ascii') |
87 |
85 |
88 wkt_m = wkt_regex.match(geom_input) |
86 wkt_m = wkt_regex.match(geom_input) |
89 json_m = json_regex.match(geom_input) |
87 json_m = json_regex.match(geom_input) |
90 if wkt_m: |
88 if wkt_m: |
|
89 if wkt_m.group('srid'): |
|
90 # If there's EWKT, set the SRS w/value of the SRID. |
|
91 srs = int(wkt_m.group('srid')) |
91 if wkt_m.group('type').upper() == 'LINEARRING': |
92 if wkt_m.group('type').upper() == 'LINEARRING': |
92 # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT. |
93 # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT. |
93 # See http://trac.osgeo.org/gdal/ticket/1992. |
94 # See http://trac.osgeo.org/gdal/ticket/1992. |
94 g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num) |
95 g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num) |
95 capi.import_wkt(g, byref(c_char_p(geom_input))) |
96 capi.import_wkt(g, byref(c_char_p(wkt_m.group('wkt')))) |
96 else: |
97 else: |
97 g = capi.from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p())) |
98 g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt'))), None, byref(c_void_p())) |
98 elif json_m: |
99 elif json_m: |
99 if GEOJSON: |
100 if GEOJSON: |
100 g = capi.from_json(geom_input) |
101 g = capi.from_json(geom_input) |
101 else: |
102 else: |
102 raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') |
103 raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.') |
127 if bool(srs): self.srs = srs |
128 if bool(srs): self.srs = srs |
128 |
129 |
129 # Setting the class depending upon the OGR Geometry Type |
130 # Setting the class depending upon the OGR Geometry Type |
130 self.__class__ = GEO_CLASSES[self.geom_type.num] |
131 self.__class__ = GEO_CLASSES[self.geom_type.num] |
131 |
132 |
|
133 def __del__(self): |
|
134 "Deletes this Geometry." |
|
135 if self._ptr: capi.destroy_geom(self._ptr) |
|
136 |
|
137 # Pickle routines |
|
138 def __getstate__(self): |
|
139 srs = self.srs |
|
140 if srs: |
|
141 srs = srs.wkt |
|
142 else: |
|
143 srs = None |
|
144 return str(self.wkb), srs |
|
145 |
|
146 def __setstate__(self, state): |
|
147 wkb, srs = state |
|
148 ptr = capi.from_wkb(wkb, None, byref(c_void_p()), len(wkb)) |
|
149 if not ptr: raise OGRException('Invalid OGRGeometry loaded from pickled state.') |
|
150 self.ptr = ptr |
|
151 self.srs = srs |
|
152 |
132 @classmethod |
153 @classmethod |
133 def from_bbox(cls, bbox): |
154 def from_bbox(cls, bbox): |
134 "Constructs a Polygon from a bounding box (4-tuple)." |
155 "Constructs a Polygon from a bounding box (4-tuple)." |
135 x0, y0, x1, y1 = bbox |
156 x0, y0, x1, y1 = bbox |
136 return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( |
157 return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' % ( |
137 x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) ) |
158 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 |
159 |
143 ### Geometry set-like operations ### |
160 ### Geometry set-like operations ### |
144 # g = g1 | g2 |
161 # g = g1 | g2 |
145 def __or__(self, other): |
162 def __or__(self, other): |
146 "Returns the union of the two geometries." |
163 "Returns the union of the two geometries." |
161 "Return the symmetric difference of this Geometry and the other." |
178 "Return the symmetric difference of this Geometry and the other." |
162 return self.sym_difference(other) |
179 return self.sym_difference(other) |
163 |
180 |
164 def __eq__(self, other): |
181 def __eq__(self, other): |
165 "Is this Geometry equal to the other?" |
182 "Is this Geometry equal to the other?" |
166 return self.equals(other) |
183 if isinstance(other, OGRGeometry): |
|
184 return self.equals(other) |
|
185 else: |
|
186 return False |
167 |
187 |
168 def __ne__(self, other): |
188 def __ne__(self, other): |
169 "Tests for inequality." |
189 "Tests for inequality." |
170 return not self.equals(other) |
190 return not (self == other) |
171 |
191 |
172 def __str__(self): |
192 def __str__(self): |
173 "WKT is used for the string representation." |
193 "WKT is used for the string representation." |
174 return self.wkt |
194 return self.wkt |
175 |
195 |
177 @property |
197 @property |
178 def dimension(self): |
198 def dimension(self): |
179 "Returns 0 for points, 1 for lines, and 2 for surfaces." |
199 "Returns 0 for points, 1 for lines, and 2 for surfaces." |
180 return capi.get_dims(self.ptr) |
200 return capi.get_dims(self.ptr) |
181 |
201 |
182 @property |
202 def _get_coord_dim(self): |
183 def coord_dim(self): |
|
184 "Returns the coordinate dimension of the Geometry." |
203 "Returns the coordinate dimension of the Geometry." |
185 return capi.get_coord_dims(self.ptr) |
204 if isinstance(self, GeometryCollection) and GDAL_VERSION < (1, 5, 2): |
|
205 # On GDAL versions prior to 1.5.2, there exists a bug in which |
|
206 # the coordinate dimension of geometry collections is always 2: |
|
207 # http://trac.osgeo.org/gdal/ticket/2334 |
|
208 # Here we workaround by returning the coordinate dimension of the |
|
209 # first geometry in the collection instead. |
|
210 if len(self): |
|
211 return capi.get_coord_dim(capi.get_geom_ref(self.ptr, 0)) |
|
212 return capi.get_coord_dim(self.ptr) |
|
213 |
|
214 def _set_coord_dim(self, dim): |
|
215 "Sets the coordinate dimension of this Geometry." |
|
216 if not dim in (2, 3): |
|
217 raise ValueError('Geometry dimension must be either 2 or 3') |
|
218 capi.set_coord_dim(self.ptr, dim) |
|
219 |
|
220 coord_dim = property(_get_coord_dim, _set_coord_dim) |
186 |
221 |
187 @property |
222 @property |
188 def geom_count(self): |
223 def geom_count(self): |
189 "The number of elements in this Geometry." |
224 "The number of elements in this Geometry." |
190 return capi.get_geom_count(self.ptr) |
225 return capi.get_geom_count(self.ptr) |
247 except SRSException: |
276 except SRSException: |
248 return None |
277 return None |
249 |
278 |
250 def _set_srs(self, srs): |
279 def _set_srs(self, srs): |
251 "Sets the SpatialReference for this geometry." |
280 "Sets the SpatialReference for this geometry." |
|
281 # Do not have to clone the `SpatialReference` object pointer because |
|
282 # when it is assigned to this `OGRGeometry` it's internal OGR |
|
283 # reference count is incremented, and will likewise be released |
|
284 # (decremented) when this geometry's destructor is called. |
252 if isinstance(srs, SpatialReference): |
285 if isinstance(srs, SpatialReference): |
253 srs_ptr = srs_api.clone_srs(srs.ptr) |
286 srs_ptr = srs.ptr |
254 elif isinstance(srs, (int, long, basestring)): |
287 elif isinstance(srs, (int, long, basestring)): |
255 sr = SpatialReference(srs) |
288 sr = SpatialReference(srs) |
256 srs_ptr = srs_api.clone_srs(sr.ptr) |
289 srs_ptr = sr.ptr |
257 else: |
290 else: |
258 raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) |
291 raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs)) |
259 capi.assign_srs(self.ptr, srs_ptr) |
292 capi.assign_srs(self.ptr, srs_ptr) |
260 |
293 |
261 srs = property(_get_srs, _set_srs) |
294 srs = property(_get_srs, _set_srs) |
361 """ |
403 """ |
362 if clone: |
404 if clone: |
363 klone = self.clone() |
405 klone = self.clone() |
364 klone.transform(coord_trans) |
406 klone.transform(coord_trans) |
365 return klone |
407 return klone |
|
408 |
|
409 # Have to get the coordinate dimension of the original geometry |
|
410 # so it can be used to reset the transformed geometry's dimension |
|
411 # afterwards. This is done because of GDAL bug (in versions prior |
|
412 # to 1.7) that turns geometries 3D after transformation, see: |
|
413 # http://trac.osgeo.org/gdal/changeset/17792 |
|
414 if GDAL_VERSION < (1, 7): |
|
415 orig_dim = self.coord_dim |
|
416 |
|
417 # Depending on the input type, use the appropriate OGR routine |
|
418 # to perform the transformation. |
366 if isinstance(coord_trans, CoordTransform): |
419 if isinstance(coord_trans, CoordTransform): |
367 capi.geom_transform(self.ptr, coord_trans.ptr) |
420 capi.geom_transform(self.ptr, coord_trans.ptr) |
368 elif isinstance(coord_trans, SpatialReference): |
421 elif isinstance(coord_trans, SpatialReference): |
369 capi.geom_transform_to(self.ptr, coord_trans.ptr) |
422 capi.geom_transform_to(self.ptr, coord_trans.ptr) |
370 elif isinstance(coord_trans, (int, long, basestring)): |
423 elif isinstance(coord_trans, (int, long, basestring)): |
371 sr = SpatialReference(coord_trans) |
424 sr = SpatialReference(coord_trans) |
372 capi.geom_transform_to(self.ptr, sr.ptr) |
425 capi.geom_transform_to(self.ptr, sr.ptr) |
373 else: |
426 else: |
374 raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.') |
427 raise TypeError('Transform only accepts CoordTransform, ' |
|
428 'SpatialReference, string, and integer objects.') |
|
429 |
|
430 # Setting with original dimension, see comment above. |
|
431 if GDAL_VERSION < (1, 7): |
|
432 if isinstance(self, GeometryCollection): |
|
433 # With geometry collections have to set dimension on |
|
434 # each internal geometry reference, as the collection |
|
435 # dimension isn't affected. |
|
436 for i in xrange(len(self)): |
|
437 internal_ptr = capi.get_geom_ref(self.ptr, i) |
|
438 if orig_dim != capi.get_coord_dim(internal_ptr): |
|
439 capi.set_coord_dim(internal_ptr, orig_dim) |
|
440 else: |
|
441 if self.coord_dim != orig_dim: |
|
442 self.coord_dim = orig_dim |
375 |
443 |
376 def transform_to(self, srs): |
444 def transform_to(self, srs): |
377 "For backwards-compatibility." |
445 "For backwards-compatibility." |
378 self.transform(srs) |
446 self.transform(srs) |
379 |
447 |
389 return func(self.ptr, other.ptr) |
457 return func(self.ptr, other.ptr) |
390 |
458 |
391 def intersects(self, other): |
459 def intersects(self, other): |
392 "Returns True if this geometry intersects with the other." |
460 "Returns True if this geometry intersects with the other." |
393 return self._topology(capi.ogr_intersects, other) |
461 return self._topology(capi.ogr_intersects, other) |
394 |
462 |
395 def equals(self, other): |
463 def equals(self, other): |
396 "Returns True if this geometry is equivalent to the other." |
464 "Returns True if this geometry is equivalent to the other." |
397 return self._topology(capi.ogr_equals, other) |
465 return self._topology(capi.ogr_equals, other) |
398 |
466 |
399 def disjoint(self, other): |
467 def disjoint(self, other): |