Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Maintenance refactor of the GDAL (OGR) ctypes interface. Changes incl…

…ude:

* All C API method explictly called from their prototype module, no longer imported via *.
* Applied DRY to C pointer management, classes that do so subclass from `GDALBase`.
* `OGRGeometry`: Added `from_bbox` class method (patch from Christopher Schmidt) and `kml` property.
* `SpatialReference`: Now initialize with `SetFromUserInput` (initialization is now more simple and flexible); removed duplicate methods.
* `Envelope`: Added `expand_to_include` method and now allow same coordinates for lower left and upper right points.  Thanks to Paul Smith for tickets and patches.
* `OGRGeomType`: Now treat OGC 'Geometry' type as 'Unknown'.

Fixed #9855, #10368, #10380.  Refs #9806.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@9985 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit f1ada99997de72fdd3f765ffa11fa4d1d9f438f9 1 parent 53da1e4
Justin Bronn authored March 07, 2009
2  django/contrib/gis/gdal/LICENSE
... ...
@@ -1,4 +1,4 @@
1  
-Copyright (c) 2007, Justin Bronn
  1
+Copyright (c) 2007-2009, Justin Bronn
2 2
 All rights reserved.
3 3
 
4 4
 Redistribution and use in source and binary forms, with or without modification,
35  django/contrib/gis/gdal/base.py
... ...
@@ -0,0 +1,35 @@
  1
+from ctypes import c_void_p
  2
+from types import NoneType
  3
+from django.contrib.gis.gdal.error import GDALException
  4
+
  5
+class GDALBase(object):
  6
+    """
  7
+    Base object for GDAL objects that has a pointer access property
  8
+    that controls access to the underlying C pointer.
  9
+    """
  10
+    # Initially the pointer is NULL.
  11
+    _ptr = None
  12
+
  13
+    # Default allowed pointer type.
  14
+    ptr_type = c_void_p
  15
+
  16
+    # Pointer access property.
  17
+    def _get_ptr(self):
  18
+        # Raise an exception if the pointer isn't valid don't
  19
+        # want to be passing NULL pointers to routines --
  20
+        # that's very bad.
  21
+        if self._ptr: return self._ptr
  22
+        else: raise GDALException('GDAL %s pointer no longer valid.' % self.__class__.__name__)
  23
+
  24
+    def _set_ptr(self, ptr):
  25
+        # Only allow the pointer to be set with pointers of the
  26
+        # compatible type or None (NULL).
  27
+        if isinstance(ptr, int):
  28
+            self._ptr = self.ptr_type(ptr)
  29
+        elif isinstance(ptr, (self.ptr_type, NoneType)):
  30
+            self._ptr = ptr
  31
+        else:
  32
+            raise TypeError('Incompatible pointer type')
  33
+
  34
+    ptr = property(_get_ptr, _set_ptr)
  35
+
40  django/contrib/gis/gdal/datasource.py
@@ -37,28 +37,23 @@
37 37
 from ctypes import byref, c_void_p
38 38
 
39 39
 # The GDAL C library, OGR exceptions, and the Layer object.
  40
+from django.contrib.gis.gdal.base import GDALBase
40 41
 from django.contrib.gis.gdal.driver import Driver
41 42
 from django.contrib.gis.gdal.error import OGRException, OGRIndexError
42 43
 from django.contrib.gis.gdal.layer import Layer
43 44
 
44 45
 # Getting the ctypes prototypes for the DataSource.
45  
-from django.contrib.gis.gdal.prototypes.ds import \
46  
-    destroy_ds, get_driver_count, register_all, open_ds, release_ds, \
47  
-    get_ds_name, get_layer, get_layer_count, get_layer_by_name
  46
+from django.contrib.gis.gdal.prototypes import ds as capi
48 47
 
49 48
 # For more information, see the OGR C API source code:
50 49
 #  http://www.gdal.org/ogr/ogr__api_8h.html
51 50
 #
52 51
 # The OGR_DS_* routines are relevant here.
53  
-class DataSource(object):
  52
+class DataSource(GDALBase):
54 53
     "Wraps an OGR Data Source object."
55 54
 
56 55
     #### Python 'magic' routines ####
57 56
     def __init__(self, ds_input, ds_driver=False, write=False):
58  
-
59  
-        # DataSource pointer is initially NULL.
60  
-        self._ptr = None
61  
-
62 57
         # The write flag.
63 58
         if write:
64 59
             self._write = 1
@@ -67,33 +62,34 @@ def __init__(self, ds_input, ds_driver=False, write=False):
67 62
 
68 63
         # Registering all the drivers, this needs to be done
69 64
         #  _before_ we try to open up a data source.
70  
-        if not get_driver_count(): register_all()
  65
+        if not capi.get_driver_count():
  66
+            capi.register_all()
71 67
 
72 68
         if isinstance(ds_input, basestring):
73 69
             # The data source driver is a void pointer.
74  
-            ds_driver = c_void_p()
  70
+            ds_driver = Driver.ptr_type()
75 71
             try:
76 72
                 # OGROpen will auto-detect the data source type.
77  
-                ds = open_ds(ds_input, self._write, byref(ds_driver))
  73
+                ds = capi.open_ds(ds_input, self._write, byref(ds_driver))
78 74
             except OGRException:
79 75
                 # Making the error message more clear rather than something
80 76
                 # like "Invalid pointer returned from OGROpen".
81 77
                 raise OGRException('Could not open the datasource at "%s"' % ds_input)
82  
-        elif isinstance(ds_input, c_void_p) and isinstance(ds_driver, c_void_p):
  78
+        elif isinstance(ds_input, self.ptr_type) and isinstance(ds_driver, Driver.ptr_type):
83 79
             ds = ds_input
84 80
         else:
85 81
             raise OGRException('Invalid data source input type: %s' % type(ds_input))
86 82
 
87 83
         if bool(ds):
88  
-            self._ptr = ds
89  
-            self._driver = Driver(ds_driver)
  84
+            self.ptr = ds
  85
+            self.driver = Driver(ds_driver)
90 86
         else:
91 87
             # Raise an exception if the returned pointer is NULL 
92 88
             raise OGRException('Invalid data source file "%s"' % ds_input)
93 89
 
