test_ds.py 11 KB

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