|
0
|
1 |
# Needed ctypes routines |
|
29
|
2 |
from ctypes import c_double, byref |
|
0
|
3 |
|
|
|
4 |
# Other GDAL imports. |
|
|
5 |
from django.contrib.gis.gdal.base import GDALBase |
|
|
6 |
from django.contrib.gis.gdal.envelope import Envelope, OGREnvelope |
|
|
7 |
from django.contrib.gis.gdal.error import OGRException, OGRIndexError, SRSException |
|
|
8 |
from django.contrib.gis.gdal.feature import Feature |
|
|
9 |
from django.contrib.gis.gdal.field import OGRFieldTypes |
|
29
|
10 |
from django.contrib.gis.gdal.geomtype import OGRGeomType |
|
|
11 |
from django.contrib.gis.gdal.geometries import OGRGeometry |
|
0
|
12 |
from django.contrib.gis.gdal.srs import SpatialReference |
|
|
13 |
|
|
|
14 |
# GDAL ctypes function prototypes. |
|
29
|
15 |
from django.contrib.gis.gdal.prototypes import ds as capi, geom as geom_api, srs as srs_api |
|
0
|
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. |
|
|
21 |
class 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 |
|
|
29
|
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 |
|
|
0
|
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)) |