PageRenderTime 683ms CodeModel.GetById 262ms app.highlight 256ms RepoModel.GetById 156ms app.codeStats 1ms

/django/contrib/gis/gdal/geometries.py

https://code.google.com/p/mango-py/
Python | 737 lines | 704 code | 0 blank | 33 comment | 44 complexity | 57f6ba2506e280077fa215a635460121 MD5 | raw file
  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.
 42import sys
 43from binascii import a2b_hex
 44from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p
 45
 46# Getting GDAL prerequisites
 47from django.contrib.gis.gdal.base import GDALBase
 48from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
 49from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
 50from django.contrib.gis.gdal.geomtype import OGRGeomType
 51from django.contrib.gis.gdal.libgdal import GEOJSON, GDAL_VERSION
 52from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
 53
 54# Getting the ctypes prototype functions that interface w/the GDAL C library.
 55from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api
 56
 57# For recognizing geometry input.
 58from django.contrib.gis.geometry.regex import hex_regex, wkt_regex, json_regex
 59
 60# For more information, see the OGR C API source code:
 61#  http://www.gdal.org/ogr/ogr__api_8h.html
 62#
 63# The OGR_G_* routines are relevant here.
 64
 65#### OGRGeometry Class ####
 66class OGRGeometry(GDALBase):
 67    "Generally encapsulates an OGR geometry."
 68
 69    def __init__(self, geom_input, srs=None):
 70        "Initializes Geometry on either WKT or an OGR pointer as input."
 71
 72        str_instance = isinstance(geom_input, basestring)
 73
 74        # If HEX, unpack input to to a binary buffer.
 75        if str_instance and hex_regex.match(geom_input):
 76            geom_input = buffer(a2b_hex(geom_input.upper()))
 77            str_instance = False
 78
 79        # Constructing the geometry,
 80        if str_instance:
 81            # Checking if unicode
 82            if isinstance(geom_input, unicode):
 83                # Encoding to ASCII, WKT or HEX doesn't need any more.
 84                geom_input = geom_input.encode('ascii')
 85
 86            wkt_m = wkt_regex.match(geom_input)
 87            json_m = json_regex.match(geom_input)
 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'))
 92                if wkt_m.group('type').upper() == 'LINEARRING':
 93                    # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT.
 94                    #  See http://trac.osgeo.org/gdal/ticket/1992.
 95                    g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num)
 96                    capi.import_wkt(g, byref(c_char_p(wkt_m.group('wkt'))))
 97                else:
 98                    g = capi.from_wkt(byref(c_char_p(wkt_m.group('wkt'))), None, byref(c_void_p()))
 99            elif json_m:
100                if GEOJSON:
101                    g = capi.from_json(geom_input)
102                else:
103                    raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.')
104            else:
105                # Seeing if the input is a valid short-hand string
106                # (e.g., 'Point', 'POLYGON').
107                ogr_t = OGRGeomType(geom_input)
108                g = capi.create_geom(OGRGeomType(geom_input).num)
109        elif isinstance(geom_input, buffer):
110            # WKB was passed in
111            g = capi.from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input))
112        elif isinstance(geom_input, OGRGeomType):
113            # OGRGeomType was passed in, an empty geometry will be created.
114            g = capi.create_geom(geom_input.num)
115        elif isinstance(geom_input, self.ptr_type):
116            # OGR pointer (c_void_p) was the input.
117            g = geom_input
118        else:
119            raise OGRException('Invalid input type for OGR Geometry construction: %s' % type(geom_input))
120
121        # Now checking the Geometry pointer before finishing initialization
122        # by setting the pointer for the object.
123        if not g:
124            raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input))
125        self.ptr = g
126
127        # Assigning the SpatialReference object to the geometry, if valid.
128        if bool(srs): self.srs = srs
129
130        # Setting the class depending upon the OGR Geometry Type
131        self.__class__ = GEO_CLASSES[self.geom_type.num]
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
153    @classmethod
154    def from_bbox(cls, bbox):
155        "Constructs a Polygon from a bounding box (4-tuple)."
156        x0, y0, x1, y1 = bbox
157        return OGRGeometry( 'POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))' %  (
158                x0, y0, x0, y1, x1, y1, x1, y0, x0, y0) )
159
160    ### Geometry set-like operations ###
161    # g = g1 | g2
162    def __or__(self, other):
163        "Returns the union of the two geometries."
164        return self.union(other)
165
166    # g = g1 & g2
167    def __and__(self, other):
168        "Returns the intersection of this Geometry and the other."
169        return self.intersection(other)
170
171    # g = g1 - g2
172    def __sub__(self, other):
173        "Return the difference this Geometry and the other."
174        return self.difference(other)
175
176    # g = g1 ^ g2
177    def __xor__(self, other):
178        "Return the symmetric difference of this Geometry and the other."
179        return self.sym_difference(other)
180
181    def __eq__(self, other):
182        "Is this Geometry equal to the other?"
183        if isinstance(other, OGRGeometry):
184            return self.equals(other)
185        else:
186            return False
187
188    def __ne__(self, other):
189        "Tests for inequality."
190        return not (self == other)
191
192    def __str__(self):
193        "WKT is used for the string representation."
194        return self.wkt
195
196    #### Geometry Properties ####
197    @property
198    def dimension(self):
199        "Returns 0 for points, 1 for lines, and 2 for surfaces."
200        return capi.get_dims(self.ptr)
201
202    def _get_coord_dim(self):
203        "Returns the coordinate dimension of the Geometry."
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)
221
222    @property
223    def geom_count(self):
224        "The number of elements in this Geometry."
225        return capi.get_geom_count(self.ptr)
226
227    @property
228    def point_count(self):
229        "Returns the number of Points in this Geometry."
230        return capi.get_point_count(self.ptr)
231
232    @property
233    def num_points(self):
234        "Alias for `point_count` (same name method in GEOS API.)"
235        return self.point_count
236
237    @property
238    def num_coords(self):
239        "Alais for `point_count`."
240        return self.point_count
241
242    @property
243    def geom_type(self):
244        "Returns the Type for this Geometry."
245        return OGRGeomType(capi.get_geom_type(self.ptr))
246
247    @property
248    def geom_name(self):
249        "Returns the Name of this Geometry."
250        return capi.get_geom_name(self.ptr)
251
252    @property
253    def area(self):
254        "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
255        return capi.get_area(self.ptr)
256
257    @property
258    def envelope(self):
259        "Returns the envelope for this Geometry."
260        # TODO: Fix Envelope() for Point geometries.
261        return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope())))
262
263    @property
264    def extent(self):
265        "Returns the envelope as a 4-tuple, instead of as an Envelope object."
266        return self.envelope.tuple
267
268    #### SpatialReference-related Properties ####
269
270    # The SRS property
271    def _get_srs(self):
272        "Returns the Spatial Reference for this Geometry."
273        try:
274            srs_ptr = capi.get_geom_srs(self.ptr)
275            return SpatialReference(srs_api.clone_srs(srs_ptr))
276        except SRSException:
277            return None
278
279    def _set_srs(self, srs):
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.
285        if isinstance(srs, SpatialReference):
286            srs_ptr = srs.ptr
287        elif isinstance(srs, (int, long, basestring)):
288            sr = SpatialReference(srs)
289            srs_ptr = sr.ptr
290        else:
291            raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
292        capi.assign_srs(self.ptr, srs_ptr)
293
294    srs = property(_get_srs, _set_srs)
295
296    # The SRID property
297    def _get_srid(self):
298        srs = self.srs
299        if srs: return srs.srid
300        return None
301
302    def _set_srid(self, srid):
303        if isinstance(srid, (int, long)):
304            self.srs = srid
305        else:
306            raise TypeError('SRID must be set with an integer.')
307
308    srid = property(_get_srid, _set_srid)
309
310    #### Output Methods ####
311    @property
312    def geos(self):
313        "Returns a GEOSGeometry object from this OGRGeometry."
314        from django.contrib.gis.geos import GEOSGeometry
315        return GEOSGeometry(self.wkb, self.srid)
316
317    @property
318    def gml(self):
319        "Returns the GML representation of the Geometry."
320        return capi.to_gml(self.ptr)
321
322    @property
323    def hex(self):
324        "Returns the hexadecimal representation of the WKB (a string)."
325        return str(self.wkb).encode('hex').upper()
326        #return b2a_hex(self.wkb).upper()
327
328    @property
329    def json(self):
330        """
331        Returns the GeoJSON representation of this Geometry (requires
332        GDAL 1.5+).
333        """
334        if GEOJSON:
335            return capi.to_json(self.ptr)
336        else:
337            raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.')
338    geojson = json
339
340    @property
341    def kml(self):
342        "Returns the KML representation of the Geometry."
343        if GEOJSON:
344            return capi.to_kml(self.ptr, None)
345        else:
346            raise NotImplementedError('KML output only supported on GDAL 1.5+.')
347
348    @property
349    def wkb_size(self):
350        "Returns the size of the WKB buffer."
351        return capi.get_wkbsize(self.ptr)
352
353    @property
354    def wkb(self):
355        "Returns the WKB representation of the Geometry."
356        if sys.byteorder == 'little':
357            byteorder = 1 # wkbNDR (from ogr_core.h)
358        else:
359            byteorder = 0 # wkbXDR
360        sz = self.wkb_size
361        # Creating the unsigned character buffer, and passing it in by reference.
362        buf = (c_ubyte * sz)()
363        wkb = capi.to_wkb(self.ptr, byteorder, byref(buf))
364        # Returning a buffer of the string at the pointer.
365        return buffer(string_at(buf, sz))
366
367    @property
368    def wkt(self):
369        "Returns the WKT representation of the Geometry."
370        return capi.to_wkt(self.ptr, byref(c_char_p()))
371
372    @property
373    def ewkt(self):
374        "Returns the EWKT representation of the Geometry."
375        srs = self.srs
376        if srs and srs.srid:
377            return 'SRID=%s;%s' % (srs.srid, self.wkt)
378        else:
379            return self.wkt
380
381    #### Geometry Methods ####
382    def clone(self):
383        "Clones this OGR Geometry."
384        return OGRGeometry(capi.clone_geom(self.ptr), self.srs)
385
386    def close_rings(self):
387        """
388        If there are any rings within this geometry that have not been
389        closed, this routine will do so by adding the starting point at the
390        end.
391        """
392        # Closing the open rings.
393        capi.geom_close_rings(self.ptr)
394
395    def transform(self, coord_trans, clone=False):
396        """
397        Transforms this geometry to a different spatial reference system.
398        May take a CoordTransform object, a SpatialReference object, string
399        WKT or PROJ.4, and/or an integer SRID.  By default nothing is returned
400        and the geometry is transformed in-place.  However, if the `clone`
401        keyword is set, then a transformed clone of this geometry will be
402        returned.
403        """
404        if clone:
405            klone = self.clone()
406            klone.transform(coord_trans)
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.
419        if isinstance(coord_trans, CoordTransform):
420            capi.geom_transform(self.ptr, coord_trans.ptr)
421        elif isinstance(coord_trans, SpatialReference):
422            capi.geom_transform_to(self.ptr, coord_trans.ptr)
423        elif isinstance(coord_trans, (int, long, basestring)):
424            sr = SpatialReference(coord_trans)
425            capi.geom_transform_to(self.ptr, sr.ptr)
426        else:
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
443
444    def transform_to(self, srs):
445        "For backwards-compatibility."
446        self.transform(srs)
447
448    #### Topology Methods ####
449    def _topology(self, func, other):
450        """A generalized function for topology operations, takes a GDAL function and
451        the other geometry to perform the operation on."""
452        if not isinstance(other, OGRGeometry):
453            raise TypeError('Must use another OGRGeometry object for topology operations!')
454
455        # Returning the output of the given function with the other geometry's
456        # pointer.
457        return func(self.ptr, other.ptr)
458
459    def intersects(self, other):
460        "Returns True if this geometry intersects with the other."
461        return self._topology(capi.ogr_intersects, other)
462
463    def equals(self, other):
464        "Returns True if this geometry is equivalent to the other."
465        return self._topology(capi.ogr_equals, other)
466
467    def disjoint(self, other):
468        "Returns True if this geometry and the other are spatially disjoint."
469        return self._topology(capi.ogr_disjoint, other)
470
471    def touches(self, other):
472        "Returns True if this geometry touches the other."
473        return self._topology(capi.ogr_touches, other)
474
475    def crosses(self, other):
476        "Returns True if this geometry crosses the other."
477        return self._topology(capi.ogr_crosses, other)
478
479    def within(self, other):
480        "Returns True if this geometry is within the other."
481        return self._topology(capi.ogr_within, other)
482
483    def contains(self, other):
484        "Returns True if this geometry contains the other."
485        return self._topology(capi.ogr_contains, other)
486
487    def overlaps(self, other):
488        "Returns True if this geometry overlaps the other."
489        return self._topology(capi.ogr_overlaps, other)
490
491    #### Geometry-generation Methods ####
492    def _geomgen(self, gen_func, other=None):
493        "A helper routine for the OGR routines that generate geometries."
494        if isinstance(other, OGRGeometry):
495            return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs)
496        else:
497            return OGRGeometry(gen_func(self.ptr), self.srs)
498
499    @property
500    def boundary(self):
501        "Returns the boundary of this geometry."
502        return self._geomgen(capi.get_boundary)
503
504    @property
505    def convex_hull(self):
506        """
507        Returns the smallest convex Polygon that contains all the points in
508        this Geometry.
509        """
510        return self._geomgen(capi.geom_convex_hull)
511
512    def difference(self, other):
513        """
514        Returns a new geometry consisting of the region which is the difference
515        of this geometry and the other.
516        """
517        return self._geomgen(capi.geom_diff, other)
518
519    def intersection(self, other):
520        """
521        Returns a new geometry consisting of the region of intersection of this
522        geometry and the other.
523        """
524        return self._geomgen(capi.geom_intersection, other)
525
526    def sym_difference(self, other):
527        """
528        Returns a new geometry which is the symmetric difference of this
529        geometry and the other.
530        """
531        return self._geomgen(capi.geom_sym_diff, other)
532
533    def union(self, other):
534        """
535        Returns a new geometry consisting of the region which is the union of
536        this geometry and the other.
537        """
538        return self._geomgen(capi.geom_union, other)
539
540# The subclasses for OGR Geometry.
541class Point(OGRGeometry):
542
543    @property
544    def x(self):
545        "Returns the X coordinate for this Point."
546        return capi.getx(self.ptr, 0)
547
548    @property
549    def y(self):
550        "Returns the Y coordinate for this Point."
551        return capi.gety(self.ptr, 0)
552
553    @property
554    def z(self):
555        "Returns the Z coordinate for this Point."
556        if self.coord_dim == 3:
557            return capi.getz(self.ptr, 0)
558
559    @property
560    def tuple(self):
561        "Returns the tuple of this point."
562        if self.coord_dim == 2:
563            return (self.x, self.y)
564        elif self.coord_dim == 3:
565            return (self.x, self.y, self.z)
566    coords = tuple
567
568class LineString(OGRGeometry):
569
570    def __getitem__(self, index):
571        "Returns the Point at the given index."
572        if index >= 0 and index < self.point_count:
573            x, y, z = c_double(), c_double(), c_double()
574            capi.get_point(self.ptr, index, byref(x), byref(y), byref(z))
575            dim = self.coord_dim
576            if dim == 1:
577                return (x.value,)
578            elif dim == 2:
579                return (x.value, y.value)
580            elif dim == 3:
581                return (x.value, y.value, z.value)
582        else:
583            raise OGRIndexError('index out of range: %s' % str(index))
584
585    def __iter__(self):
586        "Iterates over each point in the LineString."
587        for i in xrange(self.point_count):
588            yield self[i]
589
590    def __len__(self):
591        "The length returns the number of points in the LineString."
592        return self.point_count
593
594    @property
595    def tuple(self):
596        "Returns the tuple representation of this LineString."
597        return tuple([self[i] for i in xrange(len(self))])
598    coords = tuple
599
600    def _listarr(self, func):
601        """
602        Internal routine that returns a sequence (list) corresponding with
603        the given function.
604        """
605        return [func(self.ptr, i) for i in xrange(len(self))]
606
607    @property
608    def x(self):
609        "Returns the X coordinates in a list."
610        return self._listarr(capi.getx)
611
612    @property
613    def y(self):
614        "Returns the Y coordinates in a list."
615        return self._listarr(capi.gety)
616
617    @property
618    def z(self):
619        "Returns the Z coordinates in a list."
620        if self.coord_dim == 3:
621            return self._listarr(capi.getz)
622
623# LinearRings are used in Polygons.
624class LinearRing(LineString): pass
625
626class Polygon(OGRGeometry):
627
628    def __len__(self):
629        "The number of interior rings in this Polygon."
630        return self.geom_count
631
632    def __iter__(self):
633        "Iterates through each ring in the Polygon."
634        for i in xrange(self.geom_count):
635            yield self[i]
636
637    def __getitem__(self, index):
638        "Gets the ring at the specified index."
639        if index < 0 or index >= self.geom_count:
640            raise OGRIndexError('index out of range: %s' % index)
641        else:
642            return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
643
644    # Polygon Properties
645    @property
646    def shell(self):
647        "Returns the shell of this Polygon."
648        return self[0] # First ring is the shell
649    exterior_ring = shell
650
651    @property
652    def tuple(self):
653        "Returns a tuple of LinearRing coordinate tuples."
654        return tuple([self[i].tuple for i in xrange(self.geom_count)])
655    coords = tuple
656
657    @property
658    def point_count(self):
659        "The number of Points in this Polygon."
660        # Summing up the number of points in each ring of the Polygon.
661        return sum([self[i].point_count for i in xrange(self.geom_count)])
662
663    @property
664    def centroid(self):
665        "Returns the centroid (a Point) of this Polygon."
666        # The centroid is a Point, create a geometry for this.
667        p = OGRGeometry(OGRGeomType('Point'))
668        capi.get_centroid(self.ptr, p.ptr)
669        return p
670
671# Geometry Collection base class.
672class GeometryCollection(OGRGeometry):
673    "The Geometry Collection class."
674
675    def __getitem__(self, index):
676        "Gets the Geometry at the specified index."
677        if index < 0 or index >= self.geom_count:
678            raise OGRIndexError('index out of range: %s' % index)
679        else:
680            return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
681
682    def __iter__(self):
683        "Iterates over each Geometry."
684        for i in xrange(self.geom_count):
685            yield self[i]
686
687    def __len__(self):
688        "The number of geometries in this Geometry Collection."
689        return self.geom_count
690
691    def add(self, geom):
692        "Add the geometry to this Geometry Collection."
693        if isinstance(geom, OGRGeometry):
694            if isinstance(geom, self.__class__):
695                for g in geom: capi.add_geom(self.ptr, g.ptr)
696            else:
697                capi.add_geom(self.ptr, geom.ptr)
698        elif isinstance(geom, basestring):
699            tmp = OGRGeometry(geom)
700            capi.add_geom(self.ptr, tmp.ptr)
701        else:
702            raise OGRException('Must add an OGRGeometry.')
703
704    @property
705    def point_count(self):
706        "The number of Points in this Geometry Collection."
707        # Summing up the number of points in each geometry in this collection
708        return sum([self[i].point_count for i in xrange(self.geom_count)])
709
710    @property
711    def tuple(self):
712        "Returns a tuple representation of this Geometry Collection."
713        return tuple([self[i].tuple for i in xrange(self.geom_count)])
714    coords = tuple
715
716# Multiple Geometry types.
717class MultiPoint(GeometryCollection): pass
718class MultiLineString(GeometryCollection): pass
719class MultiPolygon(GeometryCollection): pass
720
721# Class mapping dictionary (using the OGRwkbGeometryType as the key)
722GEO_CLASSES = {1 : Point,
723               2 : LineString,
724               3 : Polygon,
725               4 : MultiPoint,
726               5 : MultiLineString,
727               6 : MultiPolygon,
728               7 : GeometryCollection,
729               101: LinearRing,
730               1 + OGRGeomType.wkb25bit : Point,
731               2 + OGRGeomType.wkb25bit : LineString,
732               3 + OGRGeomType.wkb25bit : Polygon,
733               4 + OGRGeomType.wkb25bit : MultiPoint,
734               5 + OGRGeomType.wkb25bit : MultiLineString,
735               6 + OGRGeomType.wkb25bit : MultiPolygon,
736               7 + OGRGeomType.wkb25bit : GeometryCollection,
737               }