GdalAlgorithmsGeneralTest.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. """
  2. ***************************************************************************
  3. GdalAlgorithmTests.py
  4. ---------------------
  5. Date : January 2016
  6. Copyright : (C) 2016 by Matthias Kuhn
  7. Email : matthias@opengis.ch
  8. ***************************************************************************
  9. * *
  10. * This program is free software; you can redistribute it and/or modify *
  11. * it under the terms of the GNU General Public License as published by *
  12. * the Free Software Foundation; either version 2 of the License, or *
  13. * (at your option) any later version. *
  14. * *
  15. ***************************************************************************
  16. """
  17. __author__ = 'Matthias Kuhn'
  18. __date__ = 'January 2016'
  19. __copyright__ = '(C) 2016, Matthias Kuhn'
  20. import nose2
  21. import os
  22. import shutil
  23. import tempfile
  24. from qgis.core import (QgsProcessingContext,
  25. QgsProcessingFeedback,
  26. QgsCoordinateReferenceSystem,
  27. QgsApplication,
  28. QgsFeature,
  29. QgsGeometry,
  30. QgsPointXY,
  31. QgsProject,
  32. QgsVectorLayer,
  33. QgsRectangle,
  34. QgsProjUtils,
  35. QgsProcessingException,
  36. QgsProcessingFeatureSourceDefinition)
  37. from qgis.testing import (start_app,
  38. unittest)
  39. from processing.algs.gdal.GdalUtils import GdalUtils
  40. from processing.algs.gdal.ogr2ogr import ogr2ogr
  41. from processing.algs.gdal.OgrToPostGis import OgrToPostGis
  42. testDataPath = os.path.join(os.path.dirname(__file__), 'testdata')
  43. class TestGdalAlgorithms(unittest.TestCase):
  44. @classmethod
  45. def setUpClass(cls):
  46. start_app()
  47. from processing.core.Processing import Processing
  48. Processing.initialize()
  49. cls.cleanup_paths = []
  50. @classmethod
  51. def tearDownClass(cls):
  52. for path in cls.cleanup_paths:
  53. shutil.rmtree(path)
  54. def testCommandName(self):
  55. # Test that algorithms report a valid commandName
  56. p = QgsApplication.processingRegistry().providerById('gdal')
  57. for a in p.algorithms():
  58. if a.id() in ('gdal:buildvirtualvector'):
  59. # build virtual vector is an exception
  60. continue
  61. self.assertTrue(a.commandName(), f'Algorithm {a.id()} has no commandName!')
  62. def testCommandNameInTags(self):
  63. # Test that algorithms commandName is present in provided tags
  64. p = QgsApplication.processingRegistry().providerById('gdal')
  65. for a in p.algorithms():
  66. if not a.commandName():
  67. continue
  68. self.assertTrue(a.commandName() in a.tags(), f'Algorithm {a.id()} commandName not found in tags!')
  69. def testNoParameters(self):
  70. # Test that algorithms throw QgsProcessingException and not base Python
  71. # exceptions when no parameters specified
  72. p = QgsApplication.processingRegistry().providerById('gdal')
  73. context = QgsProcessingContext()
  74. feedback = QgsProcessingFeedback()
  75. for a in p.algorithms():
  76. try:
  77. a.getConsoleCommands({}, context, feedback)
  78. except QgsProcessingException:
  79. pass
  80. def testGetOgrCompatibleSourceFromMemoryLayer(self):
  81. # create a memory layer and add to project and context
  82. layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
  83. "testmem", "memory")
  84. self.assertTrue(layer.isValid())
  85. pr = layer.dataProvider()
  86. f = QgsFeature()
  87. f.setAttributes(["test", 123])
  88. f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
  89. f2 = QgsFeature()
  90. f2.setAttributes(["test2", 457])
  91. f2.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
  92. self.assertTrue(pr.addFeatures([f, f2]))
  93. self.assertEqual(layer.featureCount(), 2)
  94. QgsProject.instance().addMapLayer(layer)
  95. context = QgsProcessingContext()
  96. context.setProject(QgsProject.instance())
  97. alg = QgsApplication.processingRegistry().createAlgorithmById('gdal:buffervectors')
  98. self.assertIsNotNone(alg)
  99. parameters = {'INPUT': 'testmem'}
  100. feedback = QgsProcessingFeedback()
  101. # check that memory layer is automatically saved out to geopackage when required by GDAL algorithms
  102. ogr_data_path, ogr_layer_name = alg.getOgrCompatibleSource('INPUT', parameters, context, feedback,
  103. executing=True)
  104. self.assertTrue(ogr_data_path)
  105. self.assertTrue(ogr_data_path.endswith('.gpkg'))
  106. self.assertTrue(os.path.exists(ogr_data_path))
  107. self.assertTrue(ogr_layer_name)
  108. # make sure that layer has correct features
  109. res = QgsVectorLayer(ogr_data_path, 'res')
  110. self.assertTrue(res.isValid())
  111. self.assertEqual(res.featureCount(), 2)
  112. # with memory layers - if not executing layer source should be ignored and replaced
  113. # with a dummy path, because:
  114. # - it has no meaning for the gdal command outside of QGIS, memory layers don't exist!
  115. # - we don't want to force an export of the whole memory layer to a temp file just to show the command preview
  116. # this might be very slow!
  117. ogr_data_path, ogr_layer_name = alg.getOgrCompatibleSource('INPUT', parameters, context, feedback,
  118. executing=False)
  119. self.assertEqual(ogr_data_path, 'path_to_data_file')
  120. self.assertEqual(ogr_layer_name, 'layer_name')
  121. QgsProject.instance().removeMapLayer(layer)
  122. def testGetOgrCompatibleSourceFromOgrLayer(self):
  123. p = QgsProject()
  124. source = os.path.join(testDataPath, 'points.gml')
  125. vl = QgsVectorLayer(source)
  126. self.assertTrue(vl.isValid())
  127. p.addMapLayer(vl)
  128. context = QgsProcessingContext()
  129. context.setProject(p)
  130. feedback = QgsProcessingFeedback()
  131. alg = ogr2ogr()
  132. alg.initAlgorithm()
  133. path, layer = alg.getOgrCompatibleSource('INPUT', {'INPUT': vl.id()}, context, feedback, True)
  134. self.assertEqual(path, source)
  135. path, layer = alg.getOgrCompatibleSource('INPUT', {'INPUT': vl.id()}, context, feedback, False)
  136. self.assertEqual(path, source)
  137. # with selected features only - if not executing, the 'selected features only' setting
  138. # should be ignored (because it has no meaning for the gdal command outside of QGIS!)
  139. parameters = {'INPUT': QgsProcessingFeatureSourceDefinition(vl.id(), True)}
  140. path, layer = alg.getOgrCompatibleSource('INPUT', parameters, context, feedback, False)
  141. self.assertEqual(path, source)
  142. # with subset string
  143. vl.setSubsetString('x')
  144. path, layer = alg.getOgrCompatibleSource('INPUT', parameters, context, feedback, False)
  145. self.assertEqual(path, source)
  146. # subset of layer must be exported
  147. path, layer = alg.getOgrCompatibleSource('INPUT', parameters, context, feedback, True)
  148. self.assertNotEqual(path, source)
  149. self.assertTrue(path)
  150. self.assertTrue(path.endswith('.gpkg'))
  151. self.assertTrue(os.path.exists(path))
  152. self.assertTrue(layer)
  153. # geopackage with layer
  154. source = os.path.join(testDataPath, 'custom', 'circular_strings.gpkg')
  155. vl2 = QgsVectorLayer(source + '|layername=circular_strings')
  156. self.assertTrue(vl2.isValid())
  157. p.addMapLayer(vl2)
  158. path, layer = alg.getOgrCompatibleSource('INPUT', {'INPUT': vl2.id()}, context, feedback, True)
  159. self.assertEqual(path, source)
  160. self.assertEqual(layer, 'circular_strings')
  161. vl3 = QgsVectorLayer(source + '|layername=circular_strings_with_line')
  162. self.assertTrue(vl3.isValid())
  163. p.addMapLayer(vl3)
  164. path, layer = alg.getOgrCompatibleSource('INPUT', {'INPUT': vl3.id()}, context, feedback, True)
  165. self.assertEqual(path, source)
  166. self.assertEqual(layer, 'circular_strings_with_line')
  167. def testGetOgrCompatibleSourceFromFeatureSource(self):
  168. # create a memory layer and add to project and context
  169. layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
  170. "testmem", "memory")
  171. self.assertTrue(layer.isValid())
  172. pr = layer.dataProvider()
  173. f = QgsFeature()
  174. f.setAttributes(["test", 123])
  175. f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
  176. f2 = QgsFeature()
  177. f2.setAttributes(["test2", 457])
  178. f2.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
  179. self.assertTrue(pr.addFeatures([f, f2]))
  180. self.assertEqual(layer.featureCount(), 2)
  181. # select first feature
  182. layer.selectByIds([next(layer.getFeatures()).id()])
  183. self.assertEqual(len(layer.selectedFeatureIds()), 1)
  184. QgsProject.instance().addMapLayer(layer)
  185. context = QgsProcessingContext()
  186. context.setProject(QgsProject.instance())
  187. alg = QgsApplication.processingRegistry().createAlgorithmById('gdal:buffervectors')
  188. self.assertIsNotNone(alg)
  189. parameters = {'INPUT': QgsProcessingFeatureSourceDefinition('testmem', True)}
  190. feedback = QgsProcessingFeedback()
  191. # check that memory layer is automatically saved out to geopackage when required by GDAL algorithms
  192. ogr_data_path, ogr_layer_name = alg.getOgrCompatibleSource('INPUT', parameters, context, feedback,
  193. executing=True)
  194. self.assertTrue(ogr_data_path)
  195. self.assertTrue(ogr_data_path.endswith('.gpkg'))
  196. self.assertTrue(os.path.exists(ogr_data_path))
  197. self.assertTrue(ogr_layer_name)
  198. # make sure that layer has only selected feature
  199. res = QgsVectorLayer(ogr_data_path, 'res')
  200. self.assertTrue(res.isValid())
  201. self.assertEqual(res.featureCount(), 1)
  202. QgsProject.instance().removeMapLayer(layer)
  203. def testOgrOutputLayerName(self):
  204. self.assertEqual(GdalUtils.ogrOutputLayerName('/home/me/out.shp'), 'out')
  205. self.assertEqual(GdalUtils.ogrOutputLayerName('d:/test/test_out.shp'), 'test_out')
  206. self.assertEqual(GdalUtils.ogrOutputLayerName('d:/test/TEST_OUT.shp'), 'TEST_OUT')
  207. self.assertEqual(GdalUtils.ogrOutputLayerName('d:/test/test_out.gpkg'), 'test_out')
  208. def testOgrLayerNameExtraction(self):
  209. with tempfile.TemporaryDirectory() as outdir:
  210. def _copyFile(dst):
  211. shutil.copyfile(os.path.join(testDataPath, 'custom', 'weighted.csv'), dst)
  212. # OGR provider - single layer
  213. _copyFile(os.path.join(outdir, 'a.csv'))
  214. name = GdalUtils.ogrLayerName(outdir)
  215. self.assertEqual(name, 'a')
  216. # OGR provider - multiple layers
  217. _copyFile(os.path.join(outdir, 'b.csv'))
  218. name1 = GdalUtils.ogrLayerName(outdir + '|layerid=0')
  219. name2 = GdalUtils.ogrLayerName(outdir + '|layerid=1')
  220. self.assertEqual(sorted([name1, name2]), ['a', 'b'])
  221. name = GdalUtils.ogrLayerName(outdir + '|layerid=2')
  222. self.assertIsNone(name)
  223. # OGR provider - layername takes precedence
  224. name = GdalUtils.ogrLayerName(outdir + '|layername=f')
  225. self.assertEqual(name, 'f')
  226. name = GdalUtils.ogrLayerName(outdir + '|layerid=0|layername=f')
  227. self.assertEqual(name, 'f')
  228. name = GdalUtils.ogrLayerName(outdir + '|layername=f|layerid=0')
  229. self.assertEqual(name, 'f')
  230. # SQLite provider
  231. name = GdalUtils.ogrLayerName('dbname=\'/tmp/x.sqlite\' table="t" (geometry) sql=')
  232. self.assertEqual(name, 't')
  233. # PostgreSQL provider
  234. name = GdalUtils.ogrLayerName(
  235. 'port=5493 sslmode=disable key=\'edge_id\' srid=0 type=LineString table="city_data"."edge" (geom) sql=')
  236. self.assertEqual(name, 'city_data.edge')
  237. def testOgrConnectionStringAndFormat(self):
  238. context = QgsProcessingContext()
  239. output, outputFormat = GdalUtils.ogrConnectionStringAndFormat('d:/test/test.shp', context)
  240. self.assertEqual(output, 'd:/test/test.shp')
  241. self.assertEqual(outputFormat, '"ESRI Shapefile"')
  242. output, outputFormat = GdalUtils.ogrConnectionStringAndFormat('d:/test/test.mif', context)
  243. self.assertEqual(output, 'd:/test/test.mif')
  244. self.assertEqual(outputFormat, '"MapInfo File"')
  245. def testConnectionString(self):
  246. alg = OgrToPostGis()
  247. alg.initAlgorithm()
  248. parameters = {}
  249. feedback = QgsProcessingFeedback()
  250. context = QgsProcessingContext()
  251. # NOTE: defaults are debatable, see
  252. # https://github.com/qgis/QGIS/pull/3607#issuecomment-253971020
  253. self.assertEqual(alg.getConnectionString(parameters, context),
  254. "host=localhost port=5432 active_schema=public")
  255. parameters['HOST'] = 'remote'
  256. self.assertEqual(alg.getConnectionString(parameters, context),
  257. "host=remote port=5432 active_schema=public")
  258. parameters['HOST'] = ''
  259. self.assertEqual(alg.getConnectionString(parameters, context),
  260. "port=5432 active_schema=public")
  261. parameters['PORT'] = '5555'
  262. self.assertEqual(alg.getConnectionString(parameters, context),
  263. "port=5555 active_schema=public")
  264. parameters['PORT'] = ''
  265. self.assertEqual(alg.getConnectionString(parameters, context),
  266. "active_schema=public")
  267. parameters['USER'] = 'usr'
  268. self.assertEqual(alg.getConnectionString(parameters, context),
  269. "active_schema=public user=usr")
  270. parameters['PASSWORD'] = 'pwd'
  271. self.assertEqual(alg.getConnectionString(parameters, context),
  272. "password=pwd active_schema=public user=usr")
  273. def testCrsConversion(self):
  274. self.assertFalse(GdalUtils.gdal_crs_string(QgsCoordinateReferenceSystem()))
  275. self.assertEqual(GdalUtils.gdal_crs_string(QgsCoordinateReferenceSystem('EPSG:3111')), 'EPSG:3111')
  276. self.assertEqual(GdalUtils.gdal_crs_string(QgsCoordinateReferenceSystem('POSTGIS:3111')), 'EPSG:3111')
  277. self.assertEqual(GdalUtils.gdal_crs_string(QgsCoordinateReferenceSystem(
  278. 'proj4: +proj=utm +zone=36 +south +a=6378249.145 +b=6356514.966398753 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')),
  279. 'EPSG:20936')
  280. crs = QgsCoordinateReferenceSystem()
  281. crs.createFromProj(
  282. '+proj=utm +zone=36 +south +a=600000 +b=70000 +towgs84=-143,-90,-294,0,0,0,0 +units=m +no_defs')
  283. self.assertTrue(crs.isValid())
  284. # proj 6, WKT should be used
  285. self.assertEqual(GdalUtils.gdal_crs_string(crs)[:40], 'BOUNDCRS[SOURCECRS[PROJCRS["unknown",BAS')
  286. self.assertEqual(GdalUtils.gdal_crs_string(QgsCoordinateReferenceSystem('ESRI:102003')), 'ESRI:102003')
  287. def testEscapeAndJoin(self):
  288. self.assertEqual(GdalUtils.escapeAndJoin([1, "a", "a b", "a&b", "a(b)", ";"]), '1 a "a b" "a&b" "a(b)" ";"')
  289. self.assertEqual(GdalUtils.escapeAndJoin([1, "-srcnodata", "--srcnodata", "-9999 9999"]), '1 -srcnodata --srcnodata "-9999 9999"')
  290. if __name__ == '__main__':
  291. nose2.main()