the ogrgeometry is a wrapper for using the ogr geometry class see https gdal orgapiogrgeometrycpp htmlcppv411ogrgeometry ogrgeometry may be instantiated when reading geometries from ogr data sources e g shp files or when given ogc wkt a string while the full api is not present yet the api is pythonic unlike the traditional and nextgeneration ogr python bindings one major advantage ogr geometries have over their geos counterparts is support for spatial reference systems and their transformation example from django contrib gis gdal import ogrgeometry ogrgeomtype spatialreference wkt1 wkt2 point90 30 polygon0 0 5 0 5 5 0 5 pnt ogrgeometrywkt1 printpnt point 90 30 mpnt ogrgeometryogrgeomtype multipoint spatialreference wgs84 mpnt addwkt1 mpnt addwkt1 printmpnt multipoint 90 30 90 30 printmpnt srs name wgs 84 printmpnt srs proj projlonglat ellpswgs84 datumwgs84 nodefs mpnt transformspatialreference nad27 printmpnt proj projlonglat ellpsclrk66 datumnad27 nodefs printmpnt multipoint 89 99993037860248 29 99979788655764 89 99993037860248 29 99979788655764 the ogrgeomtype class is to make it easy to specify an ogr geometry type from django contrib gis gdal import ogrgeomtype gt1 ogrgeomtype3 using an integer for the type gt2 ogrgeomtype polygon using a string gt3 ogrgeomtype polygon it s caseinsensitive printgt1 3 gt1 polygon equivalence works wnonogrgeomtype objects true true for more information see the ogr c api source code https gdal orgapivectorcapi html the ogrg routines are relevant here encapsulate an ogr geometry destructor capi destroygeom def initself geominput srsnone if hex unpack input to a binary buffer constructing the geometry if there s ewkt set the srs wvalue of the srid ogrgcreatefromwkt doesn t work with linearring wkt see https trac osgeo orggdalticket1992 seeing if the input is a valid shorthand string e g point polygon wkb was passed in ogrgeomtype was passed in an empty geometry will be created ogr pointer cvoidp was the input now checking the geometry pointer before finishing initialization by setting the pointer for the object assigning the spatialreference object to the geometry if valid setting the class depending upon the ogr geometry type pickle routines geometry setlike operations g g1 g2 g g1 g2 g g1 g2 g g1 g2 geometry properties todo fix envelope for point geometries spatialreferencerelated properties the srs property do not have to clone the spatialreference object pointer because when it is assigned to this ogrgeometry it s internal ogr reference count is incremented and will likewise be released decremented when this geometry s destructor is called the srid property output methods return the geojson representation of this geometry creating the unsigned character buffer and passing it in by reference returning a buffer of the string at the pointer geometry methods if there are any rings within this geometry that have not been closed this routine will do so by adding the starting point at the end closing the open rings transform this geometry to a different spatial reference system may take a coordtransform object a spatialreference object string wkt or proj andor an integer srid by default return nothing and transform the geometry inplace however if the clone keyword is set return a transformed clone of this geometry depending on the input type use the appropriate ogr routine to perform the transformation topology methods a generalized function for topology operations takes a gdal function and the other geometry to perform the operation on if not isinstanceother ogrgeometry raise typeerror must use another ogrgeometry object for topology operations returning the output of the given function with the other geometry s pointer return funcself ptr other ptr def intersectsself other return true if this geometry intersects with the other return self topologycapi ogrintersects other def equalsself other return true if this geometry is equivalent to the other return self topologycapi ogrequals other def disjointself other return true if this geometry and the other are spatially disjoint return self topologycapi ogrdisjoint other def touchesself other return true if this geometry touches the other return self topologycapi ogrtouches other def crossesself other return true if this geometry crosses the other return self topologycapi ogrcrosses other def withinself other return true if this geometry is within the other return self topologycapi ogrwithin other def containsself other return true if this geometry contains the other return self topologycapi ogrcontains other def overlapsself other return true if this geometry overlaps the other return self topologycapi ogroverlaps other geometrygeneration methods def geomgenself genfunc othernone a helper routine for the ogr routines that generate geometries if isinstanceother ogrgeometry return ogrgeometrygenfuncself ptr other ptr self srs else return ogrgeometrygenfuncself ptr self srs property def boundaryself return the boundary of this geometry return self geomgencapi getboundary property def convexhullself return self geomgencapi geomconvexhull def differenceself other return self geomgencapi geomdiff other def intersectionself other return self geomgencapi geomintersection other def symdifferenceself other return self geomgencapi geomsymdiff other def unionself other return self geomgencapi geomunion other the subclasses for ogr geometry class pointogrgeometry def geosptrself from django contrib gis import geos return geos point createempty if self empty else super geosptr classmethod def createemptycls return capi creategeomogrgeomtypepoint num property def xself return the x coordinate for this point return capi getxself ptr 0 property def yself return the y coordinate for this point return capi getyself ptr 0 property def zself return the z coordinate for this point if self coorddim 3 return capi getzself ptr 0 property def tupleself return the tuple of this point if self coorddim 2 return self x self y elif self coorddim 3 return self x self y self z coords tuple class linestringogrgeometry def getitemself index return the point at the given index if 0 index self pointcount x y z cdouble cdouble cdouble capi getpointself ptr index byrefx byrefy byrefz dim self coorddim if dim 1 return x value elif dim 2 return x value y value elif dim 3 return x value y value z value else raise indexerror index out of range when accessing points of a line string s index def lenself return the number of points in the linestring return self pointcount property def tupleself return the tuple representation of this linestring return tupleselfi for i in rangelenself coords tuple def listarrself func return funcself ptr i for i in rangelenself property def xself return the x coordinates in a list return self listarrcapi getx property def yself return the y coordinates in a list return self listarrcapi gety property def zself return the z coordinates in a list if self coorddim 3 return self listarrcapi getz linearrings are used in polygons class linearringlinestring pass class polygonogrgeometry def lenself return the number of interior rings in this polygon return self geomcount def getitemself index get the ring at the specified index if 0 index self geomcount return ogrgeometry capi clonegeomcapi getgeomrefself ptr index self srs else raise indexerror index out of range when accessing rings of a polygon s index polygon properties property def shellself return the shell of this polygon return self0 first ring is the shell exteriorring shell property def tupleself return a tuple of linearring coordinate tuples return tupleselfi tuple for i in rangeself geomcount coords tuple property def pointcountself return the number of points in this polygon summing up the number of points in each ring of the polygon return sumselfi pointcount for i in rangeself geomcount property def centroidself return the centroid a point of this polygon the centroid is a point create a geometry for this p ogrgeometryogrgeomtypepoint capi getcentroidself ptr p ptr return p geometry collection base class class geometrycollectionogrgeometry the geometry collection class def getitemself index get the geometry at the specified index if 0 index self geomcount return ogrgeometry capi clonegeomcapi getgeomrefself ptr index self srs else raise indexerror index out of range when accessing geometry in a collection s index def lenself return the number of geometries in this geometry collection return self geomcount def addself geom add the geometry to this geometry collection if isinstancegeom ogrgeometry if isinstancegeom self class for g in geom capi addgeomself ptr g ptr else capi addgeomself ptr geom ptr elif isinstancegeom str tmp ogrgeometrygeom capi addgeomself ptr tmp ptr else raise gdalexceptionmust add an ogrgeometry property def pointcountself return the number of points in this geometry collection summing up the number of points in each geometry in this collection return sumselfi pointcount for i in rangeself geomcount property def tupleself return a tuple representation of this geometry collection return tupleselfi tuple for i in rangeself geomcount coords tuple multiple geometry types class multipointgeometrycollection pass class multilinestringgeometrycollection pass class multipolygongeometrycollection pass class mapping dictionary using the ogrwkbgeometrytype as the key geoclasses 1 point 2 linestring 3 polygon 4 multipoint 5 multilinestring 6 multipolygon 7 geometrycollection 101 linearring 1 ogrgeomtype wkb25bit point 2 ogrgeomtype wkb25bit linestring 3 ogrgeomtype wkb25bit polygon 4 ogrgeomtype wkb25bit multipoint 5 ogrgeomtype wkb25bit multilinestring 6 ogrgeomtype wkb25bit multipolygon 7 ogrgeomtype wkb25bit geometrycollection for more information see the ogr c api source code https gdal org api vector_c_api html the ogr_g_ routines are relevant here encapsulate an ogr geometry initialize geometry on either wkt or an ogr pointer as input if hex unpack input to a binary buffer constructing the geometry if there s ewkt set the srs w value of the srid ogr_g_createfromwkt doesn t work with linearring wkt see https trac osgeo org gdal ticket 1992 seeing if the input is a valid short hand string e g point polygon wkb was passed in ogrgeomtype was passed in an empty geometry will be created ogr pointer c_void_p was the input now checking the geometry pointer before finishing initialization by setting the pointer for the object assigning the spatialreference object to the geometry if valid setting the class depending upon the ogr geometry type pickle routines geometry set like operations g g1 g2 g g1 g2 g g1 g2 g g1 g2 geometry properties todo fix envelope for point geometries spatialreference related properties the srs property do not have to clone the spatialreference object pointer because when it is assigned to this ogrgeometry it s internal ogr reference count is incremented and will likewise be released decremented when this geometry s destructor is called the srid property output methods return the geojson representation of this geometry wkbndr from ogr_core h wkbxdr creating the unsigned character buffer and passing it in by reference returning a buffer of the string at the pointer geometry methods if there are any rings within this geometry that have not been closed this routine will do so by adding the starting point at the end closing the open rings transform this geometry to a different spatial reference system may take a coordtransform object a spatialreference object string wkt or proj and or an integer srid by default return nothing and transform the geometry in place however if the clone keyword is set return a transformed clone of this geometry depending on the input type use the appropriate ogr routine to perform the transformation topology methods a generalized function for topology operations takes a gdal function and the other geometry to perform the operation on returning the output of the given function with the other geometry s pointer geometry generation methods return the smallest convex polygon that contains all the points in this geometry return a new geometry consisting of the region which is the difference of this geometry and the other return a new geometry consisting of the region of intersection of this geometry and the other return a new geometry which is the symmetric difference of this geometry and the other return a new geometry consisting of the region which is the union of this geometry and the other the subclasses for ogr geometry internal routine that returns a sequence list corresponding with the given function linearrings are used in polygons polygon properties first ring is the shell summing up the number of points in each ring of the polygon the centroid is a point create a geometry for this geometry collection base class summing up the number of points in each geometry in this collection multiple geometry types class mapping dictionary using the ogrwkbgeometrytype as the key
import sys
from binascii import b2a_hex
from ctypes import byref, c_char_p, c_double, c_ubyte, c_void_p, string_at

from django.contrib.gis.gdal.base import GDALBase
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
from django.contrib.gis.gdal.error import GDALException, SRSException
from django.contrib.gis.gdal.geomtype import OGRGeomType
from django.contrib.gis.gdal.prototypes import geom as capi
from django.contrib.gis.gdal.prototypes import srs as srs_api
from django.contrib.gis.gdal.srs import CoordTransform, SpatialReference
from django.contrib.gis.geometry import hex_regex, json_regex, wkt_regex
from django.utils.encoding import force_bytes






class OGRGeometry(GDALBase):
    

    destructor = capi.destroy_geom

    def __init__(self, geom_input, srs=None):
        
        str_instance = isinstance(geom_input, str)

        
        if str_instance and hex_regex.match(geom_input):
            geom_input = memoryview(bytes.fromhex(geom_input))
            str_instance = False

        
        if str_instance:
            wkt_m = wkt_regex.match(geom_input)
            json_m = json_regex.match(geom_input)
            if wkt_m:
                if wkt_m["srid"]:
                    
                    srs = int(wkt_m["srid"])
                if wkt_m["type"].upper() == "LINEARRING":
                    
                    
                    g = capi.create_geom(OGRGeomType(wkt_m["type"]).num)
                    capi.import_wkt(g, byref(c_char_p(wkt_m["wkt"].encode())))
                else:
                    g = capi.from_wkt(
                        byref(c_char_p(wkt_m["wkt"].encode())), None, byref(c_void_p())
                    )
            elif json_m:
                g = self._from_json(geom_input.encode())
            else:
                
                
                OGRGeomType(geom_input)
                g = capi.create_geom(OGRGeomType(geom_input).num)
        elif isinstance(geom_input, memoryview):
            
            g = self._from_wkb(geom_input)
        elif isinstance(geom_input, OGRGeomType):
            
            g = capi.create_geom(geom_input.num)
        elif isinstance(geom_input, self.ptr_type):
            
            g = geom_input
        else:
            raise GDALException(
                "Invalid input type for OGR Geometry construction: %s"
                % type(geom_input)
            )

        
        
        if not g:
            raise GDALException(
                "Cannot create OGR Geometry from input: %s" % geom_input
            )
        self.ptr = g

        
        if srs:
            self.srs = srs

        
        self.__class__ = GEO_CLASSES[self.geom_type.num]

    
    def __getstate__(self):
        srs = self.srs
        if srs:
            srs = srs.wkt
        else:
            srs = None
        return bytes(self.wkb), srs

    def __setstate__(self, state):
        wkb, srs = state
        ptr = capi.from_wkb(wkb, None, byref(c_void_p()), len(wkb))
        if not ptr:
            raise GDALException("Invalid OGRGeometry loaded from pickled state.")
        self.ptr = ptr
        self.srs = srs

    @classmethod
    def _from_wkb(cls, geom_input):
        return capi.from_wkb(
            bytes(geom_input), None, byref(c_void_p()), len(geom_input)
        )

    @staticmethod
    def _from_json(geom_input):
        return capi.from_json(geom_input)

    @classmethod
    def from_bbox(cls, bbox):
        "Construct a Polygon from a bounding box (4-tuple)."
        x0, y0, x1, y1 = bbox
        return OGRGeometry(
            "POLYGON((%s %s, %s %s, %s %s, %s %s, %s %s))"
            % (x0, y0, x0, y1, x1, y1, x1, y0, x0, y0)
        )

    @staticmethod
    def from_json(geom_input):
        return OGRGeometry(OGRGeometry._from_json(force_bytes(geom_input)))

    @classmethod
    def from_gml(cls, gml_string):
        return cls(capi.from_gml(force_bytes(gml_string)))

    
    
    def __or__(self, other):
        "Return the union of the two geometries."
        return self.union(other)

    
    def __and__(self, other):
        "Return the intersection of this Geometry and the other."
        return self.intersection(other)

    
    def __sub__(self, other):
        "Return the difference this Geometry and the other."
        return self.difference(other)

    
    def __xor__(self, other):
        "Return the symmetric difference of this Geometry and the other."
        return self.sym_difference(other)

    def __eq__(self, other):
        "Is this Geometry equal to the other?"
        return isinstance(other, OGRGeometry) and self.equals(other)

    def __str__(self):
        "WKT is used for the string representation."
        return self.wkt

    
    @property
    def dimension(self):
        "Return 0 for points, 1 for lines, and 2 for surfaces."
        return capi.get_dims(self.ptr)

    def _get_coord_dim(self):
        "Return the coordinate dimension of the Geometry."
        return capi.get_coord_dim(self.ptr)

    def _set_coord_dim(self, dim):
        "Set the coordinate dimension of this Geometry."
        if dim not in (2, 3):
            raise ValueError("Geometry dimension must be either 2 or 3")
        capi.set_coord_dim(self.ptr, dim)

    coord_dim = property(_get_coord_dim, _set_coord_dim)

    @property
    def geom_count(self):
        "Return the number of elements in this Geometry."
        return capi.get_geom_count(self.ptr)

    @property
    def point_count(self):
        "Return the number of Points in this Geometry."
        return capi.get_point_count(self.ptr)

    @property
    def num_points(self):
        "Alias for `point_count` (same name method in GEOS API.)"
        return self.point_count

    @property
    def num_coords(self):
        "Alias for `point_count`."
        return self.point_count

    @property
    def geom_type(self):
        "Return the Type for this Geometry."
        return OGRGeomType(capi.get_geom_type(self.ptr))

    @property
    def geom_name(self):
        "Return the Name of this Geometry."
        return capi.get_geom_name(self.ptr)

    @property
    def area(self):
        "Return the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
        return capi.get_area(self.ptr)

    @property
    def envelope(self):
        "Return the envelope for this Geometry."
        
        return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope())))

    @property
    def empty(self):
        return capi.is_empty(self.ptr)

    @property
    def extent(self):
        "Return the envelope as a 4-tuple, instead of as an Envelope object."
        return self.envelope.tuple

    

    
    def _get_srs(self):
        "Return the Spatial Reference for this Geometry."
        try:
            srs_ptr = capi.get_geom_srs(self.ptr)
            return SpatialReference(srs_api.clone_srs(srs_ptr))
        except SRSException:
            return None

    def _set_srs(self, srs):
        "Set the SpatialReference for this geometry."
        
        
        
        
        if isinstance(srs, SpatialReference):
            srs_ptr = srs.ptr
        elif isinstance(srs, (int, str)):
            sr = SpatialReference(srs)
            srs_ptr = sr.ptr
        elif srs is None:
            srs_ptr = None
        else:
            raise TypeError(
                "Cannot assign spatial reference with object of type: %s" % type(srs)
            )
        capi.assign_srs(self.ptr, srs_ptr)

    srs = property(_get_srs, _set_srs)

    
    def _get_srid(self):
        srs = self.srs
        if srs:
            return srs.srid
        return None

    def _set_srid(self, srid):
        if isinstance(srid, int) or srid is None:
            self.srs = srid
        else:
            raise TypeError("SRID must be set with an integer.")

    srid = property(_get_srid, _set_srid)

    
    def _geos_ptr(self):
        from django.contrib.gis.geos import GEOSGeometry

        return GEOSGeometry._from_wkb(self.wkb)

    @property
    def geos(self):
        "Return a GEOSGeometry object from this OGRGeometry."
        from django.contrib.gis.geos import GEOSGeometry

        return GEOSGeometry(self._geos_ptr(), self.srid)

    @property
    def gml(self):
        "Return the GML representation of the Geometry."
        return capi.to_gml(self.ptr)

    @property
    def hex(self):
        "Return the hexadecimal representation of the WKB (a string)."
        return b2a_hex(self.wkb).upper()

    @property
    def json(self):
        
        return capi.to_json(self.ptr)

    geojson = json

    @property
    def kml(self):
        "Return the KML representation of the Geometry."
        return capi.to_kml(self.ptr, None)

    @property
    def wkb_size(self):
        "Return the size of the WKB buffer."
        return capi.get_wkbsize(self.ptr)

    @property
    def wkb(self):
        "Return the WKB representation of the Geometry."
        if sys.byteorder == "little":
            byteorder = 1  
        else:
            byteorder = 0  
        sz = self.wkb_size
        
        buf = (c_ubyte * sz)()
        capi.to_wkb(self.ptr, byteorder, byref(buf))
        
        return memoryview(string_at(buf, sz))

    @property
    def wkt(self):
        "Return the WKT representation of the Geometry."
        return capi.to_wkt(self.ptr, byref(c_char_p()))

    @property
    def ewkt(self):
        "Return the EWKT representation of the Geometry."
        srs = self.srs
        if srs and srs.srid:
            return "SRID=%s;%s" % (srs.srid, self.wkt)
        else:
            return self.wkt

    
    def clone(self):
        "Clone this OGR Geometry."
        return OGRGeometry(capi.clone_geom(self.ptr), self.srs)

    def close_rings(self):
        
        
        capi.geom_close_rings(self.ptr)

    def transform(self, coord_trans, clone=False):
        
        if clone:
            klone = self.clone()
            klone.transform(coord_trans)
            return klone

        
        
        if isinstance(coord_trans, CoordTransform):
            capi.geom_transform(self.ptr, coord_trans.ptr)
        elif isinstance(coord_trans, SpatialReference):
            capi.geom_transform_to(self.ptr, coord_trans.ptr)
        elif isinstance(coord_trans, (int, str)):
            sr = SpatialReference(coord_trans)
            capi.geom_transform_to(self.ptr, sr.ptr)
        else:
            raise TypeError(
                "Transform only accepts CoordTransform, "
                "SpatialReference, string, and integer objects."
            )

    
    def _topology(self, func, other):
        
        if not isinstance(other, OGRGeometry):
            raise TypeError(
                "Must use another OGRGeometry object for topology operations!"
            )

        
        
        return func(self.ptr, other.ptr)

    def intersects(self, other):
        "Return True if this geometry intersects with the other."
        return self._topology(capi.ogr_intersects, other)

    def equals(self, other):
        "Return True if this geometry is equivalent to the other."
        return self._topology(capi.ogr_equals, other)

    def disjoint(self, other):
        "Return True if this geometry and the other are spatially disjoint."
        return self._topology(capi.ogr_disjoint, other)

    def touches(self, other):
        "Return True if this geometry touches the other."
        return self._topology(capi.ogr_touches, other)

    def crosses(self, other):
        "Return True if this geometry crosses the other."
        return self._topology(capi.ogr_crosses, other)

    def within(self, other):
        "Return True if this geometry is within the other."
        return self._topology(capi.ogr_within, other)

    def contains(self, other):
        "Return True if this geometry contains the other."
        return self._topology(capi.ogr_contains, other)

    def overlaps(self, other):
        "Return True if this geometry overlaps the other."
        return self._topology(capi.ogr_overlaps, other)

    
    def _geomgen(self, gen_func, other=None):
        "A helper routine for the OGR routines that generate geometries."
        if isinstance(other, OGRGeometry):
            return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs)
        else:
            return OGRGeometry(gen_func(self.ptr), self.srs)

    @property
    def boundary(self):
        "Return the boundary of this geometry."
        return self._geomgen(capi.get_boundary)

    @property
    def convex_hull(self):
        
        return self._geomgen(capi.geom_convex_hull)

    def difference(self, other):
        
        return self._geomgen(capi.geom_diff, other)

    def intersection(self, other):
        
        return self._geomgen(capi.geom_intersection, other)

    def sym_difference(self, other):
        
        return self._geomgen(capi.geom_sym_diff, other)

    def union(self, other):
        
        return self._geomgen(capi.geom_union, other)