94 90
     def __del__(self):
95 91
         "Destroys this DataStructure object."
96  
-        if self._ptr: destroy_ds(self._ptr)
  92
+        if self._ptr: capi.destroy_ds(self._ptr)
97 93
 
98 94
     def __iter__(self):
99 95
         "Allows for iteration over the layers in a data source."
@@ -103,12 +99,12 @@ def __iter__(self):
103 99
     def __getitem__(self, index):
104 100
         "Allows use of the index [] operator to get a layer at the index."
105 101
         if isinstance(index, basestring):
106  
-            l = get_layer_by_name(self._ptr, index)
  102
+            l = capi.get_layer_by_name(self.ptr, index)
107 103
             if not l: raise OGRIndexError('invalid OGR Layer name given: "%s"' % index)
108 104
         elif isinstance(index, int):
109 105
             if index < 0 or index >= self.layer_count:
110 106
                 raise OGRIndexError('index out of range')
111  
-            l = get_layer(self._ptr, index)
  107
+            l = capi.get_layer(self._ptr, index)
112 108
         else:
113 109
             raise TypeError('Invalid index type: %s' % type(index))
114 110
         return Layer(l, self)
@@ -121,18 +117,12 @@ def __str__(self):
121 117
         "Returns OGR GetName and Driver for the Data Source."
122 118
         return '%s (%s)' % (self.name, str(self.driver))
123 119
 
124  
-    #### DataSource Properties ####
125  
-    @property
126  
-    def driver(self):
127  
-        "Returns the Driver object for this Data Source."
128  
-        return self._driver
129  
-        
130 120
     @property
131 121
     def layer_count(self):
132 122
         "Returns the number of layers in the data source."
133  
-        return get_layer_count(self._ptr)
  123
+        return capi.get_layer_count(self._ptr)
134 124
 
135 125
     @property
136 126
     def name(self):
137 127
         "Returns the name of the data source."
138  
-        return get_ds_name(self._ptr)
  128
+        return capi.get_ds_name(self._ptr)
19  django/contrib/gis/gdal/driver.py
... ...
@@ -1,14 +1,14 @@
1 1
 # prerequisites imports 
2 2
 from ctypes import c_void_p
  3
+from django.contrib.gis.gdal.base import GDALBase
3 4
 from django.contrib.gis.gdal.error import OGRException
4  
-from django.contrib.gis.gdal.prototypes.ds import \
5  
-    get_driver, get_driver_by_name, get_driver_count, get_driver_name, register_all
  5
+from django.contrib.gis.gdal.prototypes import ds as capi
6 6
 
7 7
 # For more information, see the OGR C API source code:
8 8
 #  http://www.gdal.org/ogr/ogr__api_8h.html
9 9
 #
10 10
 # The OGR_Dr_* routines are relevant here.
11  
-class Driver(object):
  11
+class Driver(GDALBase):
12 12
     "Wraps an OGR Data Source Driver."
13 13
 
14 14
     # Case-insensitive aliases for OGR Drivers.
@@ -24,7 +24,6 @@ def __init__(self, dr_input):
24 24
 
25 25
         if isinstance(dr_input, basestring):
26 26
             # If a string name of the driver was passed in
27  
-            self._ptr = None # Initially NULL
28 27
             self._register()
29 28
 
30 29
             # Checking the alias dictionary (case-insensitive) to see if an alias
@@ -35,10 +34,10 @@ def __init__(self, dr_input):
35 34
                 name = dr_input
36 35
 
37 36
             # Attempting to get the OGR driver by the string name.
38  
-            dr = get_driver_by_name(name)
  37
+            dr = capi.get_driver_by_name(name)
39 38
         elif isinstance(dr_input, int):
40 39
             self._register()
41  
-            dr = get_driver(dr_input)
  40
+            dr = capi.get_driver(dr_input)
42 41
         elif isinstance(dr_input, c_void_p):
43 42
             dr = dr_input
44 43
         else:
@@ -47,20 +46,20 @@ def __init__(self, dr_input):
47 46
         # Making sure we get a valid pointer to the OGR Driver
48 47
         if not dr:
49 48
             raise OGRException('Could not initialize OGR Driver on input: %s' % str(dr_input))
50  
-        self._ptr = dr
  49
+        self.ptr = dr
51 50
 
52 51
     def __str__(self):
53 52
         "Returns the string name of the OGR Driver."
54  
-        return get_driver_name(self._ptr)
  53
+        return capi.get_driver_name(self.ptr)
55 54
 
56 55
     def _register(self):
57 56
         "Attempts to register all the data source drivers."
58 57
         # Only register all if the driver count is 0 (or else all drivers
59 58
         # will be registered over and over again)
60  
-        if not self.driver_count: register_all()
  59
+        if not self.driver_count: capi.register_all()
61 60
                     
62 61
     # Driver properties
63 62
     @property
64 63
     def driver_count(self):
65 64
         "Returns the number of OGR data source drivers registered."
66  
-        return get_driver_count()
  65
+        return capi.get_driver_count()
55  django/contrib/gis/gdal/envelope.py
@@ -11,7 +11,6 @@
11 11
  Lower left (min_x, min_y) o----------+
12 12
 """
13 13
 from ctypes import Structure, c_double
14  
-from types import TupleType, ListType
15 14
 from django.contrib.gis.gdal.error import OGRException
16 15
 
17 16
 # The OGR definition of an Envelope is a C structure containing four doubles.
@@ -42,7 +41,7 @@ def __init__(self, *args):
42 41
             if isinstance(args[0], OGREnvelope):
43 42
                 # OGREnvelope (a ctypes Structure) was passed in.
44 43
                 self._envelope = args[0]
45  
-            elif isinstance(args[0], (TupleType, ListType)):
  44
+            elif isinstance(args[0], (tuple, list)):
46 45
                 # A tuple was passed in.
47 46
                 if len(args[0]) != 4:
48 47
                     raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0]))
@@ -58,10 +57,10 @@ def __init__(self, *args):
58 57
             raise OGRException('Incorrect number (%d) of arguments.' % len(args))
59 58
 
60 59
         # Checking the x,y coordinates
61  
-        if self.min_x >= self.max_x:
62  
-            raise OGRException('Envelope minimum X >= maximum X.')
63  
-        if self.min_y >= self.max_y:
64  
-            raise OGRException('Envelope minimum Y >= maximum Y.')
  60
+        if self.min_x > self.max_x:
  61
+            raise OGRException('Envelope minimum X > maximum X.')
  62
+        if self.min_y > self.max_y:
  63
+            raise OGRException('Envelope minimum Y > maximum Y.')
65 64
 
66 65
     def __eq__(self, other):
67 66
         """
@@ -71,7 +70,7 @@ def __eq__(self, other):
71 70
         if isinstance(other, Envelope):
72 71
             return (self.min_x == other.min_x) and (self.min_y == other.min_y) and \
73 72
                    (self.max_x == other.max_x) and (self.max_y == other.max_y)
74  
-        elif isinstance(other, TupleType) and len(other) == 4:
  73
+        elif isinstance(other, tuple) and len(other) == 4:
75 74
             return (self.min_x == other[0]) and (self.min_y == other[1]) and \
76 75
                    (self.max_x == other[2]) and (self.max_y == other[3])
77 76
         else:
@@ -89,6 +88,48 @@ def _from_sequence(self, seq):
89 88
         self._envelope.MaxX = seq[2]
90 89
         self._envelope.MaxY = seq[3]
91 90
     
  91
+    def expand_to_include(self, *args): 
  92
+        """ 
  93
+        Modifies the envelope to expand to include the boundaries of 
  94
+        the passed-in 2-tuple (a point), 4-tuple (an extent) or 
  95
+        envelope. 
  96
+        """ 
  97
+        # We provide a number of different signatures for this method, 
  98
+        # and the logic here is all about converting them into a 
  99
+        # 4-tuple single parameter which does the actual work of 
  100
+        # expanding the envelope. 
  101
+        if len(args) == 1: 
  102
+            if isinstance(args[0], Envelope): 
  103
+                return self.expand_to_include(args[0].tuple) 
  104
+            elif hasattr(args[0], 'x') and hasattr(args[0], 'y'):
  105
+                return self.expand_to_include(args[0].x, args[0].y, args[0].x, args[0].y) 
  106
+            elif isinstance(args[0], (tuple, list)): 
  107
+                # A tuple was passed in. 
  108
+                if len(args[0]) == 2: 
  109
+                    return self.expand_to_include((args[0][0], args[0][1], args[0][0], args[0][1])) 
  110
+                elif len(args[0]) == 4: 
  111
+                    (minx, miny, maxx, maxy) = args[0] 
  112
+                    if minx < self._envelope.MinX: 
  113
+                        self._envelope.MinX = minx 
  114
+                    if miny < self._envelope.MinY: 
  115
+                        self._envelope.MinY = miny 
  116
+                    if maxx > self._envelope.MaxX: 
  117
+                        self._envelope.MaxX = maxx 
  118
+                    if maxy > self._envelope.MaxY: 
  119
+                        self._envelope.MaxY = maxy 
  120
+                else: 
  121
+                    raise OGRException('Incorrect number of tuple elements (%d).' % len(args[0])) 
  122
+            else: 
  123
+                raise TypeError('Incorrect type of argument: %s' % str(type(args[0]))) 
  124
+        elif len(args) == 2: 
  125
+            # An x and an y parameter were passed in 
  126
+                return self.expand_to_include((args[0], args[1], args[0], args[1])) 
  127
+        elif len(args) == 4: 
  128
+            # Individiual parameters passed in. 
  129
+            return self.expand_to_include(args) 
  130
+        else: 
  131
+            raise OGRException('Incorrect number (%d) of arguments.' % len(args[0])) 
  132
+
92 133
     @property
93 134
     def min_x(self):
94 135
         "Returns the value of the minimum X coordinate."
1  django/contrib/gis/gdal/error.py
@@ -4,6 +4,7 @@
4 4
  OGR methods.
5 5
 """
6 6
 #### OGR & SRS Exceptions ####
  7
+class GDALException(Exception): pass
7 8
 class OGRException(Exception): pass
8 9
 class SRSException(Exception): pass
9 10
 class OGRIndexError(OGRException, KeyError):
35  django/contrib/gis/gdal/feature.py
... ...
@@ -1,36 +1,31 @@
1 1
 # The GDAL C library, OGR exception, and the Field object
  2
+from django.contrib.gis.gdal.base import GDALBase
2 3
 from django.contrib.gis.gdal.error import OGRException, OGRIndexError
3 4
 from django.contrib.gis.gdal.field import Field
4 5
 from django.contrib.gis.gdal.geometries import OGRGeometry, OGRGeomType
5 6
 from django.contrib.gis.gdal.srs import SpatialReference
6 7
 
7 8
 # ctypes function prototypes
8  
-from django.contrib.gis.gdal.prototypes.ds import \
9  
-    destroy_feature, feature_equal, get_fd_geom_type, get_feat_geom_ref, \
10  
-    get_feat_name, get_feat_field_count, get_fid, get_field_defn, \
11  
-    get_field_index, get_field_name
12  
-from django.contrib.gis.gdal.prototypes.geom import clone_geom, get_geom_srs
13  
-from django.contrib.gis.gdal.prototypes.srs import clone_srs
  9
+from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api
14 10
 
15 11
 # For more information, see the OGR C API source code:
16 12
 #  http://www.gdal.org/ogr/ogr__api_8h.html
17 13
 #
18 14
 # The OGR_F_* routines are relevant here.
19  
-class Feature(object):
  15
+class Feature(GDALBase):
20 16
     "A class that wraps an OGR Feature, needs to be instantiated from a Layer object."
21 17
 
22 18
     #### Python 'magic' routines ####
23 19
     def __init__(self, feat, fdefn):
24 20
         "Initializes on the pointers for the feature and the layer definition."
25  
-        self._ptr = None # Initially NULL
26 21
         if not feat or not fdefn:
27 22
             raise OGRException('Cannot create OGR Feature, invalid pointer given.')
28  
-        self._ptr = feat
  23
+        self.ptr = feat
29 24
         self._fdefn = fdefn
30 25
 
31 26
     def __del__(self):
32 27
         "Releases a reference to this object."
33  
-        if self._ptr: destroy_feature(self._ptr)
  28
+        if self._ptr: capi.destroy_feature(self._ptr)
34 29
 
35 30
     def __getitem__(self, index):
36 31
         """
