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:
parent
7011fc6d3c
commit
825d6edd69
@ -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
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
@ -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))
|
||||
|
@ -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])
|
||||
|
4
django/contrib/gis/tests/data/test_vrt/test_vrt.csv
Normal file
4
django/contrib/gis/tests/data/test_vrt/test_vrt.csv
Normal 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
|
|
7
django/contrib/gis/tests/data/test_vrt/test_vrt.vrt
Normal file
7
django/contrib/gis/tests/data/test_vrt/test_vrt.vrt
Normal 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>
|
@ -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))
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user