1 /***************************************************************************
2 testqgsprojectproperties.cpp
3 -------------------------
4 Date : 2018-11-21
5 Copyright : (C) 2018 by Mathieu Pellerin
6 Email : nirvn dot asia at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16 #include "qgstest.h"
17 #include "qgisapp.h"
18 #include "qgsapplication.h"
19 #include "qgsvectorlayer.h"
20 #include "qgsprojectproperties.h"
21 #include "qgsproject.h"
22 #include "qgsmapcanvas.h"
23 #include "qgsprojectdisplaysettings.h"
24 #include "qgsbearingnumericformat.h"
25 #include "qgsrasterlayer.h"
26 #include "qgsprojecttimesettings.h"
27 #include "qgsmaplayertemporalproperties.h"
28 #include "qgsrasterlayertemporalproperties.h"
29
30 /**
31 * \ingroup UnitTests
32 * This is a unit test for the project properties dialog
33 */
34 class TestQgsProjectProperties : public QObject
35 {
36 Q_OBJECT
37 public:
38 TestQgsProjectProperties();
39
40 private slots:
41 void initTestCase();// will be called before the first testfunction is executed.
42 void cleanupTestCase();// will be called after the last testfunction was executed.
init()43 void init() {} // will be called before each testfunction is executed.
cleanup()44 void cleanup() {} // will be called after every testfunction.
45
46 void testProjectPropertiesDirty();
47 void testEllipsoidChange();
48 void testEllipsoidCrsSync();
49 void testBearingFormat();
50 void testTimeSettings();
51
52 private:
53 QgisApp *mQgisApp = nullptr;
54 };
55
56 TestQgsProjectProperties::TestQgsProjectProperties() = default;
57
58 //runs before all tests
initTestCase()59 void TestQgsProjectProperties::initTestCase()
60 {
61 qDebug() << "TestQgsProjectProperties::initTestCase()";
62 // init QGIS's paths - true means that all path will be inited from prefix
63 QgsApplication::init();
64 QgsApplication::initQgis();
65 mQgisApp = new QgisApp();
66 }
67
68 //runs after all tests
cleanupTestCase()69 void TestQgsProjectProperties::cleanupTestCase()
70 {
71 QgsApplication::exitQgis();
72 }
73
testProjectPropertiesDirty()74 void TestQgsProjectProperties::testProjectPropertiesDirty()
75 {
76 // create a temporary layer
77 std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "none?field=code:int&field=regular:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
78 QVERIFY( tempLayer->isValid() );
79
80 // add layer to project, to insure presence of layer-related project settings
81 QgsProject::instance()->addMapLayer( tempLayer.get() );
82
83 // opening the project properties for the first time in a new project does write new entries
84 // call apply twice here to test that subsequent opening will not dirty project
85 QgsProjectProperties *pp = new QgsProjectProperties( mQgisApp->mapCanvas() );
86 pp->apply();
87 delete pp;
88 QgsProject::instance()->setDirty( false );
89 pp = new QgsProjectProperties( mQgisApp->mapCanvas() );
90 pp->apply();
91 delete pp;
92 QCOMPARE( QgsProject::instance()->isDirty(), false );
93 }
94
testEllipsoidChange()95 void TestQgsProjectProperties::testEllipsoidChange()
96 {
97 QgsProject::instance()->clear();
98 QgsProject::instance()->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
99 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
100
101 std::unique_ptr< QgsProjectProperties > pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
102 pp->apply();
103 pp.reset();
104 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
105
106 #if PROJ_VERSION_MAJOR>=6
107 QgsProject::instance()->setEllipsoid( QStringLiteral( "ESRI:107900" ) );
108 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
109 pp->apply();
110 pp.reset();
111 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "ESRI:107900" ) );
112
113 QgsProject::instance()->setEllipsoid( QStringLiteral( "EPSG:7002" ) );
114 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
115 pp->apply();
116 pp.reset();
117 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "EPSG:7002" ) );
118
119 QgsProject::instance()->setEllipsoid( QStringLiteral( "EPSG:7005" ) );
120 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
121 pp->apply();
122 pp.reset();
123 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "EPSG:7005" ) );
124
125 #else
126 QgsProject::instance()->setEllipsoid( QStringLiteral( "bessel" ) );
127 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
128 pp->apply();
129 pp.reset();
130 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "bessel" ) );
131
132 QgsProject::instance()->setEllipsoid( QStringLiteral( "IGNF:ELG052" ) );
133 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
134 pp->apply();
135 pp.reset();
136 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "IGNF:ELG052" ) );
137
138 QgsProject::instance()->setEllipsoid( QStringLiteral( "IGNF:ELG037" ) );
139 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
140 pp->apply();
141 pp.reset();
142 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "IGNF:ELG037" ) );
143
144 #endif
145
146 QgsProject::instance()->setEllipsoid( QStringLiteral( "NONE" ) );
147 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
148 pp->apply();
149 pp.reset();
150 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
151
152 QgsProject::instance()->setEllipsoid( QStringLiteral( "PARAMETER:55:66" ) );
153 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
154 pp->apply();
155 pp.reset();
156 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "PARAMETER:55:66" ) );
157
158 }
159
testEllipsoidCrsSync()160 void TestQgsProjectProperties::testEllipsoidCrsSync()
161 {
162 // test logic around syncing ellipsoid choice to project CRS
163
164 QgsProject::instance()->clear();
165
166 // if project has a crs and ellipsoid is none, then ellipsoid should not be changed when project crs is changed
167 QgsProject::instance()->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
168 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
169
170 std::unique_ptr< QgsProjectProperties > pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
171 pp->setSelectedCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
172 pp->apply();
173 pp.reset();
174 QCOMPARE( QgsProject::instance()->crs().authid(), QStringLiteral( "EPSG:3111" ) );
175 // ellipsoid must remain not set
176 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
177
178 // if ellipsoid is not set to none, then it should always be synced with the project crs choice
179 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
180 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
181 pp->setSelectedCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
182 pp->apply();
183 pp.reset();
184 // ellipsoid must remain not set
185 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "NONE" ) );
186
187 // but if ellipsoid is initially set, then changing the project CRS should update the ellipsoid to match
188 #if PROJ_VERSION_MAJOR>=6
189 QgsProject::instance()->setEllipsoid( QStringLiteral( "EPSG:7021" ) );
190 #else
191 QgsProject::instance()->setEllipsoid( QStringLiteral( "evrst69" ) );
192 #endif
193 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
194 pp->setSelectedCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
195 pp->apply();
196 pp.reset();
197 // ellipsoid should be updated to match CRS ellipsoid
198 #if PROJ_VERSION_MAJOR>=6
199 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "EPSG:7019" ) );
200 #else
201 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "GRS80" ) );
202 #endif
203
204 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
205 #if PROJ_VERSION_MAJOR>=6
206 pp->setSelectedCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4240" ) ) );
207 #else
208 pp->setSelectedCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3907" ) ) );
209 #endif
210 pp->apply();
211 pp.reset();
212 #if PROJ_VERSION_MAJOR>=6
213 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "EPSG:7015" ) );
214 #else
215 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "bessel" ) );
216 #endif
217
218 #if PROJ_VERSION_MAJOR>=6
219 // try creating a crs from a non-standard WKT string (in this case, the invalid WKT definition of EPSG:31370 used by
220 // some ArcGIS versions: see https://github.com/OSGeo/PROJ/issues/1781
221 const QString wkt = QStringLiteral( R"""(PROJCS["Belge 1972 / Belgian Lambert 72",GEOGCS["Belge 1972",DATUM["Reseau_National_Belge_1972",SPHEROID["International 1924",6378388,297],AUTHORITY["EPSG","6313"]],PRIMEM["Greenwich",0],UNIT["Degree",0.0174532925199433]],PROJECTION["Lambert_Conformal_Conic_2SP"],PARAMETER["latitude_of_origin",90],PARAMETER["central_meridian",4.36748666666667],PARAMETER["standard_parallel_1",49.8333339],PARAMETER["standard_parallel_2",51.1666672333333],PARAMETER["false_easting",150000.01256],PARAMETER["false_northing",5400088.4378],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]])""" );
222 QgsCoordinateReferenceSystem customCrs = QgsCoordinateReferenceSystem::fromWkt( wkt );
223 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
224 pp->setSelectedCrs( customCrs );
225 pp->apply();
226 pp.reset();
227 QCOMPARE( QgsProject::instance()->ellipsoid().left( 30 ), QStringLiteral( "PARAMETER:6378388:6356911.9461" ) );
228 #endif
229
230 // ok. Next bit of logic -- if the project is initially set to NO projection and NO ellipsoid, then first setting the project CRS should set an ellipsoid to match
231 QgsProject::instance()->setCrs( QgsCoordinateReferenceSystem() );
232 QgsProject::instance()->setEllipsoid( QStringLiteral( "NONE" ) );
233
234 pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
235 pp->setSelectedCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ) );
236 pp->apply();
237 pp.reset();
238 // ellipsoid should be updated to match CRS ellipsoid
239 #if PROJ_VERSION_MAJOR>=6
240 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "EPSG:7019" ) );
241 #else
242 QCOMPARE( QgsProject::instance()->ellipsoid(), QStringLiteral( "GRS80" ) );
243 #endif
244 }
245
testBearingFormat()246 void TestQgsProjectProperties::testBearingFormat()
247 {
248 QgsProject::instance()->clear();
249 std::unique_ptr< QgsBearingNumericFormat > format = qgis::make_unique< QgsBearingNumericFormat >();
250 format->setNumberDecimalPlaces( 9 );
251 QgsProject::instance()->displaySettings()->setBearingFormat( format.release() );
252
253 std::unique_ptr< QgsProjectProperties > pp = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
254 pp->apply();
255 QCOMPARE( QgsProject::instance()->displaySettings()->bearingFormat()->numberDecimalPlaces(), 9 );
256 }
257
testTimeSettings()258 void TestQgsProjectProperties::testTimeSettings()
259 {
260 QgsProject::instance()->clear();
261 QgsDateTimeRange range = QgsDateTimeRange( QDateTime( QDate( 2020, 1, 1 ), QTime(), Qt::UTC ),
262 QDateTime( QDate( 2020, 12, 31 ), QTime(), Qt::UTC ) );
263
264 QgsProject::instance()->timeSettings()->setTemporalRange( range );
265 QgsDateTimeRange projectRange = QgsProject::instance()->timeSettings()->temporalRange();
266
267 std::unique_ptr< QgsProjectProperties > projectProperties = qgis::make_unique< QgsProjectProperties >( mQgisApp->mapCanvas() );
268
269 QCOMPARE( projectRange, range );
270
271 // Test setting Project temporal range using temporal layers
272
273 QgsRasterLayer *firstLayer = new QgsRasterLayer( QString(), QStringLiteral( "firstLayer" ), QStringLiteral( "wms" ) );
274 QgsRasterLayer *secondLayer = new QgsRasterLayer( QString(), QStringLiteral( "secondLayer" ), QStringLiteral( "wms" ) );
275 QgsRasterLayer *thirdLayer = new QgsRasterLayer( QString(), QStringLiteral( "thirdLayer" ), QStringLiteral( "wms" ) );
276
277 QgsDateTimeRange firstRange = QgsDateTimeRange( QDateTime( QDate( 2020, 1, 1 ), QTime(), Qt::UTC ),
278 QDateTime( QDate( 2020, 3, 31 ), QTime(), Qt::UTC ) );
279 QgsDateTimeRange secondRange = QgsDateTimeRange( QDateTime( QDate( 2020, 4, 1 ), QTime(), Qt::UTC ),
280 QDateTime( QDate( 2020, 7, 31 ), QTime(), Qt::UTC ) );
281 QgsDateTimeRange thirdRange = QgsDateTimeRange( QDateTime( QDate( 2019, 1, 1 ), QTime(), Qt::UTC ),
282 QDateTime( QDate( 2020, 2, 28 ), QTime(), Qt::UTC ) );
283
284 firstLayer->temporalProperties()->setIsActive( true );
285 qobject_cast< QgsRasterLayerTemporalProperties * >( firstLayer->temporalProperties() )->setFixedTemporalRange( firstRange );
286 secondLayer->temporalProperties()->setIsActive( true );
287 qobject_cast< QgsRasterLayerTemporalProperties * >( secondLayer->temporalProperties() )->setFixedTemporalRange( secondRange );
288 thirdLayer->temporalProperties()->setIsActive( true );
289 qobject_cast< QgsRasterLayerTemporalProperties * >( thirdLayer->temporalProperties() )->setFixedTemporalRange( thirdRange );
290
291 QgsProject::instance()->addMapLayers( { firstLayer, secondLayer, thirdLayer } );
292
293 projectProperties->calculateFromLayersButton_clicked();
294 projectProperties->apply();
295
296 QgsDateTimeRange expectedRange = QgsDateTimeRange( thirdRange.begin(), secondRange.end() );
297 QgsDateTimeRange secondProjectRange = QgsProject::instance()->timeSettings()->temporalRange();
298
299 QCOMPARE( secondProjectRange, expectedRange );
300 }
301
302 QGSTEST_MAIN( TestQgsProjectProperties )
303
304 #include "testqgsprojectproperties.moc"
305