app/django/contrib/gis/gdal/geometries.py
changeset 323 ff1a9aa48cfd
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     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.
       
    42 import re, sys
       
    43 from binascii import a2b_hex
       
    44 from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p
       
    45 from types import UnicodeType
       
    46 
       
    47 # Getting GDAL prerequisites
       
    48 from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
       
    49 from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
       
    50 from django.contrib.gis.gdal.geomtype import OGRGeomType
       
    51 from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
       
    52 
       
    53 # Getting the ctypes prototype functions that interface w/the GDAL C library.
       
    54 from django.contrib.gis.gdal.prototypes.geom import *
       
    55 from django.contrib.gis.gdal.prototypes.srs import clone_srs
       
    56 
       
    57 # For more information, see the OGR C API source code:
       
    58 #  http://www.gdal.org/ogr/ogr__api_8h.html
       
    59 #
       
    60 # The OGR_G_* routines are relevant here.
       
    61 
       
    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\w,\-\.\"\'\:\[\]]+\}$')
       
    66 
       
    67 #### OGRGeometry Class ####
       
    68 class OGRGeometry(object):
       
    69     "Generally encapsulates an OGR geometry."
       
    70 
       
    71     def __init__(self, geom_input, srs=None):
       
    72         "Initializes Geometry on either WKT or an OGR pointer as input."
       
    73 
       
    74         self._ptr = c_void_p(None) # Initially NULL
       
    75         str_instance = isinstance(geom_input, basestring)
       
    76 
       
    77         # If HEX, unpack input to to a binary buffer.
       
    78         if str_instance and hex_regex.match(geom_input):
       
    79             geom_input = buffer(a2b_hex(geom_input.upper()))
       
    80             str_instance = False
       
    81 
       
    82         # Constructing the geometry, 
       
    83         if str_instance:
       
    84             # Checking if unicode
       
    85             if isinstance(geom_input, UnicodeType):
       
    86                 # Encoding to ASCII, WKT or HEX doesn't need any more.
       
    87                 geo_input = geo_input.encode('ascii')
       
    88 
       
    89             wkt_m = wkt_regex.match(geom_input)
       
    90             json_m = json_regex.match(geom_input)
       
    91             if wkt_m:
       
    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 = create_geom(OGRGeomType(wkt_m.group('type')).num)
       
    96                     import_wkt(g, byref(c_char_p(geom_input)))
       
    97                 else:
       
    98                     g = from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p()))
       
    99             elif json_m:
       
   100                 if GEOJSON:
       
   101                     g = 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 = create_geom(OGRGeomType(geom_input).num)
       
   109         elif isinstance(geom_input, buffer):
       
   110             # WKB was passed in
       
   111             g = 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 = create_geom(geom_input.num)
       
   115         elif isinstance(geom_input, c_void_p):
       
   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: destroy_geom(self._ptr)
       
   136 
       
   137     ### Geometry set-like operations ###
       
   138     # g = g1 | g2
       
   139     def __or__(self, other):
       
   140         "Returns the union of the two geometries."
       
   141         return self.union(other)
       
   142 
       
   143     # g = g1 & g2
       
   144     def __and__(self, other):
       
   145         "Returns the intersection of this Geometry and the other."
       
   146         return self.intersection(other)
       
   147 
       
   148     # g = g1 - g2
       
   149     def __sub__(self, other):
       
   150         "Return the difference this Geometry and the other."
       
   151         return self.difference(other)
       
   152 
       
   153     # g = g1 ^ g2
       
   154     def __xor__(self, other):
       
   155         "Return the symmetric difference of this Geometry and the other."
       
   156         return self.sym_difference(other)
       
   157 
       
   158     def __eq__(self, other):
       
   159         "Is this Geometry equal to the other?"
       
   160         return self.equals(other)
       
   161 
       
   162     def __ne__(self, other):
       
   163         "Tests for inequality."
       
   164         return not self.equals(other)
       
   165 
       
   166     def __str__(self):
       
   167         "WKT is used for the string representation."
       
   168         return self.wkt
       
   169 
       
   170     #### Geometry Properties ####
       
   171     @property
       
   172     def dimension(self):
       
   173         "Returns 0 for points, 1 for lines, and 2 for surfaces."
       
   174         return get_dims(self._ptr)
       
   175 
       
   176     @property
       
   177     def coord_dim(self):
       
   178         "Returns the coordinate dimension of the Geometry."
       
   179         return get_coord_dims(self._ptr)
       
   180 
       
   181     @property
       
   182     def geom_count(self):
       
   183         "The number of elements in this Geometry."
       
   184         return get_geom_count(self._ptr)
       
   185 
       
   186     @property
       
   187     def point_count(self):
       
   188         "Returns the number of Points in this Geometry."
       
   189         return get_point_count(self._ptr)
       
   190 
       
   191     @property
       
   192     def num_points(self):
       
   193         "Alias for `point_count` (same name method in GEOS API.)"
       
   194         return self.point_count
       
   195 
       
   196     @property
       
   197     def num_coords(self):
       
   198         "Alais for `point_count`."
       
   199         return self.point_count
       
   200 
       
   201     @property
       
   202     def geom_type(self):
       
   203         "Returns the Type for this Geometry."
       
   204         try:
       
   205             return OGRGeomType(get_geom_type(self._ptr))
       
   206         except OGRException:
       
   207             # VRT datasources return an invalid geometry type
       
   208             # number, but a valid name -- we'll try that instead.
       
   209             # See: http://trac.osgeo.org/gdal/ticket/2491
       
   210             return OGRGeomType(get_geom_name(self._ptr))
       
   211 
       
   212     @property
       
   213     def geom_name(self):
       
   214         "Returns the Name of this Geometry."
       
   215         return get_geom_name(self._ptr)
       
   216 
       
   217     @property
       
   218     def area(self):
       
   219         "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
       
   220         return get_area(self._ptr)
       
   221 
       
   222     @property
       
   223     def envelope(self):
       
   224         "Returns the envelope for this Geometry."
       
   225         # TODO: Fix Envelope() for Point geometries.
       
   226         return Envelope(get_envelope(self._ptr, byref(OGREnvelope())))
       
   227 
       
   228     @property
       
   229     def extent(self):
       
   230         "Returns the envelope as a 4-tuple, instead of as an Envelope object."
       
   231         return self.envelope.tuple
       
   232 
       
   233     #### SpatialReference-related Properties ####
       
   234     
       
   235     # The SRS property
       
   236     def get_srs(self):
       
   237         "Returns the Spatial Reference for this Geometry."
       
   238         try:
       
   239             srs_ptr = get_geom_srs(self._ptr)
       
   240             return SpatialReference(clone_srs(srs_ptr))
       
   241         except SRSException:
       
   242             return None
       
   243 
       
   244     def set_srs(self, srs):
       
   245         "Sets the SpatialReference for this geometry."
       
   246         if isinstance(srs, SpatialReference):
       
   247             srs_ptr = clone_srs(srs._ptr)
       
   248         elif isinstance(srs, (int, long, basestring)):
       
   249             sr = SpatialReference(srs)
       
   250             srs_ptr = clone_srs(sr._ptr)
       
   251         else:
       
   252             raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
       
   253         assign_srs(self._ptr, srs_ptr)
       
   254 
       
   255     srs = property(get_srs, set_srs)
       
   256 
       
   257     # The SRID property
       
   258     def get_srid(self):
       
   259         if self.srs: return self.srs.srid
       
   260         else: return None
       
   261 
       
   262     def set_srid(self, srid):
       
   263         if isinstance(srid, (int, long)):
       
   264             self.srs = srid
       
   265         else:
       
   266             raise TypeError('SRID must be set with an integer.')
       
   267 
       
   268     srid = property(get_srid, set_srid)
       
   269 
       
   270     #### Output Methods ####
       
   271     @property
       
   272     def geos(self):
       
   273         "Returns a GEOSGeometry object from this OGRGeometry."
       
   274         from django.contrib.gis.geos import GEOSGeometry
       
   275         return GEOSGeometry(self.wkb, self.srid)
       
   276 
       
   277     @property
       
   278     def gml(self):
       
   279         "Returns the GML representation of the Geometry."
       
   280         return to_gml(self._ptr)
       
   281 
       
   282     @property
       
   283     def hex(self):
       
   284         "Returns the hexadecimal representation of the WKB (a string)."
       
   285         return str(self.wkb).encode('hex').upper()
       
   286         #return b2a_hex(self.wkb).upper()
       
   287 
       
   288     @property
       
   289     def json(self):
       
   290         if GEOJSON: 
       
   291             return to_json(self._ptr)
       
   292         else:
       
   293             raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.')
       
   294     geojson = json
       
   295 
       
   296     @property
       
   297     def wkb_size(self):
       
   298         "Returns the size of the WKB buffer."
       
   299         return get_wkbsize(self._ptr)
       
   300 
       
   301     @property
       
   302     def wkb(self):
       
   303         "Returns the WKB representation of the Geometry."
       
   304         if sys.byteorder == 'little':
       
   305             byteorder = 1 # wkbNDR (from ogr_core.h)
       
   306         else:
       
   307             byteorder = 0 # wkbXDR
       
   308         sz = self.wkb_size
       
   309         # Creating the unsigned character buffer, and passing it in by reference.
       
   310         buf = (c_ubyte * sz)()
       
   311         wkb = to_wkb(self._ptr, byteorder, byref(buf))
       
   312         # Returning a buffer of the string at the pointer.
       
   313         return buffer(string_at(buf, sz))
       
   314 
       
   315     @property
       
   316     def wkt(self):
       
   317         "Returns the WKT representation of the Geometry."
       
   318         return to_wkt(self._ptr, byref(c_char_p()))
       
   319     
       
   320     #### Geometry Methods ####
       
   321     def clone(self):
       
   322         "Clones this OGR Geometry."
       
   323         return OGRGeometry(clone_geom(self._ptr), self.srs)
       
   324 
       
   325     def close_rings(self):
       
   326         """
       
   327         If there are any rings within this geometry that have not been
       
   328         closed, this routine will do so by adding the starting point at the
       
   329         end.
       
   330         """
       
   331         # Closing the open rings.
       
   332         geom_close_rings(self._ptr)
       
   333 
       
   334     def transform(self, coord_trans, clone=False):
       
   335         """
       
   336         Transforms this geometry to a different spatial reference system.
       
   337         May take a CoordTransform object, a SpatialReference object, string
       
   338         WKT or PROJ.4, and/or an integer SRID.  By default nothing is returned
       
   339         and the geometry is transformed in-place.  However, if the `clone`
       
   340         keyword is set, then a transformed clone of this geometry will be
       
   341         returned.
       
   342         """
       
   343         if clone:
       
   344             klone = self.clone()
       
   345             klone.transform(coord_trans)
       
   346             return klone
       
   347         if isinstance(coord_trans, CoordTransform):
       
   348             geom_transform(self._ptr, coord_trans._ptr)
       
   349         elif isinstance(coord_trans, SpatialReference):
       
   350             geom_transform_to(self._ptr, coord_trans._ptr)
       
   351         elif isinstance(coord_trans, (int, long, basestring)):
       
   352             sr = SpatialReference(coord_trans)
       
   353             geom_transform_to(self._ptr, sr._ptr)
       
   354         else:
       
   355             raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.')
       
   356 
       
   357     def transform_to(self, srs):
       
   358         "For backwards-compatibility."
       
   359         self.transform(srs)
       
   360 
       
   361     #### Topology Methods ####
       
   362     def _topology(self, func, other):
       
   363         """A generalized function for topology operations, takes a GDAL function and
       
   364         the other geometry to perform the operation on."""
       
   365         if not isinstance(other, OGRGeometry):
       
   366             raise TypeError('Must use another OGRGeometry object for topology operations!')
       
   367 
       
   368         # Returning the output of the given function with the other geometry's
       
   369         # pointer.
       
   370         return func(self._ptr, other._ptr)
       
   371 
       
   372     def intersects(self, other):
       
   373         "Returns True if this geometry intersects with the other."
       
   374         return self._topology(ogr_intersects, other)
       
   375     
       
   376     def equals(self, other):
       
   377         "Returns True if this geometry is equivalent to the other."
       
   378         return self._topology(ogr_equals, other)
       
   379 
       
   380     def disjoint(self, other):
       
   381         "Returns True if this geometry and the other are spatially disjoint."
       
   382         return self._topology(ogr_disjoint, other)
       
   383 
       
   384     def touches(self, other):
       
   385         "Returns True if this geometry touches the other."
       
   386         return self._topology(ogr_touches, other)
       
   387 
       
   388     def crosses(self, other):
       
   389         "Returns True if this geometry crosses the other."
       
   390         return self._topology(ogr_crosses, other)
       
   391 
       
   392     def within(self, other):
       
   393         "Returns True if this geometry is within the other."
       
   394         return self._topology(ogr_within, other)
       
   395 
       
   396     def contains(self, other):
       
   397         "Returns True if this geometry contains the other."
       
   398         return self._topology(ogr_contains, other)
       
   399 
       
   400     def overlaps(self, other):
       
   401         "Returns True if this geometry overlaps the other."
       
   402         return self._topology(ogr_overlaps, other)
       
   403 
       
   404     #### Geometry-generation Methods ####
       
   405     def _geomgen(self, gen_func, other=None):
       
   406         "A helper routine for the OGR routines that generate geometries."
       
   407         if isinstance(other, OGRGeometry):
       
   408             return OGRGeometry(gen_func(self._ptr, other._ptr), self.srs)
       
   409         else:
       
   410             return OGRGeometry(gen_func(self._ptr), self.srs)
       
   411 
       
   412     @property
       
   413     def boundary(self):
       
   414         "Returns the boundary of this geometry."
       
   415         return self._geomgen(get_boundary)
       
   416 
       
   417     @property
       
   418     def convex_hull(self):
       
   419         """
       
   420         Returns the smallest convex Polygon that contains all the points in 
       
   421         this Geometry.
       
   422         """
       
   423         return self._geomgen(geom_convex_hull)
       
   424 
       
   425     def difference(self, other):
       
   426         """
       
   427         Returns a new geometry consisting of the region which is the difference
       
   428         of this geometry and the other.
       
   429         """
       
   430         return self._geomgen(geom_diff, other)
       
   431 
       
   432     def intersection(self, other):
       
   433         """
       
   434         Returns a new geometry consisting of the region of intersection of this
       
   435         geometry and the other.
       
   436         """
       
   437         return self._geomgen(geom_intersection, other)
       
   438 
       
   439     def sym_difference(self, other):
       
   440         """                                                                                                                                                
       
   441         Returns a new geometry which is the symmetric difference of this
       
   442         geometry and the other.
       
   443         """
       
   444         return self._geomgen(geom_sym_diff, other)
       
   445 
       
   446     def union(self, other):
       
   447         """
       
   448         Returns a new geometry consisting of the region which is the union of
       
   449         this geometry and the other.
       
   450         """
       
   451         return self._geomgen(geom_union, other)
       
   452 
       
   453 # The subclasses for OGR Geometry.
       
   454 class Point(OGRGeometry):
       
   455 
       
   456     @property
       
   457     def x(self):
       
   458         "Returns the X coordinate for this Point."
       
   459         return getx(self._ptr, 0)
       
   460 
       
   461     @property
       
   462     def y(self):
       
   463         "Returns the Y coordinate for this Point."
       
   464         return gety(self._ptr, 0)
       
   465 
       
   466     @property
       
   467     def z(self):
       
   468         "Returns the Z coordinate for this Point."
       
   469         if self.coord_dim == 3:
       
   470             return getz(self._ptr, 0)
       
   471 
       
   472     @property
       
   473     def tuple(self):
       
   474         "Returns the tuple of this point."
       
   475         if self.coord_dim == 2:
       
   476             return (self.x, self.y)
       
   477         elif self.coord_dim == 3:
       
   478             return (self.x, self.y, self.z)
       
   479     coords = tuple
       
   480 
       
   481 class LineString(OGRGeometry):
       
   482 
       
   483     def __getitem__(self, index):
       
   484         "Returns the Point at the given index."
       
   485         if index >= 0 and index < self.point_count:
       
   486             x, y, z = c_double(), c_double(), c_double()
       
   487             get_point(self._ptr, index, byref(x), byref(y), byref(z))
       
   488             dim = self.coord_dim
       
   489             if dim == 1:
       
   490                 return (x.value,)
       
   491             elif dim == 2:
       
   492                 return (x.value, y.value)
       
   493             elif dim == 3:
       
   494                 return (x.value, y.value, z.value)
       
   495         else:
       
   496             raise OGRIndexError('index out of range: %s' % str(index))
       
   497 
       
   498     def __iter__(self):
       
   499         "Iterates over each point in the LineString."
       
   500         for i in xrange(self.point_count):
       
   501             yield self[i]
       
   502 
       
   503     def __len__(self):
       
   504         "The length returns the number of points in the LineString."
       
   505         return self.point_count
       
   506 
       
   507     @property
       
   508     def tuple(self):
       
   509         "Returns the tuple representation of this LineString."
       
   510         return tuple([self[i] for i in xrange(len(self))])
       
   511     coords = tuple
       
   512 
       
   513     def _listarr(self, func):
       
   514         """
       
   515         Internal routine that returns a sequence (list) corresponding with
       
   516         the given function.
       
   517         """
       
   518         return [func(self._ptr, i) for i in xrange(len(self))]
       
   519 
       
   520     @property
       
   521     def x(self):
       
   522         "Returns the X coordinates in a list."
       
   523         return self._listarr(getx)
       
   524 
       
   525     @property
       
   526     def y(self):
       
   527         "Returns the Y coordinates in a list."
       
   528         return self._listarr(gety)
       
   529     
       
   530     @property
       
   531     def z(self):
       
   532         "Returns the Z coordinates in a list."
       
   533         if self.coord_dim == 3:
       
   534             return self._listarr(getz)
       
   535 
       
   536 # LinearRings are used in Polygons.
       
   537 class LinearRing(LineString): pass
       
   538 
       
   539 class Polygon(OGRGeometry):
       
   540 
       
   541     def __len__(self):
       
   542         "The number of interior rings in this Polygon."
       
   543         return self.geom_count
       
   544 
       
   545     def __iter__(self):
       
   546         "Iterates through each ring in the Polygon."
       
   547         for i in xrange(self.geom_count):
       
   548             yield self[i]
       
   549 
       
   550     def __getitem__(self, index):
       
   551         "Gets the ring at the specified index."
       
   552         if index < 0 or index >= self.geom_count:
       
   553             raise OGRIndexError('index out of range: %s' % index)
       
   554         else:
       
   555             return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs)
       
   556 
       
   557     # Polygon Properties
       
   558     @property
       
   559     def shell(self):
       
   560         "Returns the shell of this Polygon."
       
   561         return self[0] # First ring is the shell
       
   562     exterior_ring = shell
       
   563 
       
   564     @property
       
   565     def tuple(self):
       
   566         "Returns a tuple of LinearRing coordinate tuples."
       
   567         return tuple([self[i].tuple for i in xrange(self.geom_count)])
       
   568     coords = tuple
       
   569 
       
   570     @property
       
   571     def point_count(self):
       
   572         "The number of Points in this Polygon."
       
   573         # Summing up the number of points in each ring of the Polygon.
       
   574         return sum([self[i].point_count for i in xrange(self.geom_count)])
       
   575 
       
   576     @property
       
   577     def centroid(self):
       
   578         "Returns the centroid (a Point) of this Polygon."
       
   579         # The centroid is a Point, create a geometry for this.
       
   580         p = OGRGeometry(OGRGeomType('Point'))
       
   581         get_centroid(self._ptr, p._ptr)
       
   582         return p
       
   583 
       
   584 # Geometry Collection base class.
       
   585 class GeometryCollection(OGRGeometry):
       
   586     "The Geometry Collection class."
       
   587 
       
   588     def __getitem__(self, index):
       
   589         "Gets the Geometry at the specified index."
       
   590         if index < 0 or index >= self.geom_count:
       
   591             raise OGRIndexError('index out of range: %s' % index)
       
   592         else:
       
   593             return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs)
       
   594         
       
   595     def __iter__(self):
       
   596         "Iterates over each Geometry."
       
   597         for i in xrange(self.geom_count):
       
   598             yield self[i]
       
   599 
       
   600     def __len__(self):
       
   601         "The number of geometries in this Geometry Collection."
       
   602         return self.geom_count
       
   603 
       
   604     def add(self, geom):
       
   605         "Add the geometry to this Geometry Collection."
       
   606         if isinstance(geom, OGRGeometry):
       
   607             if isinstance(geom, self.__class__):
       
   608                 for g in geom: add_geom(self._ptr, g._ptr)
       
   609             else:
       
   610                 add_geom(self._ptr, geom._ptr)
       
   611         elif isinstance(geom, basestring):
       
   612             tmp = OGRGeometry(geom)
       
   613             add_geom(self._ptr, tmp._ptr)
       
   614         else:
       
   615             raise OGRException('Must add an OGRGeometry.')
       
   616 
       
   617     @property
       
   618     def point_count(self):
       
   619         "The number of Points in this Geometry Collection."
       
   620         # Summing up the number of points in each geometry in this collection
       
   621         return sum([self[i].point_count for i in xrange(self.geom_count)])
       
   622 
       
   623     @property
       
   624     def tuple(self):
       
   625         "Returns a tuple representation of this Geometry Collection."
       
   626         return tuple([self[i].tuple for i in xrange(self.geom_count)])
       
   627     coords = tuple
       
   628 
       
   629 # Multiple Geometry types.
       
   630 class MultiPoint(GeometryCollection): pass
       
   631 class MultiLineString(GeometryCollection): pass
       
   632 class MultiPolygon(GeometryCollection): pass
       
   633 
       
   634 # Class mapping dictionary (using the OGRwkbGeometryType as the key)
       
   635 GEO_CLASSES = {1 : Point,
       
   636                2 : LineString,
       
   637                3 : Polygon,
       
   638                4 : MultiPoint,
       
   639                5 : MultiLineString,
       
   640                6 : MultiPolygon,
       
   641                7 : GeometryCollection,
       
   642                101: LinearRing, 
       
   643                }