2
This module houses the Point, LineString, LinearRing, and Polygon OGC
3
geometry classes. All geometry classes in this module inherit from
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
14
class Point(GEOSGeometry):
16
def __init__(self, x, y=None, z=None, srid=None):
18
The Point object may be initialized with either a tuple, or individual
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
26
if isinstance(x, (tuple, list)):
27
# Here a tuple or list was passed in under the `x` parameter.
29
if ndim < 2 or ndim > 3:
30
raise TypeError('Invalid sequence parameter: %s' % str(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)):
41
raise TypeError('Invalid parameters given for Point initialization.')
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])
49
# Initializing using the address returned from the GEOS
50
# createPoint factory.
51
super(Point, self).__init__(create_point(cs), srid=srid)
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
60
"Returns the X component of the Point."
61
return self._cs.getOrdinate(0, 0)
63
def set_x(self, value):
64
"Sets the X component of the Point."
65
self._cs.setOrdinate(0, 0, value)
68
"Returns the Y component of the Point."
69
return self._cs.getOrdinate(1, 0)
71
def set_y(self, value):
72
"Sets the Y component of the Point."
73
self._cs.setOrdinate(1, 0, value)
76
"Returns the Z component of the Point."
78
return self._cs.getOrdinate(2, 0)
82
def set_z(self, value):
83
"Sets the Z component of the Point."
85
self._cs.setOrdinate(2, 0, value)
87
raise GEOSException('Cannot set Z on 2D Point.')
90
x = property(get_x, set_x)
91
y = property(get_y, set_y)
92
z = property(get_z, set_z)
94
### Tuple setting and retrieval routines. ###
96
"Returns a tuple of the point."
99
def set_coords(self, tup):
100
"Sets the coordinates of the point with the given tuple."
103
# The tuple and coords properties
104
tuple = property(get_coords, set_coords)
107
class LineString(GEOSGeometry):
109
#### Python 'magic' routines ####
110
def __init__(self, *args, **kwargs):
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.
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))
122
# If only one argument provided, set the coords array appropriately
123
if len(args) == 1: coords = args[0]
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.')
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.')
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])
147
raise TypeError('Invalid initialization input for LineStrings.')
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]
157
# Getting the correct initialization function
158
if kwargs.get('ring', False):
159
func = create_linearring
161
func = create_linestring
163
# If SRID was passed in with the keyword arguments
164
srid = kwargs.get('srid', None)
166
# Calling the base geometry initialization with the returned pointer
168
super(LineString, self).__init__(func(cs.ptr), srid=srid)
170
def __getitem__(self, index):
171
"Gets the point at the specified index."
172
return self._cs[index]
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
179
"Allows iteration over this LineString."
180
for i in xrange(len(self)):
184
"Returns the number of points in this LineString."
187
def _checkdim(self, dim):
188
if dim not in (2, 3): raise TypeError('Dimension mismatch.')
190
#### Sequence Properties ####
193
"Returns a tuple version of the geometry from the coordinate sequence."
194
return self._cs.tuple
197
def _listarr(self, func):
199
Internal routine that returns a sequence (list) corresponding with
200
the given function. Will return a numpy array if possible.
202
lst = [func(i) for i in xrange(len(self))]
203
if HAS_NUMPY: return array(lst) # ARRRR!
208
"Returns a numpy array for the LineString."
209
return self._listarr(self._cs.__getitem__)
213
"Returns a list or numpy array of the X variable."
214
return self._listarr(self._cs.getX)
218
"Returns a list or numpy array of the Y variable."
219
return self._listarr(self._cs.getY)
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)
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)
234
class Polygon(GEOSGeometry):
236
def __init__(self, *args, **kwargs):
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).
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))
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)))
252
raise TypeError('Must provide at list one LinearRing instance to initialize Polygon.')
254
# Getting the ext_ring and init_holes parameters from the argument list
256
init_holes = args[1:]
257
n_holes = len(init_holes)
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)
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'))
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)
277
# Getting the shell pointer address.
278
shell = geom_clone(ext_ring.ptr)
280
# Calling with the GEOS createPolygon factory.
281
super(Polygon, self).__init__(create_polygon(shell, byref(holes), c_uint(n_holes)), **kwargs)
283
def __getitem__(self, index):
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).
291
return self.exterior_ring
293
# Getting the interior ring, have to subtract 1 from the index.
294
return self.get_interior_ring(index-1)
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')
305
shell = geom_clone(ring.ptr)
307
shell = geom_clone(get_extring(self.ptr))
309
# Getting the interior rings (holes)
312
holes = get_pointer_arr(nholes)
313
for i in xrange(nholes):
315
holes[i] = geom_clone(ring.ptr)
317
holes[i] = geom_clone(get_intring(self.ptr, i))
318
holes_param = byref(holes)
322
# Getting the current pointer, replacing with the newly constructed
323
# geometry, and destroying the old geometry.
326
self._ptr = create_polygon(shell, holes_param, c_uint(nholes))
327
if srid: self.srid = srid
328
destroy_geom(prev_ptr)
331
"Iterates over each ring in the polygon."
332
for i in xrange(len(self)):
336
"Returns the number of rings in this Polygon."
337
return self.num_interior_rings + 1
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)
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
348
ring = LinearRing(param)
353
def get_interior_ring(self, ring_i):
355
Gets the interior ring at the specified index, 0 is for the first
356
interior ring, not the exterior ring.
358
self._checkindex(ring_i+1)
359
return GEOSGeometry(geom_clone(get_intring(self.ptr, ring_i)), srid=self.srid)
361
#### Polygon Properties ####
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)
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)
372
def set_ext_ring(self, ring):
373
"Sets the exterior ring of the Polygon."
376
# properties for the exterior ring/shell
377
exterior_ring = property(get_ext_ring, set_ext_ring)
378
shell = exterior_ring
382
"Gets the tuple for each ring in this Polygon."
383
return tuple([self[i].tuple for i in xrange(len(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)