web/lib/django/contrib/gis/gdal/geometries.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     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 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.libgdal import GEOJSON, GDAL_VERSION
       
    52 from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
       
    53 
       
    54 # Getting the ctypes prototype functions that interface w/the GDAL C library.
       
    55 from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api
       
    56 
       
    57 # For recognizing geometry input.
       
    58 from 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 ####
       
    66 class 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.
       
   541 class 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 
       
   568 class 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.
       
   624 class LinearRing(LineString): pass
       
   625 
       
   626 class 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.
       
   672 class 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.
       
   717 class MultiPoint(GeometryCollection): pass
       
   718 class MultiLineString(GeometryCollection): pass
       
   719 class MultiPolygon(GeometryCollection): pass
       
   720 
       
   721 # Class mapping dictionary (using the OGRwkbGeometryType as the key)
       
   722 GEO_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                }