~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/contrib/gis/geos/geometries.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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)