Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

gis: gdal: Features may now be fetched from OGR layers that do not su…

…pport random access reading, but no more negative indexes are allowed; cleaned up `OGRGeomType`; moved test vector data into its own directory.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@8034 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 825d6edd69404de29c6fb8be568176f03b0c982c 1 parent 7011fc6
@jbronn jbronn authored
View
1  django/contrib/gis/gdal/error.py
@@ -25,6 +25,7 @@ class OGRIndexError(OGRException, KeyError):
5 : (OGRException, 'Corrupt data.'),
6 : (OGRException, 'OGR failure.'),
7 : (SRSException, 'Unsupported SRS.'),
+ 8 : (OGRException, 'Invalid handle.'),
}
OGRERR_NONE = 0
View
8 django/contrib/gis/gdal/geometries.py
@@ -201,7 +201,13 @@ def num_coords(self):
@property
def geom_type(self):
"Returns the Type for this Geometry."
- return OGRGeomType(get_geom_type(self._ptr))
+ try:
+ return OGRGeomType(get_geom_type(self._ptr))
+ except OGRException:
+ # VRT datasources return an invalid geometry type
+ # number, but a valid name -- we'll try that instead.
+ # See: http://trac.osgeo.org/gdal/ticket/2491
+ return OGRGeomType(get_geom_name(self._ptr))
@property
def geom_name(self):
View
67 django/contrib/gis/gdal/geomtype.py
@@ -4,31 +4,42 @@
class OGRGeomType(object):
"Encapulates OGR Geometry Types."
- # Ordered array of acceptable strings and their corresponding OGRwkbGeometryType
- __ogr_str = ['Unknown', 'Point', 'LineString', 'Polygon', 'MultiPoint',
- 'MultiLineString', 'MultiPolygon', 'GeometryCollection',
- 'LinearRing']
- __ogr_int = [0, 1, 2, 3, 4, 5, 6, 7, 101]
+ # Dictionary of acceptable OGRwkbGeometryType s and their string names.
+ _types = {0 : 'Unknown',
+ 1 : 'Point',
+ 2 : 'LineString',
+ 3 : 'Polygon',
+ 4 : 'MultiPoint',
+ 5 : 'MultiLineString',
+ 6 : 'MultiPolygon',
+ 7 : 'GeometryCollection',
+ 100 : 'None',
+ 101 : 'LinearRing',
+ }
+ # Reverse type dictionary, keyed by lower-case of the name.
+ _str_types = dict([(v.lower(), k) for k, v in _types.items()])
def __init__(self, type_input):
"Figures out the correct OGR Type based upon the input."
if isinstance(type_input, OGRGeomType):
- self._index = type_input._index
+ num = type_input.num
elif isinstance(type_input, basestring):
- idx = self._has_str(self.__ogr_str, type_input)
- if idx == None:
+ num = self._str_types.get(type_input.lower(), None)
+ if num is None:
raise OGRException('Invalid OGR String Type "%s"' % type_input)
- self._index = idx
elif isinstance(type_input, int):
- if not type_input in self.__ogr_int:
+ if not type_input in self._types:
raise OGRException('Invalid OGR Integer Type: %d' % type_input)
- self._index = self.__ogr_int.index(type_input)
+ num = type_input
else:
raise TypeError('Invalid OGR input type given.')
+
+ # Setting the OGR geometry type number.
+ self.num = num
def __str__(self):
- "Returns a short-hand string form of the OGR Geometry type."
- return self.__ogr_str[self._index]
+ "Returns the value of the name property."
+ return self.name
def __eq__(self, other):
"""
@@ -36,37 +47,27 @@ def __eq__(self, other):
other OGRGeomType, the short-hand string, or the integer.
"""
if isinstance(other, OGRGeomType):
- return self._index == other._index
+ return self.num == other.num
elif isinstance(other, basestring):
- idx = self._has_str(self.__ogr_str, other)
- if not (idx == None): return self._index == idx
- return False
+ return self.name.lower() == other.lower()
elif isinstance(other, int):
- if not other in self.__ogr_int: return False
- return self.__ogr_int.index(other) == self._index
+ return self.num == other
else:
- raise TypeError('Cannot compare with type: %s' % str(type(other)))
+ return False
def __ne__(self, other):
return not (self == other)
- def _has_str(self, arr, s):
- "Case-insensitive search of the string array for the given pattern."
- s_low = s.lower()
- for i in xrange(len(arr)):
- if s_low == arr[i].lower(): return i
- return None
+ @property
+ def name(self):
+ "Returns a short-hand string form of the OGR Geometry type."
+ return self._types[self.num]
@property
def django(self):
"Returns the Django GeometryField for this OGR Type."
- s = self.__ogr_str[self._index]
- if s in ('Unknown', 'LinearRing'):
+ s = self.name
+ if s in ('Unknown', 'LinearRing', 'None'):
return None
else:
return s + 'Field'
-
- @property
- def num(self):
- "Returns the OGRwkbGeometryType number for the OGR Type."
- return self.__ogr_int[self._index]
View
61 django/contrib/gis/gdal/layer.py
@@ -14,7 +14,7 @@
get_extent, get_fd_geom_type, get_fd_name, get_feature, get_feature_count, \
get_field_count, get_field_defn, get_field_name, get_field_precision, \
get_field_width, get_field_type, get_layer_defn, get_layer_srs, \
- get_next_feature, reset_reading
+ get_next_feature, reset_reading, test_capability
from django.contrib.gis.gdal.prototypes.srs import clone_srs
# For more information, see the OGR C API source code:
@@ -32,29 +32,29 @@ def __init__(self, layer_ptr):
raise OGRException('Cannot create Layer, invalid pointer given')
self._ptr = layer_ptr
self._ldefn = get_layer_defn(self._ptr)
+ # Does the Layer support random reading?
+ self._random_read = self.test_capability('RandomRead')
def __getitem__(self, index):
"Gets the Feature at the specified index."
- if not isinstance(index, (slice, int)):
- raise TypeError
- end = self.num_feat
- if isinstance(index,int):
- # An integer index was given
- if index < 0:
- index = end - index
- if index < 0 or index >= self.num_feat:
- raise OGRIndexError('index out of range')
+ if isinstance(index, (int, long)):
+ # An integer index was given -- we cannot do a check based on the
+ # number of features because the beginning and ending feature IDs
+ # are not guaranteed to be 0 and len(layer)-1, respectively.
+ if index < 0: raise OGRIndexError('Negative indices are not allowed on OGR Layers.')
return self._make_feature(index)
- else:
+ elif isinstance(index, slice):
# A slice was given
- start, stop, stride = index.indices(end)
- return [self._make_feature(offset) for offset in range(start,stop,stride)]
+ start, stop, stride = index.indices(self.num_feat)
+ return [self._make_feature(fid) for fid in xrange(start, stop, stride)]
+ else:
+ raise TypeError('Integers and slices may only be used when indexing OGR Layers.')
def __iter__(self):
"Iterates over each Feature in the Layer."
# ResetReading() must be called before iteration is to begin.
reset_reading(self._ptr)
- for i in range(self.num_feat):
+ for i in xrange(self.num_feat):
yield Feature(get_next_feature(self._ptr), self._ldefn)
def __len__(self):
@@ -65,9 +65,26 @@ def __str__(self):
"The string name of the layer."
return self.name
- def _make_feature(self, offset):
- "Helper routine for __getitem__ that makes a feature from an offset."
- return Feature(get_feature(self._ptr, offset), self._ldefn)
+ def _make_feature(self, feat_id):
+ """
+ Helper routine for __getitem__ that constructs a Feature from the given
+ Feature ID. If the OGR Layer does not support random-access reading,
+ then each feature of the layer will be incremented through until the
+ a Feature is found matching the given feature ID.
+ """
+ if self._random_read:
+ # If the Layer supports random reading, return.
+ try:
+ return Feature(get_feature(self._ptr, feat_id), self._ldefn)
+ except OGRException:
+ pass
+ else:
+ # Random access isn't supported, have to increment through
+ # each feature until the given feature ID is encountered.
+ for feat in self:
+ if feat.fid == feat_id: return feat
+ # Should have returned a Feature, raise an OGRIndexError.
+ raise OGRIndexError('Invalid feature id: %s.' % feat_id)
#### Layer properties ####
@property
@@ -158,3 +175,13 @@ def get_geoms(self, geos=False):
return [GEOSGeometry(feat.geom.wkb) for feat in self]
else:
return [feat.geom for feat in self]
+
+ def test_capability(self, capability):
+ """
+ Returns a bool indicating whether the this Layer supports the given
+ capability (a string). Valid capability strings include:
+ 'RandomRead', 'SequentialWrite', 'RandomWrite', 'FastSpatialFilter',
+ 'FastFeatureCount', 'FastGetExtent', 'CreateField', 'Transactions',
+ 'DeleteFeature', and 'FastSetNextByIndex'.
+ """
+ return bool(test_capability(self._ptr, capability))
View
1  django/contrib/gis/gdal/prototypes/ds.py
@@ -37,6 +37,7 @@
get_layer_srs = srs_output(lgdal.OGR_L_GetSpatialRef, [c_void_p])
get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [c_void_p])
reset_reading = void_output(lgdal.OGR_L_ResetReading, [c_void_p], errcheck=False)
+test_capability = int_output(lgdal.OGR_L_TestCapability, [c_void_p, c_char_p])
### Feature Definition Routines ###
get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p])
View
0  ...ntrib/gis/tests/test_point/test_point.dbf → .../gis/tests/data/test_point/test_point.dbf
File renamed without changes
View
0  ...ntrib/gis/tests/test_point/test_point.prj → .../gis/tests/data/test_point/test_point.prj
File renamed without changes
View
0  ...ntrib/gis/tests/test_point/test_point.shp → .../gis/tests/data/test_point/test_point.shp
File renamed without changes
View
0  ...ntrib/gis/tests/test_point/test_point.shx → .../gis/tests/data/test_point/test_point.shx
File renamed without changes
View
0  ...contrib/gis/tests/test_poly/test_poly.dbf → ...ib/gis/tests/data/test_poly/test_poly.dbf
File renamed without changes
View
0  ...contrib/gis/tests/test_poly/test_poly.prj → ...ib/gis/tests/data/test_poly/test_poly.prj
File renamed without changes
View
0  ...contrib/gis/tests/test_poly/test_poly.shp → ...ib/gis/tests/data/test_poly/test_poly.shp
File renamed without changes
View
0  ...contrib/gis/tests/test_poly/test_poly.shx → ...ib/gis/tests/data/test_poly/test_poly.shx
File renamed without changes
View
4 django/contrib/gis/tests/data/test_vrt/test_vrt.csv
@@ -0,0 +1,4 @@
+POINT_X,POINT_Y,NUM
+1.0,2.0,5
+5.0,23.0,17
+100.0,523.5,23
View
7 django/contrib/gis/tests/data/test_vrt/test_vrt.vrt
@@ -0,0 +1,7 @@
+<OGRVRTDataSource>
+<OGRVRTLayer name="test_vrt">
+<SrcDataSource relativeToVRT="1">test_vrt.csv</SrcDataSource>
+<GeometryType>wkbPoint</GeometryType>
+<GeometryField encoding="PointFromColumns" x="POINT_X" y="POINT_Y" z="NUM"/>
+</OGRVRTLayer>
+</OGRVRTDataSource>
View
95 django/contrib/gis/tests/test_gdal_ds.py
@@ -3,27 +3,38 @@
from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
# Path for SHP files
-shp_path = os.path.dirname(__file__)
-def get_shp(name):
- return shp_path + os.sep + name + os.sep + name + '.shp'
+data_path = os.path.join(os.path.dirname(__file__), 'data')
+def get_ds_file(name, ext):
+ return os.sep.join([data_path, name, name + '.%s' % ext])
# Test SHP data source object
-class TestSHP:
- def __init__(self, shp, **kwargs):
- self.ds = get_shp(shp)
+class TestDS:
+ def __init__(self, name, **kwargs):
+ ext = kwargs.pop('ext', 'shp')
+ self.ds = get_ds_file(name, ext)
for key, value in kwargs.items():
setattr(self, key, value)
# List of acceptable data sources.
-ds_list = (TestSHP('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, fields={'dbl' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,},
- extent=(-1.35011,0.166623,-0.524093,0.824508), # Got extent from QGIS
- srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),
- TestSHP('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3, fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,},
- extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS
- srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),
+ds_list = (TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile',
+ fields={'dbl' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,},
+ extent=(-1.35011,0.166623,-0.524093,0.824508), # Got extent from QGIS
+ srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]',
+ field_values={'dbl' : [float(i) for i in range(1, 6)], 'int' : range(1, 6), 'str' : [str(i) for i in range(1, 6)]},
+ fids=range(5)),
+ TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype=1, driver='VRT',
+ fields={'POINT_X' : OFTString, 'POINT_Y' : OFTString, 'NUM' : OFTString}, # VRT uses CSV, which all types are OFTString.
+ extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV
+ field_values={'POINT_X' : ['1.0', '5.0', '100.0'], 'POINT_Y' : ['2.0', '23.0', '523.5'], 'NUM' : ['5', '17', '23']},
+ fids=range(1,4)),
+ TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3,
+ driver='ESRI Shapefile',
+ fields={'float' : OFTReal, 'int' : OFTInteger, 'str' : OFTString,},
+ extent=(-1.01513,-0.558245,0.161876,0.839637), # Got extent from QGIS
+ srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),
)
-bad_ds = (TestSHP('foo'),
+bad_ds = (TestDS('foo'),
)
class DataSourceTest(unittest.TestCase):
@@ -42,7 +53,7 @@ def test01_valid_shp(self):
self.assertEqual(source.ds, ds.name)
# Making sure the driver name matches up
- self.assertEqual('ESRI Shapefile', str(ds.driver))
+ self.assertEqual(source.driver, str(ds.driver))
# Making sure indexing works
try:
@@ -57,20 +68,17 @@ def test02_invalid_shp(self):
for source in bad_ds:
self.assertRaises(OGRException, DataSource, source.ds)
- def test03_layers(self):
+ def test03a_layers(self):
"Testing Data Source Layers."
-
+ print "\nBEGIN - expecting out of range feature id error; safe to ignore.\n"
for source in ds_list:
ds = DataSource(source.ds)
- # Incrementing through each layer, this tests __iter__
+ # Incrementing through each layer, this tests DataSource.__iter__
for layer in ds:
# Making sure we get the number of features we expect
self.assertEqual(len(layer), source.nfeat)
- layer[0] #can index
- layer[:1] #can slice
-
# Making sure we get the number of fields we expect
self.assertEqual(source.nfld, layer.num_fields)
self.assertEqual(source.nfld, len(layer.fields))
@@ -85,6 +93,42 @@ def test03_layers(self):
# Now checking the field names.
flds = layer.fields
for f in flds: self.assertEqual(True, f in source.fields)
+
+ # Negative FIDs are not allowed.
+ self.assertRaises(OGRIndexError, layer.__getitem__, -1)
+ self.assertRaises(OGRIndexError, layer.__getitem__, 50000)
+
+ if hasattr(source, 'field_values'):
+ fld_names = source.field_values.keys()
+
+ # Testing `Layer.get_fields` (which uses Layer.__iter__)
+ for fld_name in fld_names:
+ self.assertEqual(source.field_values[fld_name], layer.get_fields(fld_name))
+
+ # Testing `Layer.__getitem__`.
+ for i, fid in enumerate(source.fids):
+ feat = layer[fid]
+ self.assertEqual(fid, feat.fid)
+ # Maybe this should be in the test below, but we might as well test
+ # the feature values here while in this loop.
+ for fld_name in fld_names:
+ self.assertEqual(source.field_values[fld_name][i], feat.get(fld_name))
+ print "\nEND - expecting out of range feature id error; safe to ignore."
+
+ def test03b_layer_slice(self):
+ "Test indexing and slicing on Layers."
+ # Using the first data-source because the same slice
+ # can be used for both the layer and the control values.
+ source = ds_list[0]
+ ds = DataSource(source.ds)
+
+ sl = slice(1, 3)
+ feats = ds[0][sl]
+
+ for fld_name in ds[0].fields:
+ test_vals = [feat.get(fld_name) for feat in feats]
+ control_vals = source.field_values[fld_name][sl]
+ self.assertEqual(control_vals, test_vals)
def test04_features(self):
"Testing Data Source Features."
@@ -95,7 +139,8 @@ def test04_features(self):
for layer in ds:
# Incrementing through each feature in the layer
for feat in layer:
- # Making sure the number of fields is what's expected.
+ # Making sure the number of fields, and the geometry type
+ # are what's expected.
self.assertEqual(source.nfld, len(list(feat)))
self.assertEqual(source.gtype, feat.geom_type)
@@ -105,7 +150,7 @@ def test04_features(self):
# a string value index for the feature.
self.assertEqual(True, isinstance(feat[k], v))
- # Testing __iter__ on the Feature
+ # Testing Feature.__iter__
for fld in feat: self.assertEqual(True, fld.name in source.fields.keys())
def test05_geometries(self):
@@ -123,8 +168,10 @@ def test05_geometries(self):
self.assertEqual(source.gtype, g.geom_type)
# Making sure the SpatialReference is as expected.
- self.assertEqual(source.srs_wkt, g.srs.wkt)
-
+ if hasattr(source, 'srs_wkt'):
+ self.assertEqual(source.srs_wkt, g.srs.wkt)
+
+
def suite():
s = unittest.TestSuite()
s.addTest(unittest.makeSuite(DataSourceTest))
View
5 django/contrib/gis/tests/test_gdal_geom.py
@@ -36,6 +36,11 @@ def test00_geomtype(self):
self.assertEqual(False, OGRGeomType(1) != OGRGeomType('point'))
self.assertEqual(True, OGRGeomType('POINT') != OGRGeomType(6))
+ # Testing the Django field name equivalent property.
+ self.assertEqual('PointField', OGRGeomType('Point').django)
+ self.assertEqual(None, OGRGeomType('Unknown').django)
+ self.assertEqual(None, OGRGeomType('none').django)
+
def test01a_wkt(self):
"Testing WKT output."
for g in wkt_out:
Please sign in to comment.
Something went wrong with that request. Please try again.