@@ -45,7 +40,7 @@ def __getitem__(self, index):
45 40
             if index < 0 or index > self.num_fields:
46 41
                 raise OGRIndexError('index out of range')
47 42
             i = index
48  
-        return Field(self._ptr, i)
  43
+        return Field(self.ptr, i)
49 44
     
50 45
     def __iter__(self):
51 46
         "Iterates over each field in the Feature."
@@ -62,41 +57,41 @@ def __str__(self):
62 57
 
63 58
     def __eq__(self, other):
64 59
         "Does equivalence testing on the features."
65  
-        return bool(feature_equal(self._ptr, other._ptr))
  60
+        return bool(capi.feature_equal(self.ptr, other._ptr))
66 61
 
67 62
     #### Feature Properties ####
68 63
     @property
69 64
     def fid(self):
70 65
         "Returns the feature identifier."
71  
-        return get_fid(self._ptr)
  66
+        return capi.get_fid(self.ptr)
72 67
         
73 68
     @property
74 69
     def layer_name(self):
75 70
         "Returns the name of the layer for the feature."
76  
-        return get_feat_name(self._fdefn)
  71
+        return capi.get_feat_name(self._fdefn)
77 72
 
78 73
     @property
79 74
     def num_fields(self):
80 75
         "Returns the number of fields in the Feature."
81  
-        return get_feat_field_count(self._ptr)
  76
+        return capi.get_feat_field_count(self.ptr)
82 77
 
83 78
     @property
84 79
     def fields(self):
85 80
         "Returns a list of fields in the Feature."
86  
-        return [get_field_name(get_field_defn(self._fdefn, i)) 
  81
+        return [capi.get_field_name(capi.get_field_defn(self._fdefn, i)) 
87 82
                 for i in xrange(self.num_fields)]
88 83
 
89 84
     @property
90 85
     def geom(self):
91 86
         "Returns the OGR Geometry for this Feature."
92 87
         # Retrieving the geometry pointer for the feature.
93  
-        geom_ptr = get_feat_geom_ref(self._ptr)
94  
-        return OGRGeometry(clone_geom(geom_ptr))
  88
+        geom_ptr = capi.get_feat_geom_ref(self.ptr)
  89
+        return OGRGeometry(geom_api.clone_geom(geom_ptr))
95 90
 
96 91
     @property
97 92
     def geom_type(self):
98 93
         "Returns the OGR Geometry Type for this Feture."
99  
-        return OGRGeomType(get_fd_geom_type(self._fdefn))
  94
+        return OGRGeomType(capi.get_fd_geom_type(self._fdefn))
100 95
     
101 96
     #### Feature Methods ####
102 97
     def get(self, field):
@@ -110,6 +105,6 @@ def get(self, field):
110 105
 
111 106
     def index(self, field_name):
112 107
         "Returns the index of the given field name."
113  
-        i = get_field_index(self._ptr, field_name)
  108
+        i = capi.get_field_index(self.ptr, field_name)
114 109
         if i < 0: raise OGRIndexError('invalid OFT field name given: "%s"' % field_name)
115 110
         return i
41  django/contrib/gis/gdal/field.py
... ...
@@ -1,16 +1,14 @@
1 1
 from ctypes import byref, c_int
2 2
 from datetime import date, datetime, time
  3
+from django.contrib.gis.gdal.base import GDALBase
3 4
 from django.contrib.gis.gdal.error import OGRException
4  
-from django.contrib.gis.gdal.prototypes.ds import \
5  
-    get_feat_field_defn, get_field_as_datetime, get_field_as_double, \
6  
-    get_field_as_integer, get_field_as_string, get_field_name, get_field_precision, \
7  
-    get_field_type, get_field_type_name, get_field_width
  5
+from django.contrib.gis.gdal.prototypes import ds as capi
8 6
 
9 7
 # For more information, see the OGR C API source code:
10 8
 #  http://www.gdal.org/ogr/ogr__api_8h.html
11 9
 #
12 10
 # The OGR_Fld_* routines are relevant here.
13  
-class Field(object):
  11
+class Field(GDALBase):
14 12
     "A class that wraps an OGR Field, needs to be instantiated from a Feature object."
15 13
 
16 14
     #### Python 'magic' routines ####
@@ -24,13 +22,13 @@ def __init__(self, feat, index):
24 22
         self._index = index
25 23
         
26 24
         # Getting the pointer for this field.
27  
-        fld = get_feat_field_defn(feat, index)
28  
-        if not fld:
  25
+        fld_ptr = capi.get_feat_field_defn(feat, index)
  26
+        if not fld_ptr:
29 27
             raise OGRException('Cannot create OGR Field, invalid pointer given.')
30  
-        self._ptr = fld
  28
+        self.ptr = fld_ptr
31 29
 
32 30
         # Setting the class depending upon the OGR Field Type (OFT)
33  
-        self.__class__ = FIELD_CLASSES[self.type]
  31
+        self.__class__ = OGRFieldTypes[self.type]
34 32
 
35 33
         # OFTReal with no precision should be an OFTInteger.
36 34
         if isinstance(self, OFTReal) and self.precision == 0:
@@ -43,21 +41,21 @@ def __str__(self):
43 41
     #### Field Methods ####
44 42
     def as_double(self):
45 43
         "Retrieves the Field's value as a double (float)."
46  
-        return get_field_as_double(self._feat, self._index)
  44
+        return capi.get_field_as_double(self._feat, self._index)
47 45
 
48 46
     def as_int(self):
49 47
         "Retrieves the Field's value as an integer."
50  
-        return get_field_as_integer(self._feat, self._index)
  48
+        return capi.get_field_as_integer(self._feat, self._index)
51 49
 
52 50
     def as_string(self):
53 51
         "Retrieves the Field's value as a string."
54  
-        return get_field_as_string(self._feat, self._index)
  52
+        return capi.get_field_as_string(self._feat, self._index)
55 53
 
56 54
     def as_datetime(self):
57 55
         "Retrieves the Field's value as a tuple of date & time components."
58 56
         yy, mm, dd, hh, mn, ss, tz = [c_int() for i in range(7)]
59  
-        status = get_field_as_datetime(self._feat, self._index, byref(yy), byref(mm), byref(dd),
60  
-                                       byref(hh), byref(mn), byref(ss), byref(tz))
  57
+        status = capi.get_field_as_datetime(self._feat, self._index, byref(yy), byref(mm), byref(dd),
  58
+                                            byref(hh), byref(mn), byref(ss), byref(tz))
61 59
         if status:
62 60
             return (yy, mm, dd, hh, mn, ss, tz)
63 61
         else:
@@ -67,22 +65,22 @@ def as_datetime(self):
67 65
     @property
68 66
     def name(self):
69 67
         "Returns the name of this Field."
70  
-        return get_field_name(self._ptr)
  68
+        return capi.get_field_name(self.ptr)
71 69
 
72 70
     @property
73 71
     def precision(self):
74 72
         "Returns the precision of this Field."
75  
-        return get_field_precision(self._ptr)
  73
+        return capi.get_field_precision(self.ptr)
76 74
 
77 75
     @property
78 76
     def type(self):
79 77
         "Returns the OGR type of this Field."
80  
-        return get_field_type(self._ptr)
  78
+        return capi.get_field_type(self.ptr)
81 79
 
82 80
     @property
83 81
     def type_name(self):
84 82
         "Return the OGR field type name for this Field."
85  
-        return get_field_type_name(self.type)
  83
+        return capi.get_field_type_name(self.type)
86 84
 
87 85
     @property
88 86
     def value(self):
@@ -93,7 +91,7 @@ def value(self):
93 91
     @property
94 92
     def width(self):
95 93
         "Returns the width of this Field."
96  
-        return get_field_width(self._ptr)
  94
+        return capi.get_field_width(self.ptr)
97 95
 
98 96
 ### The Field sub-classes for each OGR Field type. ###
99 97
 class OFTInteger(Field):
@@ -163,8 +161,8 @@ class OFTRealList(Field): pass
163 161
 class OFTStringList(Field): pass
164 162
 class OFTWideStringList(Field): pass
165 163
 
166  
-# Class mapping dictionary for OFT Types
167  
-FIELD_CLASSES = { 0 : OFTInteger,
  164
+# Class mapping dictionary for OFT Types and reverse mapping.
  165
+OGRFieldTypes = { 0 : OFTInteger,
168 166
                   1 : OFTIntegerList,
169 167
                   2 : OFTReal,
170 168
                   3 : OFTRealList,
@@ -177,3 +175,4 @@ class OFTWideStringList(Field): pass
177 175
                  10 : OFTTime,
178 176
                  11 : OFTDateTime,
179 177
                   }
  178
+ROGRFieldTypes = dict([(cls, num) for num, cls in OGRFieldTypes.items()])
174  django/contrib/gis/gdal/geometries.py
@@ -44,14 +44,15 @@
44 44
 from ctypes import byref, string_at, c_char_p, c_double, c_ubyte, c_void_p
45 45
 
46 46
 # Getting GDAL prerequisites
  47
+from django.contrib.gis.gdal.base import GDALBase
47 48
 from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope
48 49
 from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException
49 50
 from django.contrib.gis.gdal.geomtype import OGRGeomType
50 51
 from django.contrib.gis.gdal.srs import SpatialReference, CoordTransform
51 52
 
52 53
 # Getting the ctypes prototype functions that interface w/the GDAL C library.
53  
-from django.contrib.gis.gdal.prototypes.geom import *
54  
-from django.contrib.gis.gdal.prototypes.srs import clone_srs
  54
+from django.contrib.gis.gdal.prototypes import geom as capi, srs as srs_api
  55
+GEOJSON = capi.GEOJSON
55 56
 
56 57
 # For more information, see the OGR C API source code:
57 58
 #  http://www.gdal.org/ogr/ogr__api_8h.html
@@ -64,13 +65,12 @@
64 65
 json_regex = re.compile(r'^(\s+)?\{[\s\w,\[\]\{\}\-\."\':]+\}(\s+)?$')
65 66
 
66 67
 #### OGRGeometry Class ####
67  
-class OGRGeometry(object):
  68
+class OGRGeometry(GDALBase):
68 69
     "Generally encapsulates an OGR geometry."
69 70
 
70 71
     def __init__(self, geom_input, srs=None):
71 72
         "Initializes Geometry on either WKT or an OGR pointer as input."
72 73
 
73  
-        self._ptr = c_void_p(None) # Initially NULL
74 74
         str_instance = isinstance(geom_input, basestring)
75 75
 
76 76
         # If HEX, unpack input to to a binary buffer.
@@ -91,27 +91,27 @@ def __init__(self, geom_input, srs=None):
91 91
                 if wkt_m.group('type').upper() == 'LINEARRING':
92 92
                     # OGR_G_CreateFromWkt doesn't work with LINEARRING WKT.
93 93
                     #  See http://trac.osgeo.org/gdal/ticket/1992.
94  
-                    g = create_geom(OGRGeomType(wkt_m.group('type')).num)
95  
-                    import_wkt(g, byref(c_char_p(geom_input)))
  94
+                    g = capi.create_geom(OGRGeomType(wkt_m.group('type')).num)
  95
+                    capi.import_wkt(g, byref(c_char_p(geom_input)))
96 96
                 else:
97  
-                    g = from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p()))
  97
+                    g = capi.from_wkt(byref(c_char_p(geom_input)), None, byref(c_void_p()))
98 98
             elif json_m:
99 99
                 if GEOJSON:
100  
-                    g = from_json(geom_input)
  100
+                    g = capi.from_json(geom_input)
101 101
                 else:
102 102
                     raise NotImplementedError('GeoJSON input only supported on GDAL 1.5+.')
103 103
             else:
104 104
                 # Seeing if the input is a valid short-hand string
105 105
                 # (e.g., 'Point', 'POLYGON').
106 106
                 ogr_t = OGRGeomType(geom_input)
107  
-                g = create_geom(OGRGeomType(geom_input).num)
  107
+                g = capi.create_geom(OGRGeomType(geom_input).num)
108 108
         elif isinstance(geom_input, buffer):
109 109
             # WKB was passed in
110  
-            g = from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input))
  110
+            g = capi.from_wkb(str(geom_input), None, byref(c_void_p()), len(geom_input))
111 111
         elif isinstance(geom_input, OGRGeomType):
112 112
             # OGRGeomType was passed in, an empty geometry will be created.
113  
-            g = create_geom(geom_input.num)
114  
-        elif isinstance(geom_input, c_void_p):
  113
+            g = capi.create_geom(geom_input.num)
  114
+        elif isinstance(geom_input, self.ptr_type):
115 115
             # OGR pointer (c_void_p) was the input.
116 116
             g = geom_input
117 117
         else:
@@ -121,7 +121,7 @@ def __init__(self, geom_input, srs=None):
121 121
         # by setting the pointer for the object.
122 122
         if not g:
123 123
             raise OGRException('Cannot create OGR Geometry from input: %s' % str(geom_input))
124  
-        self._ptr = g
  124
+        self.ptr = g
125 125
 
126 126
         # Assigning the SpatialReference object to the geometry, if valid.
127 127
         if bool(srs): self.srs = srs
@@ -129,9 +129,16 @@ def __init__(self, geom_input, srs=None):
129 129
         # Setting the class depending upon the OGR Geometry Type
130 130
         self.__class__ = GEO_CLASSES[self.geom_type.num]
131 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
+ 
132 139
     def __del__(self):
133 140
         "Deletes this Geometry."
134  
-        if self._ptr: destroy_geom(self._ptr)
  141
+        if self._ptr: capi.destroy_geom(self._ptr)
135 142
 
136 143
     ### Geometry set-like operations ###
137 144
     # g = g1 | g2
@@ -170,22 +177,22 @@ def __str__(self):
170 177
     @property
171 178
     def dimension(self):
172 179
         "Returns 0 for points, 1 for lines, and 2 for surfaces."
173  
-        return get_dims(self._ptr)
  180
+        return capi.get_dims(self.ptr)
174 181
 
175 182
     @property
176 183
     def coord_dim(self):
177 184
         "Returns the coordinate dimension of the Geometry."
178  
-        return get_coord_dims(self._ptr)
  185
+        return capi.get_coord_dims(self.ptr)
179 186
 
180 187
     @property
181 188
     def geom_count(self):
182 189
         "The number of elements in this Geometry."
183  
-        return get_geom_count(self._ptr)
  190
+        return capi.get_geom_count(self.ptr)
184 191
 
185 192
     @property
186 193
     def point_count(self):
187 194
         "Returns the number of Points in this Geometry."
188  
-        return get_point_count(self._ptr)
  195
+        return capi.get_point_count(self.ptr)
189 196
 
190 197
     @property
191 198
     def num_points(self):
@@ -201,28 +208,28 @@ def num_coords(self):
201 208
     def geom_type(self):
202 209
         "Returns the Type for this Geometry."
203 210
         try:
204  
-            return OGRGeomType(get_geom_type(self._ptr))
  211
+            return OGRGeomType(capi.get_geom_type(self.ptr))
205 212
         except OGRException:
206 213
             # VRT datasources return an invalid geometry type
207 214
             # number, but a valid name -- we'll try that instead.
208 215
             # See: http://trac.osgeo.org/gdal/ticket/2491
209  
-            return OGRGeomType(get_geom_name(self._ptr))
  216
+            return OGRGeomType(capi.get_geom_name(self.ptr))
210 217
 
211 218
     @property
212 219
     def geom_name(self):
213 220
         "Returns the Name of this Geometry."
214  
-        return get_geom_name(self._ptr)
  221
+        return capi.get_geom_name(self.ptr)
215 222
 
216 223
     @property
217 224
     def area(self):
218 225
         "Returns the area for a LinearRing, Polygon, or MultiPolygon; 0 otherwise."
219  
-        return get_area(self._ptr)
  226
+        return capi.get_area(self.ptr)
220 227
 
221 228
     @property
222 229
     def envelope(self):
223 230
         "Returns the envelope for this Geometry."
224 231
         # TODO: Fix Envelope() for Point geometries.
225  
-        return Envelope(get_envelope(self._ptr, byref(OGREnvelope())))
  232
+        return Envelope(capi.get_envelope(self.ptr, byref(OGREnvelope())))
226 233
 
227 234
     @property
228 235
     def extent(self):
@@ -232,39 +239,40 @@ def extent(self):
232 239
     #### SpatialReference-related Properties ####
233 240
     
234 241
     # The SRS property
235  
-    def get_srs(self):
  242
+    def _get_srs(self):
236 243
         "Returns the Spatial Reference for this Geometry."
237 244
         try:
238  
-            srs_ptr = get_geom_srs(self._ptr)
239  
-            return SpatialReference(clone_srs(srs_ptr))
  245
+            srs_ptr = capi.get_geom_srs(self.ptr)
  246
+            return SpatialReference(srs_api.clone_srs(srs_ptr))
240 247
         except SRSException:
241 248
             return None
242 249
 
243  
-    def set_srs(self, srs):
  250
+    def _set_srs(self, srs):
244 251
         "Sets the SpatialReference for this geometry."
245 252
         if isinstance(srs, SpatialReference):
246  
-            srs_ptr = clone_srs(srs._ptr)
  253
+            srs_ptr = srs_api.clone_srs(srs.ptr)
247 254
         elif isinstance(srs, (int, long, basestring)):
248 255
             sr = SpatialReference(srs)
249  
-            srs_ptr = clone_srs(sr._ptr)
  256
+            srs_ptr = srs_api.clone_srs(sr.ptr)
250 257
         else:
251 258
             raise TypeError('Cannot assign spatial reference with object of type: %s' % type(srs))
252  
-        assign_srs(self._ptr, srs_ptr)
  259
+        capi.assign_srs(self.ptr, srs_ptr)
253 260
 
254  
-    srs = property(get_srs, set_srs)
  261
+    srs = property(_get_srs, _set_srs)
255 262
 
256 263
     # The SRID property
257  
-    def get_srid(self):
258  
-        if self.srs: return self.srs.srid
259  
-        else: return None
  264
+    def _get_srid(self):
  265
+        srs = self.srs
  266
+        if srs: return srs.srid
  267
+        return None
260 268
 
