2
This module contains the 'base' GEOSGeometry object -- all GEOS Geometries
3
inherit from this object.
5
# Python, ctypes and types dependencies.
7
from ctypes import addressof, byref, c_double, c_size_t
8
from types import UnicodeType
10
# GEOS-related dependencies.
11
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
12
from django.contrib.gis.geos.error import GEOSException
13
from django.contrib.gis.geos.libgeos import GEOM_PTR
15
# All other functions in this module come from the ctypes
16
# prototypes module -- which handles all interaction with
17
# the underlying GEOS library.
18
from django.contrib.gis.geos.prototypes import *
1
from ctypes import c_void_p
2
from types import NoneType
3
from django.contrib.gis.geos.error import GEOSException, GEOSIndexError
20
5
# Trying to import GDAL libraries, if available. Have to place in
21
6
# try/except since this package may be used outside GeoDjango.
23
from django.contrib.gis.gdal import OGRGeometry, SpatialReference, GEOJSON
24
from django.contrib.gis.gdal.geometries import json_regex
27
HAS_GDAL, GEOJSON = False, False
29
# Regular expression for recognizing HEXEWKB and WKT. A prophylactic measure
30
# to prevent potentially malicious input from reaching the underlying C
31
# library. Not a substitute for good web security programming practices.
32
hex_regex = re.compile(r'^[0-9A-F]+$', re.I)
33
wkt_regex = re.compile(r'^(SRID=(?P<srid>\d+);)?(?P<wkt>(POINT|LINESTRING|LINEARRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)[ACEGIMLONPSRUTY\d,\.\-\(\) ]+)$', re.I)
35
class GEOSGeometry(object):
36
"A class that, generally, encapsulates a GEOS geometry."
38
# Initially, the geometry pointer is NULL
8
from django.contrib.gis import gdal
10
# A 'dummy' gdal module.
11
class GDALInfo(object):
22
class GEOSBase(object):
24
Base object for GEOS objects that has a pointer access property
25
that controls access to the underlying C pointer.
27
# Initially the pointer is NULL.
41
#### Python 'magic' routines ####
42
def __init__(self, geo_input, srid=None):
44
The base constructor for GEOS geometry objects, and may take the
48
* string: HEXEWKB (a PostGIS-specific canonical form)
51
The `srid` keyword is used to specify the Source Reference Identifier
52
(SRID) number for this Geometry. If not set, the SRID will be None.
54
if isinstance(geo_input, basestring):
55
if isinstance(geo_input, UnicodeType):
56
# Encoding to ASCII, WKT or HEXEWKB doesn't need any more.
57
geo_input = geo_input.encode('ascii')
59
wkt_m = wkt_regex.match(geo_input)
62
if wkt_m.group('srid'): srid = int(wkt_m.group('srid'))
63
g = from_wkt(wkt_m.group('wkt'))
64
elif hex_regex.match(geo_input):
65
# Handling HEXEWKB input.
66
g = from_hex(geo_input, len(geo_input))
67
elif GEOJSON and json_regex.match(geo_input):
68
# Handling GeoJSON input.
69
wkb_input = str(OGRGeometry(geo_input).wkb)
70
g = from_wkb(wkb_input, len(wkb_input))
72
raise ValueError('String or unicode input unrecognized as WKT EWKT, and HEXEWKB.')
73
elif isinstance(geo_input, GEOM_PTR):
74
# When the input is a pointer to a geomtry (GEOM_PTR).
76
elif isinstance(geo_input, buffer):
77
# When the input is a buffer (WKB).
78
wkb_input = str(geo_input)
79
g = from_wkb(wkb_input, len(wkb_input))
81
# Invalid geometry type.
82
raise TypeError('Improper geometry input type: %s' % str(type(geo_input)))
85
# Setting the pointer object with a valid pointer.
88
raise GEOSException('Could not initialize GEOS Geometry with given input.')
90
# Post-initialization setup.
93
def _post_init(self, srid):
94
"Helper routine for performing post-initialization setup."
95
# Setting the SRID, if given.
96
if srid and isinstance(srid, int): self.srid = srid
98
# Setting the class type (e.g., Point, Polygon, etc.)
99
self.__class__ = GEOS_CLASSES[self.geom_typeid]
101
# Setting the coordinate sequence for the geometry (will be None on
102
# geometries that do not have coordinate sequences)
108
Property for controlling access to the GEOS geometry pointer. Using
109
this raises an exception when the pointer is NULL, thus preventing
110
the C library from attempting to access an invalid memory location.
115
raise GEOSException('NULL GEOS pointer encountered; was this geometry modified?')
119
Destroys this Geometry; in other words, frees the memory used by the
122
if self._ptr: destroy_geom(self._ptr)
126
Returns a clone because the copy of a GEOSGeometry may contain an
127
invalid pointer location if the original is garbage collected.
131
def __deepcopy__(self, memodict):
133
The `deepcopy` routine is used by the `Node` class of django.utils.tree;
134
thus, the protocol routine needs to be implemented to return correct
135
copies (clones) of these GEOS objects, which use C pointers.
140
"WKT is used for the string representation."
144
"Short-hand representation because WKT may be very large."
145
return '<%s object at %s>' % (self.geom_type, hex(addressof(self.ptr)))
148
def __getstate__(self):
149
# The pickled state is simply a tuple of the WKB (in string form)
151
return str(self.wkb), self.srid
153
def __setstate__(self, state):
154
# Instantiating from the tuple state that was pickled.
156
ptr = from_wkb(wkb, len(wkb))
157
if not ptr: raise GEOSException('Invalid Geometry loaded from pickled state.')
159
self._post_init(srid)
161
# Comparison operators
162
def __eq__(self, other):
164
Equivalence testing, a Geometry may be compared with another Geometry
165
or a WKT representation.
167
if isinstance(other, basestring):
168
return self.wkt == other
169
elif isinstance(other, GEOSGeometry):
170
return self.equals_exact(other)
174
def __ne__(self, other):
175
"The not equals operator."
176
return not (self == other)
178
### Geometry set-like operations ###
179
# Thanks to Sean Gillies for inspiration:
180
# http://lists.gispython.org/pipermail/community/2007-July/001034.html
182
def __or__(self, other):
183
"Returns the union of this Geometry and the other."
184
return self.union(other)
187
def __and__(self, other):
188
"Returns the intersection of this Geometry and the other."
189
return self.intersection(other)
192
def __sub__(self, other):
193
"Return the difference this Geometry and the other."
194
return self.difference(other)
197
def __xor__(self, other):
198
"Return the symmetric difference of this Geometry and the other."
199
return self.sym_difference(other)
201
#### Coordinate Sequence Routines ####
204
"Returns True if this Geometry has a coordinate sequence, False if not."
205
# Only these geometries are allowed to have coordinate sequences.
206
if isinstance(self, (Point, LineString, LinearRing)):
212
"Sets the coordinate sequence for this Geometry."
214
self._cs = GEOSCoordSeq(get_cs(self.ptr), self.hasz)
220
"Returns a clone of the coordinate sequence for this Geometry."
222
return self._cs.clone()
224
#### Geometry Info ####
227
"Returns a string representing the Geometry type, e.g. 'Polygon'"
228
return geos_type(self.ptr)
231
def geom_typeid(self):
232
"Returns an integer representing the Geometry type."
233
return geos_typeid(self.ptr)
237
"Returns the number of geometries in the Geometry."
238
return get_num_geoms(self.ptr)
241
def num_coords(self):
242
"Returns the number of coordinates in the Geometry."
243
return get_num_coords(self.ptr)
246
def num_points(self):
247
"Returns the number points, or coordinates, in the Geometry."
248
return self.num_coords
252
"Returns the dimension of this Geometry (0=point, 1=line, 2=surface)."
253
return get_dims(self.ptr)
256
"Converts this Geometry to normal form (or canonical form)."
257
return geos_normalize(self.ptr)
259
#### Unary predicates ####
263
Returns a boolean indicating whether the set of points in this Geometry
266
return geos_isempty(self.ptr)
270
"Returns whether the geometry has a 3D dimension."
271
return geos_hasz(self.ptr)
275
"Returns whether or not the geometry is a ring."
276
return geos_isring(self.ptr)
280
"Returns false if the Geometry not simple."
281
return geos_issimple(self.ptr)
285
"This property tests the validity of this Geometry."
286
return geos_isvalid(self.ptr)
288
#### Binary predicates. ####
289
def contains(self, other):
290
"Returns true if other.within(this) returns true."
291
return geos_contains(self.ptr, other.ptr)
293
def crosses(self, other):
295
Returns true if the DE-9IM intersection matrix for the two Geometries
296
is T*T****** (for a point and a curve,a point and an area or a line and
297
an area) 0******** (for two curves).
299
return geos_crosses(self.ptr, other.ptr)
301
def disjoint(self, other):
303
Returns true if the DE-9IM intersection matrix for the two Geometries
306
return geos_disjoint(self.ptr, other.ptr)
308
def equals(self, other):
310
Returns true if the DE-9IM intersection matrix for the two Geometries
313
return geos_equals(self.ptr, other.ptr)
315
def equals_exact(self, other, tolerance=0):
317
Returns true if the two Geometries are exactly equal, up to a
320
return geos_equalsexact(self.ptr, other.ptr, float(tolerance))
322
def intersects(self, other):
323
"Returns true if disjoint returns false."
324
return geos_intersects(self.ptr, other.ptr)
326
def overlaps(self, other):
328
Returns true if the DE-9IM intersection matrix for the two Geometries
329
is T*T***T** (for two points or two surfaces) 1*T***T** (for two curves).
331
return geos_overlaps(self.ptr, other.ptr)
333
def relate_pattern(self, other, pattern):
335
Returns true if the elements in the DE-9IM intersection matrix for the
336
two Geometries match the elements in pattern.
338
if not isinstance(pattern, str) or len(pattern) > 9:
339
raise GEOSException('invalid intersection matrix pattern')
340
return geos_relatepattern(self.ptr, other.ptr, pattern)
342
def touches(self, other):
344
Returns true if the DE-9IM intersection matrix for the two Geometries
345
is FT*******, F**T***** or F***T****.
347
return geos_touches(self.ptr, other.ptr)
349
def within(self, other):
351
Returns true if the DE-9IM intersection matrix for the two Geometries
354
return geos_within(self.ptr, other.ptr)
356
#### SRID Routines ####
358
"Gets the SRID for the geometry, returns None if no SRID is set."
359
s = geos_get_srid(self.ptr)
360
if s == 0: return None
363
def set_srid(self, srid):
364
"Sets the SRID for the geometry."
365
geos_set_srid(self.ptr, srid)
366
srid = property(get_srid, set_srid)
368
#### Output Routines ####
371
"Returns the EWKT (WKT + SRID) of the Geometry."
372
if self.get_srid(): return 'SRID=%s;%s' % (self.srid, self.wkt)
373
else: return self.wkt
377
"Returns the WKT (Well-Known Text) of the Geometry."
378
return to_wkt(self.ptr)
383
Returns the HEX of the Geometry -- please note that the SRID is not
384
included in this representation, because the GEOS C library uses
385
-1 by default, even if the SRID is set.
387
# A possible faster, all-python, implementation:
388
# str(self.wkb).encode('hex')
389
return to_hex(self.ptr, byref(c_size_t()))
394
Returns GeoJSON representation of this Geometry if GDAL 1.5+
397
if GEOJSON: return self.ogr.json
402
"Returns the WKB of the Geometry as a buffer."
403
bin = to_wkb(self.ptr, byref(c_size_t()))
408
"Returns the KML representation of this Geometry."
409
gtype = self.geom_type
410
return '<%s>%s</%s>' % (gtype, self.coord_seq.kml, gtype)
412
#### GDAL-specific output routines ####
415
"Returns the OGR Geometry for this Geometry."
418
return OGRGeometry(self.wkb, self.srid)
420
return OGRGeometry(self.wkb)
426
"Returns the OSR SpatialReference for SRID of this Geometry."
427
if HAS_GDAL and self.srid:
428
return SpatialReference(self.srid)
434
"Alias for `srs` property."
437
def transform(self, ct, clone=False):
439
Requires GDAL. Transforms the geometry according to the given
440
transformation object, which may be an integer SRID, and WKT or
441
PROJ.4 string. By default, the geometry is transformed in-place and
442
nothing is returned. However if the `clone` keyword is set, then this
443
geometry will not be modified and a transformed clone will be returned
447
if HAS_GDAL and srid:
448
g = OGRGeometry(self.wkb, srid)
451
ptr = from_wkb(wkb, len(wkb))
453
# User wants a cloned transformed geometry returned.
454
return GEOSGeometry(ptr, srid=g.srid)
456
# Reassigning pointer, and performing post-initialization setup
457
# again due to the reassignment.
458
destroy_geom(self.ptr)
460
self._post_init(g.srid)
462
raise GEOSException('Transformed WKB was invalid.')
464
#### Topology Routines ####
465
def _topology(self, gptr):
466
"Helper routine to return Geometry from the given pointer."
467
return GEOSGeometry(gptr, srid=self.srid)
471
"Returns the boundary as a newly allocated Geometry object."
472
return self._topology(geos_boundary(self.ptr))
474
def buffer(self, width, quadsegs=8):
476
Returns a geometry that represents all points whose distance from this
477
Geometry is less than or equal to distance. Calculations are in the
478
Spatial Reference System of this Geometry. The optional third parameter sets
479
the number of segment used to approximate a quarter circle (defaults to 8).
480
(Text from PostGIS documentation at ch. 6.1.3)
482
return self._topology(geos_buffer(self.ptr, width, quadsegs))
487
The centroid is equal to the centroid of the set of component Geometries
488
of highest dimension (since the lower-dimension geometries contribute zero
489
"weight" to the centroid).
491
return self._topology(geos_centroid(self.ptr))
494
def convex_hull(self):
496
Returns the smallest convex Polygon that contains all the points
499
return self._topology(geos_convexhull(self.ptr))
501
def difference(self, other):
503
Returns a Geometry representing the points making up this Geometry
504
that do not make up other.
506
return self._topology(geos_difference(self.ptr, other.ptr))
510
"Return the envelope for this geometry (a polygon)."
511
return self._topology(geos_envelope(self.ptr))
513
def intersection(self, other):
514
"Returns a Geometry representing the points shared by this Geometry and other."
515
return self._topology(geos_intersection(self.ptr, other.ptr))
518
def point_on_surface(self):
519
"Computes an interior point of this Geometry."
520
return self._topology(geos_pointonsurface(self.ptr))
522
def relate(self, other):
523
"Returns the DE-9IM intersection matrix for this Geometry and the other."
524
return geos_relate(self.ptr, other.ptr)
526
def simplify(self, tolerance=0.0, preserve_topology=False):
528
Returns the Geometry, simplified using the Douglas-Peucker algorithm
529
to the specified tolerance (higher tolerance => less points). If no
530
tolerance provided, defaults to 0.
532
By default, this function does not preserve topology - e.g. polygons can
533
be split, collapse to lines or disappear holes can be created or
534
disappear, and lines can cross. By specifying preserve_topology=True,
535
the result will have the same dimension and number of components as the
536
input. This is significantly slower.
538
if preserve_topology:
539
return self._topology(geos_preservesimplify(self.ptr, tolerance))
541
return self._topology(geos_simplify(self.ptr, tolerance))
543
def sym_difference(self, other):
545
Returns a set combining the points in this Geometry not in other,
546
and the points in other not in this Geometry.
548
return self._topology(geos_symdifference(self.ptr, other.ptr))
550
def union(self, other):
551
"Returns a Geometry representing all the points in this Geometry and other."
552
return self._topology(geos_union(self.ptr, other.ptr))
554
#### Other Routines ####
557
"Returns the area of the Geometry."
558
return geos_area(self.ptr, byref(c_double()))
560
def distance(self, other):
562
Returns the distance between the closest points on this Geometry
563
and the other. Units will be in those of the coordinate system of
566
if not isinstance(other, GEOSGeometry):
567
raise TypeError('distance() works only on other GEOS Geometries.')
568
return geos_distance(self.ptr, other.ptr, byref(c_double()))
573
Returns the extent of this geometry as a 4-tuple, consisting of
574
(xmin, ymin, xmax, ymax).
577
if isinstance(env, Point):
578
xmin, ymin = env.tuple
579
xmax, ymax = xmin, ymin
581
xmin, ymin = env[0][0]
582
xmax, ymax = env[0][2]
583
return (xmin, ymin, xmax, ymax)
588
Returns the length of this Geometry (e.g., 0 for point, or the
589
circumfrence of a Polygon).
591
return geos_length(self.ptr, byref(c_double()))
594
"Clones this Geometry."
595
return GEOSGeometry(geom_clone(self.ptr), srid=self.srid)
597
# Class mapping dictionary
598
from django.contrib.gis.geos.geometries import Point, Polygon, LineString, LinearRing
599
from django.contrib.gis.geos.collections import GeometryCollection, MultiPoint, MultiLineString, MultiPolygon
600
GEOS_CLASSES = {0 : Point,
607
7 : GeometryCollection,
30
# Default allowed pointer type.
33
# Pointer access property.
35
# Raise an exception if the pointer isn't valid don't
36
# want to be passing NULL pointers to routines --
38
if self._ptr: return self._ptr
39
else: raise GEOSException('NULL GEOS %s pointer encountered.' % self.__class__.__name__)
41
def _set_ptr(self, ptr):
42
# Only allow the pointer to be set with pointers of the
43
# compatible type or None (NULL).
44
if isinstance(ptr, (self.ptr_type, NoneType)):
47
raise TypeError('Incompatible pointer type')
49
# Property for controlling access to the GEOS object pointers. Using
50
# this raises an exception when the pointer is NULL, thus preventing
51
# the C library from attempting to access an invalid memory location.
52
ptr = property(_get_ptr, _set_ptr)