test_ds.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import os
  2. import unittest
  3. from unittest import skipUnless
  4. from django.contrib.gis.gdal import HAS_GDAL
  5. from django.contrib.gis.geometry.test_data import get_ds_file, TestDS, TEST_DATA
  6. if HAS_GDAL:
  7. from django.contrib.gis.gdal import DataSource, Envelope, OGRGeometry, OGRException, OGRIndexError, GDAL_VERSION
  8. from django.contrib.gis.gdal.field import OFTReal, OFTInteger, OFTString
  9. # List of acceptable data sources.
  10. ds_list = (
  11. TestDS('test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile',
  12. fields={'dbl': OFTReal, 'int': OFTInteger, 'str': OFTString},
  13. extent=(-1.35011, 0.166623, -0.524093, 0.824508), # Got extent from QGIS
  14. srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]',
  15. field_values={'dbl': [float(i) for i in range(1, 6)], 'int': list(range(1, 6)), 'str': [str(i) for i in range(1, 6)]},
  16. fids=range(5)),
  17. TestDS('test_vrt', ext='vrt', nfeat=3, nfld=3, geom='POINT', gtype='Point25D', driver='VRT',
  18. fields={'POINT_X': OFTString, 'POINT_Y': OFTString, 'NUM': OFTString}, # VRT uses CSV, which all types are OFTString.
  19. extent=(1.0, 2.0, 100.0, 523.5), # Min/Max from CSV
  20. field_values={'POINT_X': ['1.0', '5.0', '100.0'], 'POINT_Y': ['2.0', '23.0', '523.5'], 'NUM': ['5', '17', '23']},
  21. fids=range(1, 4)),
  22. TestDS('test_poly', nfeat=3, nfld=3, geom='POLYGON', gtype=3,
  23. driver='ESRI Shapefile',
  24. fields={'float': OFTReal, 'int': OFTInteger, 'str': OFTString},
  25. extent=(-1.01513, -0.558245, 0.161876, 0.839637), # Got extent from QGIS
  26. srs_wkt='GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]'),
  27. )
  28. bad_ds = (TestDS('foo'),)
  29. @skipUnless(HAS_GDAL, "GDAL is required")
  30. class DataSourceTest(unittest.TestCase):
  31. def test01_valid_shp(self):
  32. "Testing valid SHP Data Source files."
  33. for source in ds_list:
  34. # Loading up the data source
  35. ds = DataSource(source.ds)
  36. # Making sure the layer count is what's expected (only 1 layer in a SHP file)
  37. self.assertEqual(1, len(ds))
  38. # Making sure GetName works
  39. self.assertEqual(source.ds, ds.name)
  40. # Making sure the driver name matches up
  41. self.assertEqual(source.driver, str(ds.driver))
  42. # Making sure indexing works
  43. try:
  44. ds[len(ds)]
  45. except OGRIndexError:
  46. pass
  47. else:
  48. self.fail('Expected an IndexError!')
  49. def test02_invalid_shp(self):
  50. "Testing invalid SHP files for the Data Source."
  51. for source in bad_ds:
  52. self.assertRaises(OGRException, DataSource, source.ds)
  53. def test03a_layers(self):
  54. "Testing Data Source Layers."
  55. for source in ds_list:
  56. ds = DataSource(source.ds)
  57. # Incrementing through each layer, this tests DataSource.__iter__
  58. for layer in ds:
  59. # Making sure we get the number of features we expect
  60. self.assertEqual(len(layer), source.nfeat)
  61. # Making sure we get the number of fields we expect
  62. self.assertEqual(source.nfld, layer.num_fields)
  63. self.assertEqual(source.nfld, len(layer.fields))
  64. # Testing the layer's extent (an Envelope), and its properties
  65. if source.driver == 'VRT' and (GDAL_VERSION >= (1, 7, 0) and GDAL_VERSION < (1, 7, 3)):
  66. # There's a known GDAL regression with retrieving the extent
  67. # of a VRT layer in versions 1.7.0-1.7.2:
  68. # http://trac.osgeo.org/gdal/ticket/3783
  69. pass
  70. else:
  71. self.assertEqual(True, isinstance(layer.extent, Envelope))
  72. self.assertAlmostEqual(source.extent[0], layer.extent.min_x, 5)
  73. self.assertAlmostEqual(source.extent[1], layer.extent.min_y, 5)
  74. self.assertAlmostEqual(source.extent[2], layer.extent.max_x, 5)
  75. self.assertAlmostEqual(source.extent[3], layer.extent.max_y, 5)
  76. # Now checking the field names.
  77. flds = layer.fields
  78. for f in flds:
  79. self.assertEqual(True, f in source.fields)
  80. # Negative FIDs are not allowed.
  81. self.assertRaises(OGRIndexError, layer.__getitem__, -1)
  82. self.assertRaises(OGRIndexError, layer.__getitem__, 50000)
  83. if hasattr(source, 'field_values'):
  84. fld_names = source.field_values.keys()
  85. # Testing `Layer.get_fields` (which uses Layer.__iter__)
  86. for fld_name in fld_names:
  87. self.assertEqual(source.field_values[fld_name], layer.get_fields(fld_name))
  88. # Testing `Layer.__getitem__`.
  89. for i, fid in enumerate(source.fids):
  90. feat = layer[fid]
  91. self.assertEqual(fid, feat.fid)
  92. # Maybe this should be in the test below, but we might as well test
  93. # the feature values here while in this loop.
  94. for fld_name in fld_names:
  95. self.assertEqual(source.field_values[fld_name][i], feat.get(fld_name))
  96. def test03b_layer_slice(self):
  97. "Test indexing and slicing on Layers."
  98. # Using the first data-source because the same slice
  99. # can be used for both the layer and the control values.
  100. source = ds_list[0]
  101. ds = DataSource(source.ds)
  102. sl = slice(1, 3)
  103. feats = ds[0][sl]
  104. for fld_name in ds[0].fields:
  105. test_vals = [feat.get(fld_name) for feat in feats]
  106. control_vals = source.field_values[fld_name][sl]
  107. self.assertEqual(control_vals, test_vals)
  108. def test03c_layer_references(self):
  109. """
  110. Ensure OGR objects keep references to the objects they belong to.
  111. """
  112. source = ds_list[0]
  113. # See ticket #9448.
  114. def get_layer():
  115. # This DataSource object is not accessible outside this
  116. # scope. However, a reference should still be kept alive
  117. # on the `Layer` returned.
  118. ds = DataSource(source.ds)
  119. return ds[0]
  120. # Making sure we can call OGR routines on the Layer returned.
  121. lyr = get_layer()
  122. self.assertEqual(source.nfeat, len(lyr))
  123. self.assertEqual(source.gtype, lyr.geom_type.num)
  124. # Same issue for Feature/Field objects, see #18640
  125. self.assertEqual(str(lyr[0]['str']), "1")
  126. def test04_features(self):
  127. "Testing Data Source Features."
  128. for source in ds_list:
  129. ds = DataSource(source.ds)
  130. # Incrementing through each layer
  131. for layer in ds:
  132. # Incrementing through each feature in the layer
  133. for feat in layer:
  134. # Making sure the number of fields, and the geometry type
  135. # are what's expected.
  136. self.assertEqual(source.nfld, len(list(feat)))
  137. self.assertEqual(source.gtype, feat.geom_type)
  138. # Making sure the fields match to an appropriate OFT type.
  139. for k, v in source.fields.items():
  140. # Making sure we get the proper OGR Field instance, using
  141. # a string value index for the feature.
  142. self.assertEqual(True, isinstance(feat[k], v))
  143. # Testing Feature.__iter__
  144. for fld in feat:
  145. self.assertEqual(True, fld.name in source.fields.keys())
  146. def test05_geometries(self):
  147. "Testing Geometries from Data Source Features."
  148. for source in ds_list:
  149. ds = DataSource(source.ds)
  150. # Incrementing through each layer and feature.
  151. for layer in ds:
  152. for feat in layer:
  153. g = feat.geom
  154. # Making sure we get the right Geometry name & type
  155. self.assertEqual(source.geom, g.geom_name)
  156. self.assertEqual(source.gtype, g.geom_type)
  157. # Making sure the SpatialReference is as expected.
  158. if hasattr(source, 'srs_wkt'):
  159. self.assertEqual(
  160. source.srs_wkt,
  161. # Depending on lib versions, WGS_84 might be WGS_1984
  162. g.srs.wkt.replace('SPHEROID["WGS_84"', 'SPHEROID["WGS_1984"')
  163. )
  164. def test06_spatial_filter(self):
  165. "Testing the Layer.spatial_filter property."
  166. ds = DataSource(get_ds_file('cities', 'shp'))
  167. lyr = ds[0]
  168. # When not set, it should be None.
  169. self.assertEqual(None, lyr.spatial_filter)
  170. # Must be set a/an OGRGeometry or 4-tuple.
  171. self.assertRaises(TypeError, lyr._set_spatial_filter, 'foo')
  172. # Setting the spatial filter with a tuple/list with the extent of
  173. # a buffer centering around Pueblo.
  174. self.assertRaises(ValueError, lyr._set_spatial_filter, list(range(5)))
  175. filter_extent = (-105.609252, 37.255001, -103.609252, 39.255001)
  176. lyr.spatial_filter = (-105.609252, 37.255001, -103.609252, 39.255001)
  177. self.assertEqual(OGRGeometry.from_bbox(filter_extent), lyr.spatial_filter)
  178. feats = [feat for feat in lyr]
  179. self.assertEqual(1, len(feats))
  180. self.assertEqual('Pueblo', feats[0].get('Name'))
  181. # Setting the spatial filter with an OGRGeometry for buffer centering
  182. # around Houston.
  183. filter_geom = OGRGeometry('POLYGON((-96.363151 28.763374,-94.363151 28.763374,-94.363151 30.763374,-96.363151 30.763374,-96.363151 28.763374))')
  184. lyr.spatial_filter = filter_geom
  185. self.assertEqual(filter_geom, lyr.spatial_filter)
  186. feats = [feat for feat in lyr]
  187. self.assertEqual(1, len(feats))
  188. self.assertEqual('Houston', feats[0].get('Name'))
  189. # Clearing the spatial filter by setting it to None. Now
  190. # should indicate that there are 3 features in the Layer.
  191. lyr.spatial_filter = None
  192. self.assertEqual(3, len(lyr))
  193. def test07_integer_overflow(self):
  194. "Testing that OFTReal fields, treated as OFTInteger, do not overflow."
  195. # Using *.dbf from Census 2010 TIGER Shapefile for Texas,
  196. # which has land area ('ALAND10') stored in a Real field
  197. # with no precision.
  198. ds = DataSource(os.path.join(TEST_DATA, 'texas.dbf'))
  199. feat = ds[0][0]
  200. # Reference value obtained using `ogrinfo`.
  201. self.assertEqual(676586997978, feat.get('ALAND10'))