261  
-    def set_srid(self, srid):
  269
+    def _set_srid(self, srid):
262 270
         if isinstance(srid, (int, long)):
263 271
             self.srs = srid
264 272
         else:
265 273
             raise TypeError('SRID must be set with an integer.')
266 274
 
267  
-    srid = property(get_srid, set_srid)
  275
+    srid = property(_get_srid, _set_srid)
268 276
 
269 277
     #### Output Methods ####
270 278
     @property
@@ -276,7 +284,7 @@ def geos(self):
276 284
     @property
277 285
     def gml(self):
278 286
         "Returns the GML representation of the Geometry."
279  
-        return to_gml(self._ptr)
  287
+        return capi.to_gml(self.ptr)
280 288
 
281 289
     @property
282 290
     def hex(self):
@@ -286,16 +294,28 @@ def hex(self):
286 294
 
287 295
     @property
288 296
     def json(self):
  297
+        """
  298
+        Returns the GeoJSON representation of this Geometry (requires
  299
+        GDAL 1.5+).
  300
+        """
289 301
         if GEOJSON: 
290  
-            return to_json(self._ptr)
  302
+            return capi.to_json(self.ptr)
291 303
         else:
292 304
             raise NotImplementedError('GeoJSON output only supported on GDAL 1.5+.')
293 305
     geojson = json
294 306
 
295 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
296 316
     def wkb_size(self):
297 317
         "Returns the size of the WKB buffer."
298  
-        return get_wkbsize(self._ptr)
  318
+        return capi.get_wkbsize(self.ptr)
299 319
 
300 320
     @property
301 321
     def wkb(self):
@@ -307,19 +327,19 @@ def wkb(self):
307 327
         sz = self.wkb_size
308 328
         # Creating the unsigned character buffer, and passing it in by reference.
309 329
         buf = (c_ubyte * sz)()
310  
-        wkb = to_wkb(self._ptr, byteorder, byref(buf))
  330
+        wkb = capi.to_wkb(self.ptr, byteorder, byref(buf))
311 331
         # Returning a buffer of the string at the pointer.
312 332
         return buffer(string_at(buf, sz))
313 333
 
314 334
     @property
315 335
     def wkt(self):
316 336
         "Returns the WKT representation of the Geometry."
317  
-        return to_wkt(self._ptr, byref(c_char_p()))
  337
+        return capi.to_wkt(self.ptr, byref(c_char_p()))
318 338
     
319 339
     #### Geometry Methods ####
320 340
     def clone(self):
321 341
         "Clones this OGR Geometry."
322  
-        return OGRGeometry(clone_geom(self._ptr), self.srs)
  342
+        return OGRGeometry(capi.clone_geom(self.ptr), self.srs)
323 343
 
324 344
     def close_rings(self):
325 345
         """
@@ -328,7 +348,7 @@ def close_rings(self):
328 348
         end.
329 349
         """
330 350
         # Closing the open rings.
331  
-        geom_close_rings(self._ptr)
  351
+        capi.geom_close_rings(self.ptr)
332 352
 
333 353
     def transform(self, coord_trans, clone=False):
334 354
         """
@@ -344,12 +364,12 @@ def transform(self, coord_trans, clone=False):
344 364
             klone.transform(coord_trans)
345 365
             return klone
346 366
         if isinstance(coord_trans, CoordTransform):
347  
-            geom_transform(self._ptr, coord_trans._ptr)
  367
+            capi.geom_transform(self.ptr, coord_trans.ptr)
348 368
         elif isinstance(coord_trans, SpatialReference):
349  
-            geom_transform_to(self._ptr, coord_trans._ptr)
  369
+            capi.geom_transform_to(self.ptr, coord_trans.ptr)
350 370
         elif isinstance(coord_trans, (int, long, basestring)):
351 371
             sr = SpatialReference(coord_trans)
352  
-            geom_transform_to(self._ptr, sr._ptr)
  372
+            capi.geom_transform_to(self.ptr, sr.ptr)
353 373
         else:
354 374
             raise TypeError('Transform only accepts CoordTransform, SpatialReference, string, and integer objects.')
355 375
 
@@ -366,52 +386,52 @@ def _topology(self, func, other):
366 386
 
367 387
         # Returning the output of the given function with the other geometry's
368 388
         # pointer.
369  
-        return func(self._ptr, other._ptr)
  389
+        return func(self.ptr, other.ptr)
370 390
 
371 391
     def intersects(self, other):
372 392
         "Returns True if this geometry intersects with the other."
373  
-        return self._topology(ogr_intersects, other)
  393
+        return self._topology(capi.ogr_intersects, other)
374 394
     
375 395
     def equals(self, other):
376 396
         "Returns True if this geometry is equivalent to the other."
377  
-        return self._topology(ogr_equals, other)
  397
+        return self._topology(capi.ogr_equals, other)
378 398
 
379 399
     def disjoint(self, other):
380 400
         "Returns True if this geometry and the other are spatially disjoint."
381  
-        return self._topology(ogr_disjoint, other)
  401
+        return self._topology(capi.ogr_disjoint, other)
382 402
 
383 403
     def touches(self, other):
384 404
         "Returns True if this geometry touches the other."
385  
-        return self._topology(ogr_touches, other)
  405
+        return self._topology(capi.ogr_touches, other)
386 406
 
387 407
     def crosses(self, other):
388 408
         "Returns True if this geometry crosses the other."
389  
-        return self._topology(ogr_crosses, other)
  409
+        return self._topology(capi.ogr_crosses, other)
390 410
 
391 411
     def within(self, other):
392 412
         "Returns True if this geometry is within the other."
393  
-        return self._topology(ogr_within, other)
  413
+        return self._topology(capi.ogr_within, other)
394 414
 
395 415
     def contains(self, other):
396 416
         "Returns True if this geometry contains the other."
397  
-        return self._topology(ogr_contains, other)
  417
+        return self._topology(capi.ogr_contains, other)
398 418
 
399 419
     def overlaps(self, other):
400 420
         "Returns True if this geometry overlaps the other."
401  
-        return self._topology(ogr_overlaps, other)
  421
+        return self._topology(capi.ogr_overlaps, other)
402 422
 
403 423
     #### Geometry-generation Methods ####
404 424
     def _geomgen(self, gen_func, other=None):
405 425
         "A helper routine for the OGR routines that generate geometries."
406 426
         if isinstance(other, OGRGeometry):
407  
-            return OGRGeometry(gen_func(self._ptr, other._ptr), self.srs)
  427
+            return OGRGeometry(gen_func(self.ptr, other.ptr), self.srs)
408 428
         else:
409  
-            return OGRGeometry(gen_func(self._ptr), self.srs)
  429
+            return OGRGeometry(gen_func(self.ptr), self.srs)
410 430
 
411 431
     @property
412 432
     def boundary(self):
413 433
         "Returns the boundary of this geometry."
414  
-        return self._geomgen(get_boundary)
  434
+        return self._geomgen(capi.get_boundary)
415 435
 
416 436
     @property
417 437
     def convex_hull(self):
@@ -419,35 +439,35 @@ def convex_hull(self):
419 439
         Returns the smallest convex Polygon that contains all the points in 
420 440
         this Geometry.
421 441
         """