class Point(OGRGeometry):
    def _geos_ptr(self):
        from django.contrib.gis import geos

        return geos.Point._create_empty() if self.empty else super()._geos_ptr()

    @classmethod
    def _create_empty(cls):
        return capi.create_geom(OGRGeomType("point").num)

    @property
    def x(self):
        "Return the X coordinate for this Point."
        return capi.getx(self.ptr, 0)

    @property
    def y(self):
        "Return the Y coordinate for this Point."
        return capi.gety(self.ptr, 0)

    @property
    def z(self):
        "Return the Z coordinate for this Point."
        if self.coord_dim == 3:
            return capi.getz(self.ptr, 0)

    @property
    def tuple(self):
        "Return the tuple of this point."
        if self.coord_dim == 2:
            return (self.x, self.y)
        elif self.coord_dim == 3:
            return (self.x, self.y, self.z)

    coords = tuple


class LineString(OGRGeometry):
    def __getitem__(self, index):
        "Return the Point at the given index."
        if 0 <= index < self.point_count:
            x, y, z = c_double(), c_double(), c_double()
            capi.get_point(self.ptr, index, byref(x), byref(y), byref(z))
            dim = self.coord_dim
            if dim == 1:
                return (x.value,)
            elif dim == 2:
                return (x.value, y.value)
            elif dim == 3:
                return (x.value, y.value, z.value)
        else:
            raise IndexError(
                "Index out of range when accessing points of a line string: %s." % index
            )

    def __len__(self):
        "Return the number of points in the LineString."
        return self.point_count

    @property
    def tuple(self):
        "Return the tuple representation of this LineString."
        return tuple(self[i] for i in range(len(self)))

    coords = tuple

    def _listarr(self, func):
        
        return [func(self.ptr, i) for i in range(len(self))]

    @property
    def x(self):
        "Return the X coordinates in a list."
        return self._listarr(capi.getx)

    @property
    def y(self):
        "Return the Y coordinates in a list."
        return self._listarr(capi.gety)

    @property
    def z(self):
        "Return the Z coordinates in a list."
        if self.coord_dim == 3:
            return self._listarr(capi.getz)



