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