422  
-        return self._geomgen(geom_convex_hull)
  442
+        return self._geomgen(capi.geom_convex_hull)
423 443
 
424 444
     def difference(self, other):
425 445
         """
426 446
         Returns a new geometry consisting of the region which is the difference
427 447
         of this geometry and the other.
428 448
         """
429  
-        return self._geomgen(geom_diff, other)
  449
+        return self._geomgen(capi.geom_diff, other)
430 450
 
431 451
     def intersection(self, other):
432 452
         """
433 453
         Returns a new geometry consisting of the region of intersection of this
434 454
         geometry and the other.
435 455
         """
436  
-        return self._geomgen(geom_intersection, other)
  456
+        return self._geomgen(capi.geom_intersection, other)
437 457
 
438 458
     def sym_difference(self, other):
439 459
         """                                                                                                                                                
440 460
         Returns a new geometry which is the symmetric difference of this
441 461
         geometry and the other.
442 462
         """
443  
-        return self._geomgen(geom_sym_diff, other)
  463
+        return self._geomgen(capi.geom_sym_diff, other)
444 464
 
445 465
     def union(self, other):
446 466
         """
447 467
         Returns a new geometry consisting of the region which is the union of
448 468
         this geometry and the other.
449 469
         """
450  
-        return self._geomgen(geom_union, other)
  470
+        return self._geomgen(capi.geom_union, other)
451 471
 
452 472
 # The subclasses for OGR Geometry.
453 473
 class Point(OGRGeometry):
@@ -455,18 +475,18 @@ class Point(OGRGeometry):
455 475
     @property
456 476
     def x(self):
457 477
         "Returns the X coordinate for this Point."
458  
-        return getx(self._ptr, 0)
  478
+        return capi.getx(self.ptr, 0)
459 479
 
460 480
     @property
461 481
     def y(self):
462 482
         "Returns the Y coordinate for this Point."
463  
-        return gety(self._ptr, 0)
  483
+        return capi.gety(self.ptr, 0)
464 484
 
465 485
     @property
466 486
     def z(self):
467 487
         "Returns the Z coordinate for this Point."
468 488
         if self.coord_dim == 3:
469  
-            return getz(self._ptr, 0)
  489
+            return capi.getz(self.ptr, 0)
470 490
 
471 491
     @property
472 492
     def tuple(self):
@@ -483,7 +503,7 @@ def __getitem__(self, index):
483 503
         "Returns the Point at the given index."
484 504
         if index >= 0 and index < self.point_count:
485 505
             x, y, z = c_double(), c_double(), c_double()
486  
-            get_point(self._ptr, index, byref(x), byref(y), byref(z))
  506
+            capi.get_point(self.ptr, index, byref(x), byref(y), byref(z))
487 507
             dim = self.coord_dim
488 508
             if dim == 1:
489 509
                 return (x.value,)
@@ -514,23 +534,23 @@ def _listarr(self, func):
514 534
         Internal routine that returns a sequence (list) corresponding with
515 535
         the given function.
516 536
         """
517  
-        return [func(self._ptr, i) for i in xrange(len(self))]
  537
+        return [func(self.ptr, i) for i in xrange(len(self))]
518 538
 
519 539
     @property
520 540
     def x(self):
521 541
         "Returns the X coordinates in a list."
522  
-        return self._listarr(getx)
  542
+        return self._listarr(capi.getx)
523 543
 
524 544
     @property
525 545
     def y(self):
526 546
         "Returns the Y coordinates in a list."
527  
-        return self._listarr(gety)
  547
+        return self._listarr(capi.gety)
528 548
     
529 549
     @property
530 550
     def z(self):
531 551
         "Returns the Z coordinates in a list."
532 552
         if self.coord_dim == 3:
533  
-            return self._listarr(getz)
  553
+            return self._listarr(capi.getz)
534 554
 
535 555
 # LinearRings are used in Polygons.
536 556
 class LinearRing(LineString): pass
@@ -551,7 +571,7 @@ def __getitem__(self, index):
551 571
         if index < 0 or index >= self.geom_count:
552 572
             raise OGRIndexError('index out of range: %s' % index)
553 573
         else:
554  
-            return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs)
  574
+            return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
555 575
 
556 576
     # Polygon Properties
557 577
     @property
@@ -577,7 +597,7 @@ def centroid(self):
577 597
         "Returns the centroid (a Point) of this Polygon."
578 598
         # The centroid is a Point, create a geometry for this.
579 599
         p = OGRGeometry(OGRGeomType('Point'))
580  
-        get_centroid(self._ptr, p._ptr)
  600
+        capi.get_centroid(self.ptr, p.ptr)
581 601
         return p
582 602
 
583 603
 # Geometry Collection base class.
@@ -589,7 +609,7 @@ def __getitem__(self, index):
589 609
         if index < 0 or index >= self.geom_count:
590 610
             raise OGRIndexError('index out of range: %s' % index)
591 611
         else:
592  
-            return OGRGeometry(clone_geom(get_geom_ref(self._ptr, index)), self.srs)
  612
+            return OGRGeometry(capi.clone_geom(capi.get_geom_ref(self.ptr, index)), self.srs)
593 613
         
594 614
     def __iter__(self):