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