1
2# -*- coding: utf-8 -*-
3"""QGIS Unit tests for QgsDatumTransforms.
4
5.. note:: This program is free software; you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation; either version 2 of the License, or
8(at your option) any later version.
9"""
10__author__ = 'Nyall Dawson'
11__date__ = '2019-05-25'
12__copyright__ = 'Copyright 2019, The QGIS Project'
13
14from qgis.core import (
15    QgsProjUtils,
16    QgsCoordinateReferenceSystem,
17    QgsDatumTransform
18)
19from qgis.testing import (start_app,
20                          unittest,
21                          )
22from utilities import unitTestDataPath
23
24start_app()
25TEST_DATA_DIR = unitTestDataPath()
26
27
28class TestPyQgsDatumTransform(unittest.TestCase):
29
30    @unittest.skipIf(QgsProjUtils.projVersionMajor() < 6, 'Not a proj6 build')
31    def testOperations(self):
32        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem(),
33                                           QgsCoordinateReferenceSystem())
34        self.assertEqual(ops, [])
35        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:3111'),
36                                           QgsCoordinateReferenceSystem())
37        self.assertEqual(ops, [])
38        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem(),
39                                           QgsCoordinateReferenceSystem('EPSG:3111'))
40        self.assertEqual(ops, [])
41
42        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:3111'),
43                                           QgsCoordinateReferenceSystem('EPSG:3111'))
44        self.assertEqual(len(ops), 1)
45        self.assertTrue(ops[0].name)
46        self.assertEqual(ops[0].proj, '+proj=noop')
47        self.assertEqual(ops[0].accuracy, 0.0)
48        self.assertTrue(ops[0].isAvailable)
49
50        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:3111'),
51                                           QgsCoordinateReferenceSystem('EPSG:4283'))
52        self.assertEqual(len(ops), 1)
53        self.assertTrue(ops[0].name)
54        self.assertEqual(ops[0].proj, '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=unitconvert +xy_in=rad +xy_out=deg')
55        self.assertEqual(ops[0].accuracy, -1.0)
56        self.assertTrue(ops[0].isAvailable)
57
58        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:3111'),
59                                           QgsCoordinateReferenceSystem('EPSG:28355'))
60        self.assertEqual(len(ops), 1)
61        self.assertTrue(ops[0].name)
62        self.assertEqual(ops[0].proj, '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=utm +zone=55 +south +ellps=GRS80')
63        self.assertEqual(ops[0].accuracy, 0.0)
64        self.assertTrue(ops[0].isAvailable)
65
66        # uses a grid file
67        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:4283'),
68                                           QgsCoordinateReferenceSystem('EPSG:7844'))
69        self.assertGreaterEqual(len(ops), 5)
70
71        op1_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=GRS80 +step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 +rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 +convention=coordinate_frame +step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg'][0]
72        self.assertTrue(ops[op1_index].name)
73        self.assertEqual(ops[op1_index].proj, '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=GRS80 +step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 +rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 +convention=coordinate_frame +step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg')
74        self.assertTrue(ops[op1_index].isAvailable)
75        self.assertEqual(ops[op1_index].accuracy, 0.01)
76        self.assertEqual(len(ops[op1_index].grids), 0)
77
78        if QgsProjUtils.projVersionMajor() == 6:
79            op2_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_and_distortion.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg'][0]
80        else:
81            op2_index = [i for i in range(len(ops)) if ops[
82                i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_and_distortion.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg'][
83                0]
84        self.assertTrue(ops[op2_index].name)
85        if QgsProjUtils.projVersionMajor() == 6:
86            self.assertEqual(ops[op2_index].proj, '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_and_distortion.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg')
87        else:
88            self.assertEqual(ops[op2_index].proj,
89                             '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_and_distortion.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg')
90        self.assertEqual(ops[op2_index].accuracy, 0.05)
91        self.assertEqual(len(ops[op2_index].grids), 1)
92        if QgsProjUtils.projVersionMajor() == 6:
93            self.assertEqual(ops[op2_index].grids[0].shortName, 'GDA94_GDA2020_conformal_and_distortion.gsb')
94        else:
95            self.assertEqual(ops[op2_index].grids[0].shortName, 'au_icsm_GDA94_GDA2020_conformal_and_distortion.tif')
96        self.assertEqual(ops[op2_index].grids[0].fullName, '')
97        if QgsProjUtils.projVersionMajor() == 6:
98            self.assertTrue(ops[op2_index].grids[0].packageName)
99        self.assertIn('http', ops[op2_index].grids[0].url)
100        self.assertTrue(ops[op2_index].grids[0].directDownload)
101        self.assertTrue(ops[op2_index].grids[0].openLicense)
102
103        if QgsProjUtils.projVersionMajor() == 6:
104            op3_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg'][0]
105        else:
106            op3_index = [i for i in range(len(ops)) if ops[
107                i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg'][
108                0]
109        self.assertTrue(ops[op3_index].name)
110        if QgsProjUtils.projVersionMajor() == 6:
111            self.assertEqual(ops[op3_index].proj, '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg')
112        else:
113            self.assertEqual(ops[op3_index].proj,
114                             '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg')
115        self.assertEqual(ops[op3_index].accuracy, 0.05)
116        self.assertEqual(len(ops[op3_index].grids), 1)
117        if QgsProjUtils.projVersionMajor() == 6:
118            self.assertEqual(ops[op3_index].grids[0].shortName, 'GDA94_GDA2020_conformal.gsb')
119        else:
120            self.assertEqual(ops[op3_index].grids[0].shortName, 'au_icsm_GDA94_GDA2020_conformal.tif')
121        self.assertEqual(ops[op3_index].grids[0].fullName, '')
122        if QgsProjUtils.projVersionMajor() == 6:
123            self.assertTrue(ops[op3_index].grids[0].packageName)
124        self.assertIn('http', ops[op3_index].grids[0].url)
125        self.assertTrue(ops[op3_index].grids[0].directDownload)
126        self.assertTrue(ops[op3_index].grids[0].openLicense)
127
128        if QgsProjUtils.projVersionMajor() == 6:
129            op4_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_cocos_island.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg'][0]
130        else:
131            op4_index = [i for i in range(len(ops)) if ops[
132                i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_cocos_island.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg'][
133                0]
134        self.assertTrue(ops[op4_index].name)
135        if QgsProjUtils.projVersionMajor() == 6:
136            self.assertEqual(ops[op4_index].proj, '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_cocos_island.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg')
137        else:
138            self.assertEqual(ops[op4_index].proj,
139                             '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_cocos_island.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg')
140        self.assertEqual(ops[op4_index].accuracy, 0.05)
141        self.assertEqual(len(ops[op4_index].grids), 1)
142        if QgsProjUtils.projVersionMajor() == 6:
143            self.assertEqual(ops[op4_index].grids[0].shortName, 'GDA94_GDA2020_conformal_cocos_island.gsb')
144        else:
145            self.assertEqual(ops[op4_index].grids[0].shortName, 'au_icsm_GDA94_GDA2020_conformal_cocos_island.tif')
146        self.assertEqual(ops[op4_index].grids[0].fullName, '')
147        if QgsProjUtils.projVersionMajor() == 6:
148            self.assertTrue(ops[op4_index].grids[0].packageName)
149        self.assertIn('http', ops[op4_index].grids[0].url)
150
151        if QgsProjUtils.projVersionMajor() == 6:
152            op5_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_christmas_island.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg'][0]
153        else:
154            op5_index = [i for i in range(len(ops)) if ops[
155                i].proj == '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_christmas_island.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg'][
156                0]
157        self.assertTrue(ops[op5_index].name)
158        if QgsProjUtils.projVersionMajor() == 6:
159            self.assertEqual(ops[op5_index].proj, '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_christmas_island.gsb +step +proj=unitconvert +xy_in=rad +xy_out=deg')
160        else:
161            self.assertEqual(ops[op5_index].proj,
162                             '+proj=pipeline +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_christmas_island.tif +step +proj=unitconvert +xy_in=rad +xy_out=deg')
163        self.assertEqual(ops[op5_index].accuracy, 0.05)
164        self.assertEqual(len(ops[op5_index].grids), 1)
165        if QgsProjUtils.projVersionMajor() == 6:
166            self.assertEqual(ops[op5_index].grids[0].shortName, 'GDA94_GDA2020_conformal_christmas_island.gsb')
167        else:
168            self.assertEqual(ops[op5_index].grids[0].shortName, 'au_icsm_GDA94_GDA2020_conformal_christmas_island.tif')
169        self.assertEqual(ops[op5_index].grids[0].fullName, '')
170        if QgsProjUtils.projVersionMajor() == 6:
171            self.assertTrue(ops[op5_index].grids[0].packageName)
172        self.assertIn('http', ops[op5_index].grids[0].url)
173
174        # uses a pivot datum (technically a proj test, but this will help me sleep at night ;)
175        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:3111'),
176                                           QgsCoordinateReferenceSystem('EPSG:7899'))
177
178        self.assertGreaterEqual(len(ops), 3)
179
180        op1_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=push +v_3 +step +proj=cart +ellps=GRS80 +step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 +rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 +convention=coordinate_frame +step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80'][0]
181        self.assertTrue(ops[op1_index].name)
182        self.assertEqual(ops[op1_index].proj, '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=push +v_3 +step +proj=cart +ellps=GRS80 +step +proj=helmert +x=0.06155 +y=-0.01087 +z=-0.04019 +rx=-0.0394924 +ry=-0.0327221 +rz=-0.0328979 +s=-0.009994 +convention=coordinate_frame +step +inv +proj=cart +ellps=GRS80 +step +proj=pop +v_3 +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80')
183        self.assertTrue(ops[op1_index].isAvailable)
184        self.assertEqual(ops[op1_index].accuracy, 0.01)
185        self.assertEqual(len(ops[op1_index].grids), 0)
186
187        if QgsProjUtils.projVersionMajor() == 6:
188            op2_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_and_distortion.gsb +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80'][0]
189        else:
190            op2_index = [i for i in range(len(ops)) if ops[
191                i].proj == '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_and_distortion.tif +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80'][
192                0]
193        self.assertTrue(ops[op2_index].name)
194        if QgsProjUtils.projVersionMajor() == 6:
195            self.assertEqual(ops[op2_index].proj, '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=GDA94_GDA2020_conformal_and_distortion.gsb +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80')
196        else:
197            self.assertEqual(ops[op2_index].proj,
198                             '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal_and_distortion.tif +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80')
199        self.assertEqual(ops[op2_index].accuracy, 0.05)
200        self.assertEqual(len(ops[op2_index].grids), 1)
201        if QgsProjUtils.projVersionMajor() == 6:
202            self.assertEqual(ops[op2_index].grids[0].shortName, 'GDA94_GDA2020_conformal_and_distortion.gsb')
203        else:
204            self.assertEqual(ops[op2_index].grids[0].shortName, 'au_icsm_GDA94_GDA2020_conformal_and_distortion.tif')
205        self.assertEqual(ops[op2_index].grids[0].fullName, '')
206        if QgsProjUtils.projVersionMajor() == 6:
207            self.assertTrue(ops[op2_index].grids[0].packageName)
208        self.assertIn('http', ops[op2_index].grids[0].url)
209        self.assertTrue(ops[op2_index].grids[0].directDownload)
210        self.assertTrue(ops[op2_index].grids[0].openLicense)
211
212        if QgsProjUtils.projVersionMajor() == 6:
213            op3_index = [i for i in range(len(ops)) if ops[i].proj == '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=GDA94_GDA2020_conformal.gsb +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80'][0]
214        else:
215            op3_index = [i for i in range(len(ops)) if ops[
216                i].proj == '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal.tif +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80'][
217                0]
218        self.assertTrue(ops[op3_index].name)
219        if QgsProjUtils.projVersionMajor() == 6:
220            self.assertEqual(ops[op3_index].proj, '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=GDA94_GDA2020_conformal.gsb +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80')
221        else:
222            self.assertEqual(ops[op3_index].proj,
223                             '+proj=pipeline +step +inv +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80 +step +proj=hgridshift +grids=au_icsm_GDA94_GDA2020_conformal.tif +step +proj=lcc +lat_0=-37 +lon_0=145 +lat_1=-36 +lat_2=-38 +x_0=2500000 +y_0=2500000 +ellps=GRS80')
224        self.assertEqual(ops[op3_index].accuracy, 0.05)
225        self.assertEqual(len(ops[op3_index].grids), 1)
226        if QgsProjUtils.projVersionMajor() == 6:
227            self.assertEqual(ops[op3_index].grids[0].shortName, 'GDA94_GDA2020_conformal.gsb')
228        else:
229            self.assertEqual(ops[op3_index].grids[0].shortName, 'au_icsm_GDA94_GDA2020_conformal.tif')
230        self.assertEqual(ops[op3_index].grids[0].fullName, '')
231        if QgsProjUtils.projVersionMajor() == 6:
232            self.assertTrue(ops[op3_index].grids[0].packageName)
233        self.assertIn('http', ops[op3_index].grids[0].url)
234        self.assertTrue(ops[op3_index].grids[0].directDownload)
235        self.assertTrue(ops[op3_index].grids[0].openLicense)
236
237    @unittest.skipIf(QgsProjUtils.projVersionMajor() < 7, 'Not a proj >= 7 build')
238    def testNoLasLos(self):
239        """
240        Test that operations which rely on an NADCON5 grid shift file (which are unsupported by Proj... at time of writing !) are not returned
241        """
242        ops = QgsDatumTransform.operations(QgsCoordinateReferenceSystem('EPSG:4138'),
243                                           QgsCoordinateReferenceSystem('EPSG:4269'))
244        self.assertEqual(len(ops), 2)
245        self.assertTrue(ops[0].name)
246        self.assertTrue(ops[0].proj)
247        self.assertTrue(ops[1].name)
248        self.assertTrue(ops[1].proj)
249
250
251if __name__ == '__main__':
252    unittest.main()
253