class LinearRing(LineString):
    pass


class Polygon(OGRGeometry):
    def __len__(self):
        "Return the number of interior rings in this Polygon."
        return self.geom_count

    def __getitem__(self, index):
        "Get the ring at the specified index."
        if 0 <= index < self.geom_count:
            return OGRGeometry(
                capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs
            )
        else:
            raise IndexError(
                "Index out of range when accessing rings of a polygon: %s." % index
            )

    
    @property
    def shell(self):
        "Return the shell of this Polygon."
        return self[0]  

    exterior_ring = shell

    @property
    def tuple(self):
        "Return a tuple of LinearRing coordinate tuples."
        return tuple(self[i].tuple for i in range(self.geom_count))

    coords = tuple

    @property
    def point_count(self):
        "Return the number of Points in this Polygon."
        
        return sum(self[i].point_count for i in range(self.geom_count))

    @property
    def centroid(self):
        "Return the centroid (a Point) of this Polygon."
        
        p = OGRGeometry(OGRGeomType("Point"))
        capi.get_centroid(self.ptr, p.ptr)
        return p



class GeometryCollection(OGRGeometry):
    "The Geometry Collection class."

    def __getitem__(self, index):
        "Get the Geometry at the specified index."
        if 0 <= index < self.geom_count:
            return OGRGeometry(
                capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs
            )
        else:
            raise IndexError(
                "Index out of range when accessing geometry in a collection: %s."
                % index
            )

    def __len__(self):
        "Return the number of geometries in this Geometry Collection."
        return self.geom_count

    def add(self, geom):
        "Add the geometry to this Geometry Collection."
        if isinstance(geom, OGRGeometry):
            if isinstance(geom, self.__class__):
                for g in geom:
                    capi.add_geom(self.ptr, g.ptr)
            else:
                capi.add_geom(self.ptr, geom.ptr)
        elif isinstance(geom, str):
            tmp = OGRGeometry(geom)
            capi.add_geom(self.ptr, tmp.ptr)
        else:
            raise GDALException("Must add an OGRGeometry.")

    @property
    def point_count(self):
        "Return the number of Points in this Geometry Collection."
        
        return sum(self[i].point_count for i in range(self.geom_count))

    @property
    def tuple(self):
        "Return a tuple representation of this Geometry Collection."
        return tuple(self[i].tuple for i in range(self.geom_count))

    coords = tuple



class MultiPoint(GeometryCollection):
    pass


class MultiLineString(GeometryCollection):
    pass


class MultiPolygon(GeometryCollection):
    pass



GEO_CLASSES = {
    1: Point,
    2: LineString,
    3: Polygon,
    4: MultiPoint,
    5: MultiLineString,
    6: MultiPolygon,
    7: GeometryCollection,
    101: LinearRing,
    1 + OGRGeomType.wkb25bit: Point,
    2 + OGRGeomType.wkb25bit: LineString,
    3 + OGRGeomType.wkb25bit: Polygon,
    4 + OGRGeomType.wkb25bit: MultiPoint,
    5 + OGRGeomType.wkb25bit: MultiLineString,
    6 + OGRGeomType.wkb25bit: MultiPolygon,
    7 + OGRGeomType.wkb25bit: GeometryCollection,
}