/django/contrib/gis/gdal/layer.py
Python | 212 lines | 132 code | 25 blank | 55 comment | 33 complexity | 9e612d749b966925f67e34f3253bc584 MD5 | raw file
1# Needed ctypes routines 2from ctypes import c_double, byref 3 4# Other GDAL imports. 5from django.contrib.gis.gdal.base import GDALBase 6from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope 7from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException 8from django.contrib.gis.gdal.feature import Feature 9from django.contrib.gis.gdal.field import OGRFieldTypes 10from django.contrib.gis.gdal.geomtype import OGRGeomType 11from django.contrib.gis.gdal.geometries import OGRGeometry 12from django.contrib.gis.gdal.srs import SpatialReference 13 14# GDAL ctypes function prototypes. 15from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api 16 17# For more information, see the OGR C API source code: 18# http://www.gdal.org/ogr/ogr__api_8h.html 19# 20# The OGR_L_* routines are relevant here. 21class Layer(GDALBase): 22 "A class that wraps an OGR Layer, needs to be instantiated from a DataSource object." 23 24 #### Python 'magic' routines #### 25 def __init__(self, layer_ptr, ds): 26 """ 27 Initializes on an OGR C pointer to the Layer and the `DataSource` object 28 that owns this layer. The `DataSource` object is required so that a 29 reference to it is kept with this Layer. This prevents garbage 30 collection of the `DataSource` while this Layer is still active. 31 """ 32 if not layer_ptr: 33 raise OGRException('Cannot create Layer, invalid pointer given') 34 self.ptr = layer_ptr 35 self._ds = ds 36 self._ldefn = capi.get_layer_defn(self._ptr) 37 # Does the Layer support random reading? 38 self._random_read = self.test_capability('RandomRead') 39 40 def __getitem__(self, index): 41 "Gets the Feature at the specified index." 42 if isinstance(index, (int, long)): 43 # An integer index was given -- we cannot do a check based on the 44 # number of features because the beginning and ending feature IDs 45 # are not guaranteed to be 0 and len(layer)-1, respectively. 46 if index < 0: raise OGRIndexError('Negative indices are not allowed on OGR Layers.') 47 return self._make_feature(index) 48 elif isinstance(index, slice): 49 # A slice was given 50 start, stop, stride = index.indices(self.num_feat) 51 return [self._make_feature(fid) for fid in xrange(start, stop, stride)] 52 else: 53 raise TypeError('Integers and slices may only be used when indexing OGR Layers.') 54 55 def __iter__(self): 56 "Iterates over each Feature in the Layer." 57 # ResetReading() must be called before iteration is to begin. 58 capi.reset_reading(self._ptr) 59 for i in xrange(self.num_feat): 60 yield Feature(capi.get_next_feature(self._ptr), self._ldefn) 61 62 def __len__(self): 63 "The length is the number of features." 64 return self.num_feat 65 66 def __str__(self): 67 "The string name of the layer." 68 return self.name 69 70 def _make_feature(self, feat_id): 71 """ 72 Helper routine for __getitem__ that constructs a Feature from the given 73 Feature ID. If the OGR Layer does not support random-access reading, 74 then each feature of the layer will be incremented through until the 75 a Feature is found matching the given feature ID. 76 """ 77 if self._random_read: 78 # If the Layer supports random reading, return. 79 try: 80 return Feature(capi.get_feature(self.ptr, feat_id), self._ldefn) 81 except OGRException: 82 pass 83 else: 84 # Random access isn't supported, have to increment through 85 # each feature until the given feature ID is encountered. 86 for feat in self: 87 if feat.fid == feat_id: return feat 88 # Should have returned a Feature, raise an OGRIndexError. 89 raise OGRIndexError('Invalid feature id: %s.' % feat_id) 90 91 #### Layer properties #### 92 @property 93 def extent(self): 94 "Returns the extent (an Envelope) of this layer." 95 env = OGREnvelope() 96 capi.get_extent(self.ptr, byref(env), 1) 97 return Envelope(env) 98 99 @property 100 def name(self): 101 "Returns the name of this layer in the Data Source." 102 return capi.get_fd_name(self._ldefn) 103 104 @property 105 def num_feat(self, force=1): 106 "Returns the number of features in the Layer." 107 return capi.get_feature_count(self.ptr, force) 108 109 @property 110 def num_fields(self): 111 "Returns the number of fields in the Layer." 112 return capi.get_field_count(self._ldefn) 113 114 @property 115 def geom_type(self): 116 "Returns the geometry type (OGRGeomType) of the Layer." 117 return OGRGeomType(capi.get_fd_geom_type(self._ldefn)) 118 119 @property 120 def srs(self): 121 "Returns the Spatial Reference used in this Layer." 122 try: 123 ptr = capi.get_layer_srs(self.ptr) 124 return SpatialReference(srs_api.clone_srs(ptr)) 125 except SRSException: 126 return None 127 128 @property 129 def fields(self): 130 """ 131 Returns a list of string names corresponding to each of the Fields 132 available in this Layer. 133 """ 134 return [capi.get_field_name(capi.get_field_defn(self._ldefn, i)) 135 for i in xrange(self.num_fields) ] 136 137 @property 138 def field_types(self): 139 """ 140 Returns a list of the types of fields in this Layer. For example, 141 the list [OFTInteger, OFTReal, OFTString] would be returned for 142 an OGR layer that had an integer, a floating-point, and string 143 fields. 144 """ 145 return [OGRFieldTypes[capi.get_field_type(capi.get_field_defn(self._ldefn, i))] 146 for i in xrange(self.num_fields)] 147 148 @property 149 def field_widths(self): 150 "Returns a list of the maximum field widths for the features." 151 return [capi.get_field_width(capi.get_field_defn(self._ldefn, i)) 152 for i in xrange(self.num_fields)] 153 154 @property 155 def field_precisions(self): 156 "Returns the field precisions for the features." 157 return [capi.get_field_precision(capi.get_field_defn(self._ldefn, i)) 158 for i in xrange(self.num_fields)] 159 160 def _get_spatial_filter(self): 161 try: 162 return OGRGeometry(geom_api.clone_geom(capi.get_spatial_filter(self.ptr))) 163 except OGRException: 164 return None 165 166 def _set_spatial_filter(self, filter): 167 if isinstance(filter, OGRGeometry): 168 capi.set_spatial_filter(self.ptr, filter.ptr) 169 elif isinstance(filter, (tuple, list)): 170 if not len(filter) == 4: 171 raise ValueError('Spatial filter list/tuple must have 4 elements.') 172 # Map c_double onto params -- if a bad type is passed in it 173 # will be caught here. 174 xmin, ymin, xmax, ymax = map(c_double, filter) 175 capi.set_spatial_filter_rect(self.ptr, xmin, ymin, xmax, ymax) 176 elif filter is None: 177 capi.set_spatial_filter(self.ptr, None) 178 else: 179 raise TypeError('Spatial filter must be either an OGRGeometry instance, a 4-tuple, or None.') 180 181 spatial_filter = property(_get_spatial_filter, _set_spatial_filter) 182 183 #### Layer Methods #### 184 def get_fields(self, field_name): 185 """ 186 Returns a list containing the given field name for every Feature 187 in the Layer. 188 """ 189 if not field_name in self.fields: 190 raise OGRException('invalid field name: %s' % field_name) 191 return [feat.get(field_name) for feat in self] 192 193 def get_geoms(self, geos=False): 194 """ 195 Returns a list containing the OGRGeometry for every Feature in 196 the Layer. 197 """ 198 if geos: 199 from django.contrib.gis.geos import GEOSGeometry 200 return [GEOSGeometry(feat.geom.wkb) for feat in self] 201 else: 202 return [feat.geom for feat in self] 203 204 def test_capability(self, capability): 205 """ 206 Returns a bool indicating whether the this Layer supports the given 207 capability (a string). Valid capability strings include: 208 'RandomRead', 'SequentialWrite', 'RandomWrite', 'FastSpatialFilter', 209 'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions', 210 'DeleteFeature', and 'FastSetNextByIndex'. 211 """ 212 return bool(capi.test_capability(self.ptr, capability))