1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49: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.'), 5 : (OGRException, 'Corrupt data.'),
6 : (OGRException, 'OGR failure.'), 6 : (OGRException, 'OGR failure.'),
7 : (SRSException, 'Unsupported SRS.'), 7 : (SRSException, 'Unsupported SRS.'),
8 : (OGRException, 'Invalid handle.'),
} }
OGRERR_NONE = 0 OGRERR_NONE = 0

View File

@ -201,7 +201,13 @@ class OGRGeometry(object):
@property @property
def geom_type(self): def geom_type(self):
"Returns the Type for this Geometry." "Returns the Type for this Geometry."
try:
return OGRGeomType(get_geom_type(self._ptr)) 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 @property
def geom_name(self): def geom_name(self):

View File

@ -4,31 +4,42 @@ from django.contrib.gis.gdal.error import OGRException
class OGRGeomType(object): class OGRGeomType(object):
"Encapulates OGR Geometry Types." "Encapulates OGR Geometry Types."
# Ordered array of acceptable strings and their corresponding OGRwkbGeometryType # Dictionary of acceptable OGRwkbGeometryType s and their string names.
__ogr_str = ['Unknown', 'Point', 'LineString', 'Polygon', 'MultiPoint', _types = {0 : 'Unknown',
'MultiLineString', 'MultiPolygon', 'GeometryCollection', 1 : 'Point',
'LinearRing'] 2 : 'LineString',
__ogr_int = [0, 1, 2, 3, 4, 5, 6, 7, 101] 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): def __init__(self, type_input):
"Figures out the correct OGR Type based upon the input." "Figures out the correct OGR Type based upon the input."
if isinstance(type_input, OGRGeomType): if isinstance(type_input, OGRGeomType):
self._index = type_input._index num = type_input.num
elif isinstance(type_input, basestring): elif isinstance(type_input, basestring):
idx = self._has_str(self.__ogr_str, type_input) num = self._str_types.get(type_input.lower(), None)
if idx == None: if num is None:
raise OGRException('Invalid OGR String Type "%s"' % type_input) raise OGRException('Invalid OGR String Type "%s"' % type_input)
self._index = idx
elif isinstance(type_input, int): 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) raise OGRException('Invalid OGR Integer Type: %d' % type_input)
self._index = self.__ogr_int.index(type_input) num = type_input
else: else:
raise TypeError('Invalid OGR input type given.') raise TypeError('Invalid OGR input type given.')
# Setting the OGR geometry type number.
self.num = num
def __str__(self): def __str__(self):
"Returns a short-hand string form of the OGR Geometry type." "Returns the value of the name property."
return self.__ogr_str[self._index] return self.name
def __eq__(self, other): def __eq__(self, other):
""" """
@ -36,37 +47,27 @@ class OGRGeomType(object):
other OGRGeomType, the short-hand string, or the integer. other OGRGeomType, the short-hand string, or the integer.
""" """
if isinstance(other, OGRGeomType): if isinstance(other, OGRGeomType):
return self._index == other._index return self.num == other.num
elif isinstance(other, basestring): elif isinstance(other, basestring):
idx = self._has_str(self.__ogr_str, other) return self.name.lower() == other.lower()
if not (idx == None): return self._index == idx
return False
elif isinstance(other, int): elif isinstance(other, int):
if not other in self.__ogr_int: return False return self.num == other
return self.__ogr_int.index(other) == self._index
else: else:
raise TypeError('Cannot compare with type: %s' % str(type(other))) return False
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
def _has_str(self, arr, s): @property
"Case-insensitive search of the string array for the given pattern." def name(self):
s_low = s.lower() "Returns a short-hand string form of the OGR Geometry type."
for i in xrange(len(arr)): return self._types[self.num]
if s_low == arr[i].lower(): return i
return None
@property @property
def django(self): def django(self):
"Returns the Django GeometryField for this OGR Type." "Returns the Django GeometryField for this OGR Type."
s = self.__ogr_str[self._index] s = self.name
if s in ('Unknown', 'LinearRing'): if s in ('Unknown', 'LinearRing', 'None'):
return None return None
else: else:
return s + 'Field' 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_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_count, get_field_defn, get_field_name, get_field_precision, \
get_field_width, get_field_type, get_layer_defn, get_layer_srs, \ 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 from django.contrib.gis.gdal.prototypes.srs import clone_srs
# For more information, see the OGR C API source code: # 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') raise OGRException('Cannot create Layer, invalid pointer given')
self._ptr = layer_ptr self._ptr = layer_ptr
self._ldefn = get_layer_defn(self._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): def __getitem__(self, index):
"Gets the Feature at the specified index." "Gets the Feature at the specified index."
if not isinstance(index, (slice, int)): if isinstance(index, (int, long)):
raise TypeError # An integer index was given -- we cannot do a check based on the
end = self.num_feat # number of features because the beginning and ending feature IDs
if isinstance(index,int): # are not guaranteed to be 0 and len(layer)-1, respectively.
# An integer index was given if index < 0: raise OGRIndexError('Negative indices are not allowed on OGR Layers.')
if index < 0:
index = end - index
if index < 0 or index >= self.num_feat:
raise OGRIndexError('index out of range')
return self._make_feature(index) return self._make_feature(index)
else: elif isinstance(index, slice):
# A slice was given # A slice was given
start, stop, stride = index.indices(end) start, stop, stride = index.indices(self.num_feat)
return [self._make_feature(offset) for offset in range(start,stop,stride)] 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): def __iter__(self):
"Iterates over each Feature in the Layer." "Iterates over each Feature in the Layer."
# ResetReading() must be called before iteration is to begin. # ResetReading() must be called before iteration is to begin.
reset_reading(self._ptr) 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) yield Feature(get_next_feature(self._ptr), self._ldefn)
def __len__(self): def __len__(self):
@ -65,9 +65,26 @@ class Layer(object):
"The string name of the layer." "The string name of the layer."
return self.name return self.name
def _make_feature(self, offset): def _make_feature(self, feat_id):
"Helper routine for __getitem__ that makes a feature from an offset." """
return Feature(get_feature(self._ptr, offset), self._ldefn) 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 #### #### Layer properties ####
@property @property
@ -158,3 +175,13 @@ class Layer(object):
return [GEOSGeometry(feat.geom.wkb) for feat in self] return [GEOSGeometry(feat.geom.wkb) for feat in self]
else: else:
return [feat.geom for feat in self] 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_layer_srs = srs_output(lgdal.OGR_L_GetSpatialRef, [c_void_p])
get_next_feature = voidptr_output(lgdal.OGR_L_GetNextFeature, [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) 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 ### ### Feature Definition Routines ###
get_fd_geom_type = int_output(lgdal.OGR_FD_GetGeomType, [c_void_p]) 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 from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
# Path for SHP files # Path for SHP files
shp_path = os.path.dirname(__file__) data_path = os.path.join(os.path.dirname(__file__), 'data')
def get_shp(name): def get_ds_file(name, ext):
return shp_path + os.sep + name + os.sep + name + '.shp' return os.sep.join([data_path, name, name + '.%s' % ext])
# Test SHP data source object # Test SHP data source object
class TestSHP: class TestDS:
def __init__(self, shp, **kwargs): def __init__(self, name, **kwargs):
self.ds = get_shp(shp) ext = kwargs.pop('ext', 'shp')
self.ds = get_ds_file(name, ext)
for key, value in kwargs.items(): for key, value in kwargs.items():
setattr(self, key, value) setattr(self, key, value)
# List of acceptable data sources. # 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,}, 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 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]]'), 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,}, 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 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]]'), 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): class DataSourceTest(unittest.TestCase):
@ -42,7 +53,7 @@ class DataSourceTest(unittest.TestCase):
self.assertEqual(source.ds, ds.name) self.assertEqual(source.ds, ds.name)
# Making sure the driver name matches up # 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 # Making sure indexing works
try: try:
@ -57,20 +68,17 @@ class DataSourceTest(unittest.TestCase):
for source in bad_ds: for source in bad_ds:
self.assertRaises(OGRException, DataSource, source.ds) self.assertRaises(OGRException, DataSource, source.ds)
def test03_layers(self): def test03a_layers(self):
"Testing Data Source Layers." "Testing Data Source Layers."
print "\nBEGIN - expecting out of range feature id error; safe to ignore.\n"
for source in ds_list: for source in ds_list:
ds = DataSource(source.ds) ds = DataSource(source.ds)
# Incrementing through each layer, this tests __iter__ # Incrementing through each layer, this tests DataSource.__iter__
for layer in ds: for layer in ds:
# Making sure we get the number of features we expect # Making sure we get the number of features we expect
self.assertEqual(len(layer), source.nfeat) self.assertEqual(len(layer), source.nfeat)
layer[0] #can index
layer[:1] #can slice
# Making sure we get the number of fields we expect # Making sure we get the number of fields we expect
self.assertEqual(source.nfld, layer.num_fields) self.assertEqual(source.nfld, layer.num_fields)
self.assertEqual(source.nfld, len(layer.fields)) self.assertEqual(source.nfld, len(layer.fields))
@ -86,6 +94,42 @@ class DataSourceTest(unittest.TestCase):
flds = layer.fields flds = layer.fields
for f in flds: self.assertEqual(True, f in source.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): def test04_features(self):
"Testing Data Source Features." "Testing Data Source Features."
for source in ds_list: for source in ds_list:
@ -95,7 +139,8 @@ class DataSourceTest(unittest.TestCase):
for layer in ds: for layer in ds:
# Incrementing through each feature in the layer # Incrementing through each feature in the layer
for feat in 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.nfld, len(list(feat)))
self.assertEqual(source.gtype, feat.geom_type) self.assertEqual(source.gtype, feat.geom_type)
@ -105,7 +150,7 @@ class DataSourceTest(unittest.TestCase):
# a string value index for the feature. # a string value index for the feature.
self.assertEqual(True, isinstance(feat[k], v)) 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()) for fld in feat: self.assertEqual(True, fld.name in source.fields.keys())
def test05_geometries(self): def test05_geometries(self):
@ -123,8 +168,10 @@ class DataSourceTest(unittest.TestCase):
self.assertEqual(source.gtype, g.geom_type) self.assertEqual(source.gtype, g.geom_type)
# Making sure the SpatialReference is as expected. # Making sure the SpatialReference is as expected.
if hasattr(source, 'srs_wkt'):
self.assertEqual(source.srs_wkt, g.srs.wkt) self.assertEqual(source.srs_wkt, g.srs.wkt)
def suite(): def suite():
s = unittest.TestSuite() s = unittest.TestSuite()
s.addTest(unittest.makeSuite(DataSourceTest)) s.addTest(unittest.makeSuite(DataSourceTest))

View File

@ -36,6 +36,11 @@ class OGRGeomTest(unittest.TestCase):
self.assertEqual(False, OGRGeomType(1) != OGRGeomType('point')) self.assertEqual(False, OGRGeomType(1) != OGRGeomType('point'))
self.assertEqual(True, OGRGeomType('POINT') != OGRGeomType(6)) 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): def test01a_wkt(self):
"Testing WKT output." "Testing WKT output."
for g in wkt_out: for g in wkt_out: