1
0
mirror of https://github.com/django/django.git synced 2025-07-03 17:29:12 +00:00

gis: gdal: Features may now be fetched from OGR layers that do not support 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
This commit is contained in:
Justin Bronn 2008-07-22 01:10:59 +00:00
parent 7011fc6d3c
commit 825d6edd69
17 changed files with 174 additions and 75 deletions

View File

@ -25,6 +25,7 @@ OGRERR_DICT = { 1 : (OGRException, 'Not enough data.'),
5 : (OGRException, 'Corrupt data.'),
6 : (OGRException, 'OGR failure.'),
7 : (SRSException, 'Unsupported SRS.'),
8 : (OGRException, 'Invalid handle.'),
}
OGRERR_NONE = 0

View File

@ -201,7 +201,13 @@ class OGRGeometry(object):
@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 File

@ -4,31 +4,42 @@ from django.contrib.gis.gdal.error import OGRException
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 @@ class OGRGeomType(object):
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 File

@ -14,7 +14,7 @@ from django.contrib.gis.gdal.prototypes.ds import \
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 @@ class Layer(object):
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 @@ class Layer(object):
"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 @@ class Layer(object):
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 File

@ -37,6 +37,7 @@ get_layer_defn = voidptr_output(lgdal.OGR_L_GetLayerDefn, [c_void_p])
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 File

@ -0,0 +1,4 @@
POINT_X,POINT_Y,NUM
1.0,2.0,5
5.0,23.0,17
100.0,523.5,23
1 POINT_X POINT_Y NUM
2 1.0 2.0 5
3 5.0 23.0 17
4 100.0 523.5 23

View File

@ -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 File

@ -3,27 +3,38 @@ from django.contrib.gis.gdal import DataSource, Envelope, OGRException, OGRIndex
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 @@ class DataSourceTest(unittest.TestCase):
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 @@ class DataSourceTest(unittest.TestCase):
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 @@ class DataSourceTest(unittest.TestCase):
# 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 @@ class DataSourceTest(unittest.TestCase):
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 @@ class DataSourceTest(unittest.TestCase):
# 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 @@ class DataSourceTest(unittest.TestCase):
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 File

@ -36,6 +36,11 @@ class OGRGeomTest(unittest.TestCase):
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: