1 /***************************************************************************
2      testqgsmaptoolidentifyaction.cpp
3      --------------------------------
4     Date                 : 2016-02-14
5     Copyright            : (C) 2016 by Nyall Dawson
6     Email                : nyall dot dawson 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 
18 #include "qgsapplication.h"
19 #include "qgsvectorlayer.h"
20 #include "qgsrasterlayer.h"
21 #include "qgsfeature.h"
22 #include "qgsgeometry.h"
23 #include "qgsvectordataprovider.h"
24 #include "qgsvectortilelayer.h"
25 #include "qgsproject.h"
26 #include "qgsmapcanvas.h"
27 #include "qgsmeshlayer.h"
28 #include "qgsunittypes.h"
29 #include "qgsmaptoolidentifyaction.h"
30 #include "qgssettings.h"
31 #include "qgsidentifymenu.h"
32 #include "qgisapp.h"
33 #include "qgsaction.h"
34 #include "qgsactionmanager.h"
35 #include "qgsactionmenu.h"
36 #include "qgsidentifyresultsdialog.h"
37 #include "qgsmapmouseevent.h"
38 #include "qgsmaplayertemporalproperties.h"
39 #include "qgsmeshlayertemporalproperties.h"
40 #include "qgsrasterlayertemporalproperties.h"
41 
42 #include <QTimer>
43 
44 #include "cpl_conv.h"
45 
46 class TestQgsMapToolIdentifyAction : public QObject
47 {
48     Q_OBJECT
49   public:
50     TestQgsMapToolIdentifyAction() = default;
51 
52   private slots:
53     void initTestCase(); // will be called before the first testfunction is executed.
54     void cleanupTestCase(); // will be called after the last testfunction was executed.
55     void init(); // will be called before each testfunction is executed.
56     void cleanup(); // will be called after every testfunction.
57     void lengthCalculation(); //test calculation of derived length attributes
58     void perimeterCalculation(); //test calculation of derived perimeter attribute
59     void areaCalculation(); //test calculation of derived area attribute
60     void identifyRasterFloat32(); // test pixel identification and decimal precision
61     void identifyRasterFloat64(); // test pixel identification and decimal precision
62     void identifyRasterTemporal();
63     void identifyMesh(); // test identification for mesh layer
64     void identifyVectorTile();  // test identification for vector tile layer
65     void identifyInvalidPolygons(); // test selecting invalid polygons
66     void clickxy(); // test if click_x and click_y variables are propagated
67     void closestPoint();
68 
69   private:
70     void doAction();
71 
72     QgsMapCanvas *canvas = nullptr;
73     QgsMapToolIdentifyAction *mIdentifyAction = nullptr;
74     QgisApp *mQgisApp = nullptr;
75 
76     QString testIdentifyRaster( QgsRasterLayer *layer, double xGeoref, double yGeoref );
77     QList<QgsMapToolIdentify::IdentifyResult> testIdentifyVector( QgsVectorLayer *layer, double xGeoref, double yGeoref );
78     QList<QgsMapToolIdentify::IdentifyResult> testIdentifyMesh( QgsMeshLayer *layer, double xGeoref, double yGeoref );
79     QList<QgsMapToolIdentify::IdentifyResult> testIdentifyVectorTile( QgsVectorTileLayer *layer, double xGeoref, double yGeoref );
80 
81     // Release return with delete []
82     unsigned char *
hex2bytes(const char * hex,int * size)83     hex2bytes( const char *hex, int *size )
84     {
85       QByteArray ba = QByteArray::fromHex( hex );
86       unsigned char *out = new unsigned char[ba.size()];
87       memcpy( out, ba.data(), ba.size() );
88       *size = ba.size();
89       return out;
90     }
91 
92     // TODO: make this a QgsGeometry member...
geomFromHexWKB(const char * hexwkb)93     QgsGeometry geomFromHexWKB( const char *hexwkb )
94     {
95       int wkbsize;
96       unsigned char *wkb = hex2bytes( hexwkb, &wkbsize );
97       QgsGeometry geom;
98       // NOTE: QgsGeometry takes ownership of wkb
99       geom.fromWkb( wkb, wkbsize );
100       return geom;
101     }
102 };
103 
initTestCase()104 void TestQgsMapToolIdentifyAction::initTestCase()
105 {
106   QgsApplication::init();
107   QgsApplication::initQgis();
108   // Set up the QgsSettings environment
109   QCoreApplication::setOrganizationName( QStringLiteral( "QGIS" ) );
110   QCoreApplication::setOrganizationDomain( QStringLiteral( "qgis.org" ) );
111   QCoreApplication::setApplicationName( QStringLiteral( "QGIS-TEST" ) );
112 
113   QgsApplication::showSettings();
114 
115   // enforce C locale because the tests expect it
116   // (decimal separators / thousand separators)
117   QLocale::setDefault( QLocale::c() );
118 
119   mQgisApp = new QgisApp();
120 }
121 
cleanupTestCase()122 void TestQgsMapToolIdentifyAction::cleanupTestCase()
123 {
124   QgsApplication::exitQgis();
125 }
126 
init()127 void TestQgsMapToolIdentifyAction::init()
128 {
129   canvas = new QgsMapCanvas();
130 }
131 
cleanup()132 void TestQgsMapToolIdentifyAction::cleanup()
133 {
134   delete canvas;
135 }
136 
doAction()137 void TestQgsMapToolIdentifyAction::doAction()
138 {
139   bool ok = false;
140   const int clickxOk = 2484588;
141   const int clickyOk = 2425722;
142 
143   // test QActionMenu
144   QList<QAction *> actions = mIdentifyAction->identifyMenu()->actions();
145   bool testDone = false;
146 
147   for ( int i = 0; i < actions.count(); i++ )
148   {
149     if ( actions[i]->text().compare( "MyAction" ) == 0 )
150     {
151       const QgsActionMenu::ActionData data = actions[i]->data().value<QgsActionMenu::ActionData>();
152       const QgsAction act = data.actionData.value<QgsAction>();
153 
154       const int clickx = act.expressionContextScope().variable( "click_x" ).toString().toInt( &ok, 10 );
155       QCOMPARE( clickx, clickxOk );
156 
157       const int clicky = act.expressionContextScope().variable( "click_y" ).toString().toInt( &ok, 10 );
158       QCOMPARE( clicky, clickyOk );
159 
160       testDone = true;
161     }
162   }
163 
164   QCOMPARE( testDone, true );
165 
166   // test QgsIdentifyResultsDialog expression context scope
167   QgsIdentifyResultsDialog *dlg = mIdentifyAction->resultsDialog();
168   const int clickx = dlg->expressionContextScope().variable( "click_x" ).toString().toInt( &ok, 10 );
169   QCOMPARE( clickx, clickxOk );
170 
171   const int clicky = dlg->expressionContextScope().variable( "click_y" ).toString().toInt( &ok, 10 );
172   QCOMPARE( clicky, clickyOk );
173 
174   // close
175   mIdentifyAction->identifyMenu()->close();
176 }
177 
clickxy()178 void TestQgsMapToolIdentifyAction::clickxy()
179 {
180   // create temp layer
181   std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:3111" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
182   QVERIFY( tempLayer->isValid() );
183 
184   // add feature
185   QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
186   const QgsPointXY wordPoint( 2484588, 2425722 );
187   const QgsGeometry geom = QgsGeometry::fromPointXY( wordPoint ) ;
188   f1.setGeometry( geom );
189   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
190 
191   // prepare canvas
192   QList<QgsMapLayer *> layers;
193   layers.append( tempLayer.get() );
194 
195   const QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
196   canvas->setDestinationCrs( srs );
197   canvas->setLayers( layers );
198   canvas->setCurrentLayer( tempLayer.get() );
199 
200   // create/add action
201   QgsAction act( QgsAction::GenericPython, "MyAction", "", true );
202 
203   QSet<QString> scopes;
204   scopes << "Feature";
205   act.setActionScopes( scopes );
206   tempLayer->actions()->addAction( act );
207 
208   // init map tool identify action
209   mIdentifyAction = new QgsMapToolIdentifyAction( canvas );
210 
211   // simulate a click on the canvas
212   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 2484588, 2425722 );
213   const QPoint point = QPoint( mapPoint.x(), mapPoint.y() );
214   QMouseEvent releases( QEvent::MouseButtonRelease, point,
215                         Qt::RightButton, Qt::LeftButton, Qt::NoModifier );
216   QgsMapMouseEvent mapReleases( nullptr, &releases );
217 
218   // simulate a click on the corresponding action
219   QTimer::singleShot( 2000, this, &TestQgsMapToolIdentifyAction::doAction );
220   mIdentifyAction->canvasReleaseEvent( &mapReleases );
221 }
222 
closestPoint()223 void TestQgsMapToolIdentifyAction::closestPoint()
224 {
225   QgsSettings s;
226   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
227 
228   //create a temporary layer
229   std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineStringZM?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
230   QVERIFY( tempLayer->isValid() );
231   QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
232   f1.setAttribute( QStringLiteral( "pk" ), 1 );
233   f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
234   QgsPolylineXY line3111;
235   line3111 << QgsPointXY( 2484588, 2425722 ) << QgsPointXY( 2482767, 2398853 );
236   const QgsGeometry line3111G = QgsGeometry::fromWkt( QStringLiteral( "LineStringZM( 2484588 2425722 11 31, 2484588 2398853 15 37)" ) ) ;
237   f1.setGeometry( line3111G );
238   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
239 
240   // set project CRS and ellipsoid
241   const QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
242   canvas->setDestinationCrs( srs );
243   canvas->setExtent( f1.geometry().boundingBox() );
244   QgsProject::instance()->setCrs( srs );
245   QgsProject::instance()->setEllipsoid( QStringLiteral( "WGS84" ) );
246   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceMeters );
247 
248   QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 2484587, 2399800 );
249 
250   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
251 
252   //check that closest point attributes are present
253   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
254   QCOMPARE( result.length(), 1 );
255   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Closest X" )], QStringLiteral( "2484588" ) );
256   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Closest Y" )], QStringLiteral( "2399800" ) );
257   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Interpolated M" )].left( 4 ), QStringLiteral( "36.7" ) );
258   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Interpolated Z" )].left( 4 ), QStringLiteral( "14.8" ) );
259 
260   // polygons
261   //create a temporary layer
262   std::unique_ptr< QgsVectorLayer> tempLayer2( new QgsVectorLayer( QStringLiteral( "PolygonZM?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
263   QVERIFY( tempLayer2->isValid() );
264   f1 = QgsFeature( tempLayer2->dataProvider()->fields(), 1 );
265   f1.setAttribute( QStringLiteral( "pk" ), 1 );
266   f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
267 
268   f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "PolygonZM((2484588 2425722 1 11, 2484588 2398853 2 12, 2520109 2397715 3 13, 2520792 2425494 4 14, 2484588 2425722 1 11))" ) ) );
269   QVERIFY( f1.hasGeometry() );
270   tempLayer2->dataProvider()->addFeatures( QgsFeatureList() << f1 );
271 
272   mapPoint = canvas->getCoordinateTransform()->transform( 2484589, 2399800 );
273   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer2.get() );
274   QCOMPARE( result.length(), 1 );
275   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Closest X" )], QStringLiteral( "2484588" ) );
276   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Closest Y" )], QStringLiteral( "2399800" ) );
277   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Interpolated M" )].left( 4 ), QStringLiteral( "11.9" ) );
278   QCOMPARE( result.at( 0 ).mDerivedAttributes[tr( "Interpolated Z" )].left( 4 ), QStringLiteral( "1.96" ) );
279 }
280 
lengthCalculation()281 void TestQgsMapToolIdentifyAction::lengthCalculation()
282 {
283   QgsSettings s;
284   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
285 
286   //create a temporary layer
287   std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
288   QVERIFY( tempLayer->isValid() );
289   QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
290   f1.setAttribute( QStringLiteral( "pk" ), 1 );
291   f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
292   QgsPolylineXY line3111;
293   line3111 << QgsPointXY( 2484588, 2425722 ) << QgsPointXY( 2482767, 2398853 );
294   const QgsGeometry line3111G = QgsGeometry::fromPolylineXY( line3111 ) ;
295   f1.setGeometry( line3111G );
296   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
297 
298   // set project CRS and ellipsoid
299   const QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
300   canvas->setDestinationCrs( srs );
301   canvas->setExtent( f1.geometry().boundingBox() );
302   QgsProject::instance()->setCrs( srs );
303   QgsProject::instance()->setEllipsoid( QStringLiteral( "WGS84" ) );
304   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceMeters );
305 
306   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 2484588, 2425722 );
307 
308   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
309   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
310   QCOMPARE( result.length(), 1 );
311   QString derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
312   double length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
313   QGSCOMPARENEAR( length, 26932.2, 0.1 );
314   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian)" )];
315   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
316   QGSCOMPARENEAR( length, 26930.6, 0.1 );
317 
318   //check that project units are respected
319   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceFeet );
320   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
321   QCOMPARE( result.length(), 1 );
322   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
323   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
324   QGSCOMPARENEAR( length, 88360.1, 0.1 );
325   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian)" )];
326   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
327   QGSCOMPARENEAR( length, 88355.1, 0.1 );
328 
329   //test unchecked "keep base units" setting
330   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), false );
331   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
332   QCOMPARE( result.length(), 1 );
333   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
334   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
335   QGSCOMPARENEAR( length, 16.735, 0.001 );
336   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian)" )];
337   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
338   QGSCOMPARENEAR( length, 16.734000, 0.001 );
339 
340   // no conversion of Cartesian lengths between unit types
341   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
342   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceDegrees );
343   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
344   QCOMPARE( result.length(), 1 );
345   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
346   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
347   QGSCOMPARENEAR( length, 0.242000, 0.001 );
348   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian)" )];
349   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
350   QGSCOMPARENEAR( length, 26930.6, 0.1 );
351 
352   // LineString with Z
353   tempLayer = std::make_unique< QgsVectorLayer>( QStringLiteral( "LineStringZ?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
354   QVERIFY( tempLayer->isValid() );
355   f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "LineStringZ(2484588 2425722 10, 2482767 2398853 1000)" ) ) );
356   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
357   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
358   QCOMPARE( result.length(), 1 );
359   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
360   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
361   QGSCOMPARENEAR( length, 0.242000, 0.001 );
362   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian — 2D)" )];
363   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
364   QGSCOMPARENEAR( length, 26930.6, 0.1 );
365   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian — 3D)" )];
366   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
367   QGSCOMPARENEAR( length, 26948.827000, 0.1 );
368 
369   // CircularString with Z (no length 3d for now, not supported by circular string API)
370   tempLayer = std::make_unique< QgsVectorLayer>( QStringLiteral( "CircularStringZ?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
371   QVERIFY( tempLayer->isValid() );
372   f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "CircularStringZ(2484588 2425722 10, 2483588 2429722 10, 2482767 2398853 1000)" ) ) );
373   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
374   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
375   QCOMPARE( result.length(), 1 );
376   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
377   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
378   QGSCOMPARENEAR( length, 2.5880, 0.001 );
379   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian)" )];
380   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
381   QGSCOMPARENEAR( length, 288140.206, 0.1 );
382 
383   // MultiLineString with Z
384   tempLayer = std::make_unique< QgsVectorLayer>( QStringLiteral( "MultiLineStringZ?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
385   QVERIFY( tempLayer->isValid() );
386   f1.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "MultiLineStringZ((2484588 2425722 10, 2482767 2398853 1000), (2494588 2435722 10, 2422767 2318853 1000))" ) ) );
387   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
388   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
389   QCOMPARE( result.length(), 1 );
390   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Ellipsoidal — WGS84)" )];
391   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
392   QGSCOMPARENEAR( length, 1.4740, 0.001 );
393   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian — 2D)" )];
394   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
395   QGSCOMPARENEAR( length, 164104.319, 0.1 );
396   derivedLength = result.at( 0 ).mDerivedAttributes[tr( "Length (Cartesian — 3D)" )];
397   length = derivedLength.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
398   QGSCOMPARENEAR( length, 164126.083, 0.1 );
399 
400 }
401 
perimeterCalculation()402 void TestQgsMapToolIdentifyAction::perimeterCalculation()
403 {
404   QgsSettings s;
405   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
406 
407   //create a temporary layer
408   std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
409   QVERIFY( tempLayer->isValid() );
410   QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
411   f1.setAttribute( QStringLiteral( "pk" ), 1 );
412   f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
413   QgsPolylineXY polygonRing3111;
414   polygonRing3111 << QgsPointXY( 2484588, 2425722 ) << QgsPointXY( 2482767, 2398853 ) << QgsPointXY( 2520109, 2397715 ) << QgsPointXY( 2520792, 2425494 ) << QgsPointXY( 2484588, 2425722 );
415   QgsPolygonXY polygon3111;
416   polygon3111 << polygonRing3111;
417   const QgsGeometry polygon3111G = QgsGeometry::fromPolygonXY( polygon3111 ) ;
418   f1.setGeometry( polygon3111G );
419   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
420 
421   // set project CRS and ellipsoid
422   const QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
423   canvas->setDestinationCrs( srs );
424   canvas->setExtent( f1.geometry().boundingBox() );
425   QgsProject::instance()->setCrs( srs );
426   QgsProject::instance()->setEllipsoid( QStringLiteral( "WGS84" ) );
427   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceMeters );
428 
429   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 2484588, 2425722 );
430 
431   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
432   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
433   QCOMPARE( result.length(), 1 );
434   QString derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Ellipsoidal — WGS84)" )];
435   double perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
436   QCOMPARE( perimeter, 128289.074 );
437   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Cartesian)" )];
438   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
439   QCOMPARE( perimeter, 128282.086 );
440 
441   //check that project units are respected
442   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceFeet );
443   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
444   QCOMPARE( result.length(), 1 );
445   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Ellipsoidal — WGS84)" )];
446   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
447   QGSCOMPARENEAR( perimeter, 420896.0, 0.1 );
448   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Cartesian)" )];
449   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
450   QGSCOMPARENEAR( perimeter, 420873.0, 0.1 );
451 
452   //test unchecked "keep base units" setting
453   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), false );
454   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
455   QCOMPARE( result.length(), 1 );
456   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Ellipsoidal — WGS84)" )];
457   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
458   QGSCOMPARENEAR( perimeter, 79.715, 0.001 );
459   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Cartesian)" )];
460   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
461   QCOMPARE( perimeter, 79.711 );
462 
463   // no conversion of Cartesian lengths between unit types
464   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
465   QgsProject::instance()->setDistanceUnits( QgsUnitTypes::DistanceDegrees );
466   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
467   QCOMPARE( result.length(), 1 );
468   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Ellipsoidal — WGS84)" )];
469   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
470   QGSCOMPARENEAR( perimeter, 1.152000, 0.001 );
471   derivedPerimeter = result.at( 0 ).mDerivedAttributes[tr( "Perimeter (Cartesian)" )];
472   perimeter = derivedPerimeter.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
473   QGSCOMPARENEAR( perimeter, 128282, 0.1 );
474 }
475 
areaCalculation()476 void TestQgsMapToolIdentifyAction::areaCalculation()
477 {
478   QgsSettings s;
479   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
480 
481   //create a temporary layer
482   std::unique_ptr< QgsVectorLayer> tempLayer( new QgsVectorLayer( QStringLiteral( "Polygon?crs=epsg:3111&field=pk:int&field=col1:double" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
483   QVERIFY( tempLayer->isValid() );
484   QgsFeature f1( tempLayer->dataProvider()->fields(), 1 );
485   f1.setAttribute( QStringLiteral( "pk" ), 1 );
486   f1.setAttribute( QStringLiteral( "col1" ), 0.0 );
487 
488   QgsPolylineXY polygonRing3111;
489   polygonRing3111 << QgsPointXY( 2484588, 2425722 ) << QgsPointXY( 2482767, 2398853 ) << QgsPointXY( 2520109, 2397715 ) << QgsPointXY( 2520792, 2425494 ) << QgsPointXY( 2484588, 2425722 );
490   QgsPolygonXY polygon3111;
491   polygon3111 << polygonRing3111;
492   const QgsGeometry polygon3111G = QgsGeometry::fromPolygonXY( polygon3111 ) ;
493   f1.setGeometry( polygon3111G );
494   tempLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
495 
496   // set project CRS and ellipsoid
497   const QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3111" ) );
498   canvas->setDestinationCrs( srs );
499   canvas->setExtent( f1.geometry().boundingBox() );
500   QgsProject::instance()->setCrs( srs );
501   QgsProject::instance()->setEllipsoid( QStringLiteral( "WGS84" ) );
502   QgsProject::instance()->setAreaUnits( QgsUnitTypes::AreaSquareMeters );
503 
504   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 2484588, 2425722 );
505 
506   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
507   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
508   QCOMPARE( result.length(), 1 );
509   QString derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Ellipsoidal — WGS84)" )];
510   double area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
511   QGSCOMPARENEAR( area, 1005755617.819000, 1.0 );
512   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Cartesian)" )];
513   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
514   QGSCOMPARENEAR( area, 1005640568.0, 1.0 );
515 
516   //check that project units are respected
517   QgsProject::instance()->setAreaUnits( QgsUnitTypes::AreaSquareMiles );
518   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
519   QCOMPARE( result.length(), 1 );
520   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Ellipsoidal — WGS84)" )];
521   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
522   QGSCOMPARENEAR( area, 388.324000, 0.001 );
523   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Cartesian)" )];
524   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
525   QGSCOMPARENEAR( area, 388.280000, 0.001 );
526 
527   //test unchecked "keep base units" setting
528   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), false );
529   QgsProject::instance()->setAreaUnits( QgsUnitTypes::AreaSquareFeet );
530   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
531   QCOMPARE( result.length(), 1 );
532   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Ellipsoidal — WGS84)" )];
533   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
534   QGSCOMPARENEAR( area, 388.324000, 0.001 );
535   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Cartesian)" )];
536   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
537   QGSCOMPARENEAR( area, 388.280000, 0.001 );
538 
539   // no conversion of Cartesian lengths between unit types
540   s.setValue( QStringLiteral( "/qgis/measure/keepbaseunit" ), true );
541   QgsProject::instance()->setAreaUnits( QgsUnitTypes::AreaSquareDegrees );
542   result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << tempLayer.get() );
543   QCOMPARE( result.length(), 1 );
544   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Ellipsoidal — WGS84)" )];
545   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
546   QGSCOMPARENEAR( area, 0.081000, 0.001 );
547   derivedArea = result.at( 0 ).mDerivedAttributes[tr( "Area (Cartesian)" )];
548   area = derivedArea.remove( ',' ).split( ' ' ).at( 0 ).toDouble();
549   QGSCOMPARENEAR( area, 1005640568.6, 1 );
550 }
551 
552 // private
testIdentifyRaster(QgsRasterLayer * layer,double xGeoref,double yGeoref)553 QString TestQgsMapToolIdentifyAction::testIdentifyRaster( QgsRasterLayer *layer, double xGeoref, double yGeoref )
554 {
555   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
556   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
557 
558   QgsIdentifyContext identifyContext;
559   if ( canvas->mapSettings().isTemporal() )
560     identifyContext.setTemporalRange( canvas->temporalRange() );
561 
562   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << layer, QgsMapToolIdentify::DefaultQgsSetting, identifyContext );
563   if ( result.length() != 1 )
564     return QString();
565   return result[0].mAttributes[QStringLiteral( "Band 1" )];
566 }
567 
568 // private
testIdentifyMesh(QgsMeshLayer * layer,double xGeoref,double yGeoref)569 QList<QgsMapToolIdentify::IdentifyResult> TestQgsMapToolIdentifyAction::testIdentifyMesh( QgsMeshLayer *layer, double xGeoref, double yGeoref )
570 {
571   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
572   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
573   //check that closest point attributes are present
574   QgsIdentifyContext identifyContext;
575   if ( canvas->mapSettings().isTemporal() )
576     identifyContext.setTemporalRange( canvas->temporalRange() );
577   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << layer, QgsMapToolIdentify::DefaultQgsSetting, identifyContext );
578   return result;
579 }
580 
581 // private
582 QList<QgsMapToolIdentify::IdentifyResult>
testIdentifyVector(QgsVectorLayer * layer,double xGeoref,double yGeoref)583 TestQgsMapToolIdentifyAction::testIdentifyVector( QgsVectorLayer *layer, double xGeoref, double yGeoref )
584 {
585   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
586   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
587   QgsIdentifyContext identifyContext;
588   if ( canvas->mapSettings().isTemporal() )
589     identifyContext.setTemporalRange( canvas->temporalRange() );
590   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << layer, QgsMapToolIdentify::DefaultQgsSetting, identifyContext );
591   return result;
592 }
593 
594 // private
595 QList<QgsMapToolIdentify::IdentifyResult>
testIdentifyVectorTile(QgsVectorTileLayer * layer,double xGeoref,double yGeoref)596 TestQgsMapToolIdentifyAction::testIdentifyVectorTile( QgsVectorTileLayer *layer, double xGeoref, double yGeoref )
597 {
598   std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) );
599   const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( xGeoref, yGeoref );
600   QgsIdentifyContext identifyContext;
601   if ( canvas->mapSettings().isTemporal() )
602     identifyContext.setTemporalRange( canvas->temporalRange() );
603   QList<QgsMapToolIdentify::IdentifyResult> result = action->identify( mapPoint.x(), mapPoint.y(), QList<QgsMapLayer *>() << layer, QgsMapToolIdentify::DefaultQgsSetting, identifyContext );
604   return result;
605 }
606 
identifyRasterTemporal()607 void TestQgsMapToolIdentifyAction::identifyRasterTemporal()
608 {
609   //create a temporary layer
610   const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/test.asc";
611   std::unique_ptr< QgsRasterLayer> tempLayer = std::make_unique< QgsRasterLayer >( raster );
612   QVERIFY( tempLayer->isValid() );
613 
614   // activate temporal properties
615   tempLayer->temporalProperties()->setIsActive( true );
616 
617   const QgsDateTimeRange range = QgsDateTimeRange( QDateTime( QDate( 2020, 1, 1 ), QTime(), Qt::UTC ),
618                                  QDateTime( QDate( 2020, 3, 31 ), QTime(), Qt::UTC ) );
619   qobject_cast< QgsRasterLayerTemporalProperties * >( tempLayer->temporalProperties() )->setFixedTemporalRange( range );
620 
621   canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
622 
623   // invalid temporal range on canvas
624   canvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 1950, 01, 01 ), QTime( 0, 0, 0 ), Qt::UTC ),
625                             QDateTime( QDate( 1950, 01, 01 ), QTime( 1, 0, 0 ), Qt::UTC ) ) );
626   QCOMPARE( testIdentifyRaster( tempLayer.get(), 0.5, 0.5 ), QString( ) );
627 
628   // valid temporal range on canvas
629   canvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 1950, 01, 01 ), QTime( 0, 0, 0 ), Qt::UTC ),
630                             QDateTime( QDate( 2050, 01, 01 ), QTime( 1, 0, 0 ), Qt::UTC ) ) );
631   QCOMPARE( testIdentifyRaster( tempLayer.get(), 0.5, 0.5 ), QString( "-999.9" ) );
632 }
633 
identifyRasterFloat32()634 void TestQgsMapToolIdentifyAction::identifyRasterFloat32()
635 {
636   //create a temporary layer
637   const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/test.asc";
638 
639   // By default the QgsRasterLayer forces AAIGRID_DATATYPE=Float64
640   CPLSetConfigOption( "AAIGRID_DATATYPE", "Float32" );
641   std::unique_ptr< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) );
642   CPLSetConfigOption( "AAIGRID_DATATYPE", nullptr );
643 
644   QVERIFY( tempLayer->isValid() );
645 
646   canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
647 
648   QCOMPARE( testIdentifyRaster( tempLayer.get(), 0.5, 0.5 ), QString( "-999.9" ) );
649 
650   QCOMPARE( testIdentifyRaster( tempLayer.get(), 1.5, 0.5 ), QString( "-999.987" ) );
651 
652   // More than 6 significant digits for corresponding value in .asc:
653   // precision loss in Float32
654   QCOMPARE( testIdentifyRaster( tempLayer.get(), 2.5, 0.5 ), QString( "1.234568" ) ); // in .asc file : 1.2345678
655 
656   QCOMPARE( testIdentifyRaster( tempLayer.get(), 3.5, 0.5 ), QString( "123456" ) );
657 
658   // More than 6 significant digits: no precision loss here for that particular value
659   QCOMPARE( testIdentifyRaster( tempLayer.get(), 4.5, 0.5 ), QString( "1234567" ) );
660 
661   // More than 6 significant digits: no precision loss here for that particular value
662   QCOMPARE( testIdentifyRaster( tempLayer.get(), 5.5, 0.5 ), QString( "-999.9876" ) );
663 
664   // More than 6 significant digits for corresponding value in .asc:
665   // precision loss in Float32
666   QCOMPARE( testIdentifyRaster( tempLayer.get(), 6.5, 0.5 ), QString( "1.234568" ) ); // in .asc file : 1.2345678901234
667 }
668 
identifyRasterFloat64()669 void TestQgsMapToolIdentifyAction::identifyRasterFloat64()
670 {
671   //create a temporary layer
672   const QString raster = QStringLiteral( TEST_DATA_DIR ) + "/raster/test.asc";
673   std::unique_ptr< QgsRasterLayer> tempLayer( new QgsRasterLayer( raster ) );
674   QVERIFY( tempLayer->isValid() );
675 
676   canvas->setExtent( QgsRectangle( 0, 0, 7, 1 ) );
677 
678   QCOMPARE( testIdentifyRaster( tempLayer.get(), 0.5, 0.5 ), QString( "-999.9" ) );
679 
680   QCOMPARE( testIdentifyRaster( tempLayer.get(), 1.5, 0.5 ), QString( "-999.987" ) );
681 
682   QCOMPARE( testIdentifyRaster( tempLayer.get(), 2.5, 0.5 ), QString( "1.2345678" ) );
683 
684   QCOMPARE( testIdentifyRaster( tempLayer.get(), 3.5, 0.5 ), QString( "123456" ) );
685 
686   QCOMPARE( testIdentifyRaster( tempLayer.get(), 4.5, 0.5 ), QString( "1234567" ) );
687 
688   QCOMPARE( testIdentifyRaster( tempLayer.get(), 5.5, 0.5 ), QString( "-999.9876" ) );
689 
690   QCOMPARE( testIdentifyRaster( tempLayer.get(), 6.5, 0.5 ), QString( "1.2345678901234" ) );
691 }
692 
identifyMesh()693 void TestQgsMapToolIdentifyAction::identifyMesh()
694 {
695   //create a temporary layer
696   const QString mesh = QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle.2dm";
697   QgsMeshLayer *tempLayer = new QgsMeshLayer( mesh, "testlayer", "mdal" );
698   QVERIFY( tempLayer->isValid() );
699   const QString vectorDs = QStringLiteral( TEST_DATA_DIR ) + "/mesh/quad_and_triangle_vertex_vector.dat";
700   tempLayer->dataProvider()->addDataset( vectorDs );
701   static_cast<QgsMeshLayerTemporalProperties *>(
702     tempLayer->temporalProperties() )->setReferenceTime(
703       QDateTime( QDate( 1950, 01, 01 ), QTime( 0, 0, 0 ), Qt::UTC ), tempLayer->dataProvider()->temporalCapabilities() );
704 
705   // we need to setup renderer otherwise triangular mesh
706   // will not be populated and identify will not work
707   QgsMapSettings mapSettings;
708   mapSettings.setExtent( tempLayer->extent() );
709   mapSettings.setDestinationCrs( tempLayer->crs() );
710   mapSettings.setOutputDpi( 96 );
711 
712   // here we check that datasets automatically get our default color ramp applied ("Plasma")
713   QgsRenderContext context = QgsRenderContext::fromMapSettings( mapSettings );
714   tempLayer->createMapRenderer( context );
715 
716   // only scalar dataset
717   tempLayer->temporalProperties()->setIsActive( false );
718   tempLayer->setStaticScalarDatasetIndex( QgsMeshDatasetIndex( 0, 0 ) );
719   QList<QgsMapToolIdentify::IdentifyResult> results;
720 
721   results = testIdentifyMesh( tempLayer, 500, 500 );
722   QCOMPARE( results.size(), 2 );
723   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Scalar Value" )], QStringLiteral( "no data" ) );
724   QCOMPARE( results[0].mDerivedAttributes[QStringLiteral( "Source" )], mesh );
725   QCOMPARE( results[1].mLabel, QStringLiteral( "Geometry" ) );
726   results = testIdentifyMesh( tempLayer, 2400, 2400 );
727   QCOMPARE( results.size(), 2 );
728   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Scalar Value" )], QStringLiteral( "42" ) );
729   QCOMPARE( results[0].mDerivedAttributes[QStringLiteral( "Source" )], mesh );
730   QCOMPARE( results[1].mLabel, QStringLiteral( "Geometry" ) );
731   QCOMPARE( results[1].mDerivedAttributes[QStringLiteral( "Face Centroid X" )], QStringLiteral( "2333.33" ) );
732   QCOMPARE( results[1].mDerivedAttributes[QStringLiteral( "Face Centroid Y" )], QStringLiteral( "2333.33" ) );
733   results = testIdentifyMesh( tempLayer, 1999, 2999 );
734   QCOMPARE( results[1].mDerivedAttributes[QStringLiteral( "Snapped Vertex Position X" )], QStringLiteral( "2000" ) );
735   QCOMPARE( results[1].mDerivedAttributes[QStringLiteral( "Snapped Vertex Position Y" )], QStringLiteral( "3000" ) );
736 
737   canvas->setTemporalRange( QgsDateTimeRange( QDateTime( QDate( 1950, 01, 01 ), QTime( 0, 0, 0 ), Qt::UTC ),
738                             QDateTime( QDate( 1950, 01, 01 ), QTime( 1, 0, 0 ), Qt::UTC ) ) );
739 
740   tempLayer->temporalProperties()->setIsActive( true );
741   results = testIdentifyMesh( tempLayer, 2400, 2400 );
742   QCOMPARE( results.size(), 3 );
743   QCOMPARE( results[0].mLabel, QStringLiteral( "Bed Elevation (active)" ) );
744   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Scalar Value" )], QStringLiteral( "42" ) );
745   QCOMPARE( results[0].mDerivedAttributes[QStringLiteral( "Source" )], mesh );
746 
747   QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Time Step" )], QStringLiteral( "1950-01-01 00:00:00" ) );
748 
749   QCOMPARE( results[1].mLabel, QStringLiteral( "VertexVectorDataset" ) );
750   QCOMPARE( results[1].mDerivedAttributes[QStringLiteral( "Source" )], vectorDs );
751   QCOMPARE( results[1].mAttributes[ QStringLiteral( "Vector Magnitude" )], QStringLiteral( "3" ) );
752   QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Vector x-component" )], QStringLiteral( "1.8" ) );
753   QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Vector y-component" )], QStringLiteral( "2.4" ) );
754 
755   QCOMPARE( results[2].mLabel, QStringLiteral( "Geometry" ) );
756   QCOMPARE( results[2].mDerivedAttributes[QStringLiteral( "Face Centroid X" )], QStringLiteral( "2333.33" ) );
757   QCOMPARE( results[2].mDerivedAttributes[QStringLiteral( "Face Centroid Y" )], QStringLiteral( "2333.33" ) );
758   results = testIdentifyMesh( tempLayer, 1999, 2999 );
759   QCOMPARE( results[2].mDerivedAttributes[QStringLiteral( "Snapped Vertex Position X" )], QStringLiteral( "2000" ) );
760   QCOMPARE( results[2].mDerivedAttributes[QStringLiteral( "Snapped Vertex Position Y" )], QStringLiteral( "3000" ) );
761 
762   tempLayer->temporalProperties()->setIsActive( false );
763 
764   // scalar + vector same
765   tempLayer->setStaticScalarDatasetIndex( QgsMeshDatasetIndex( 1, 0 ) );
766   tempLayer->setStaticVectorDatasetIndex( QgsMeshDatasetIndex( 1, 0 ) );
767   results = testIdentifyMesh( tempLayer, 500, 500 );
768   QCOMPARE( results.size(), 2 );
769   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Vector Value" )], QStringLiteral( "no data" ) );
770   results = testIdentifyMesh( tempLayer, 2400, 2400 );
771   QCOMPARE( results.size(), 2 );
772   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Vector Magnitude" )], QStringLiteral( "3" ) );
773   QCOMPARE( results[0].mDerivedAttributes[ QStringLiteral( "Vector x-component" )], QStringLiteral( "1.8" ) );
774   QCOMPARE( results[0].mDerivedAttributes[ QStringLiteral( "Vector y-component" )], QStringLiteral( "2.4" ) );
775 
776   // scalar + vector different
777   tempLayer->setStaticScalarDatasetIndex( QgsMeshDatasetIndex( 0, 0 ) );
778   tempLayer->setStaticVectorDatasetIndex( QgsMeshDatasetIndex( 1, 0 ) );
779   results = testIdentifyMesh( tempLayer, 2400, 2400 );
780   QCOMPARE( results.size(), 3 );
781   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Scalar Value" )], QStringLiteral( "42" ) );
782   QCOMPARE( results[1].mAttributes[ QStringLiteral( "Vector Magnitude" )], QStringLiteral( "3" ) );
783   QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Vector x-component" )], QStringLiteral( "1.8" ) );
784   QCOMPARE( results[1].mDerivedAttributes[ QStringLiteral( "Vector y-component" )], QStringLiteral( "2.4" ) );
785 
786   // only vector
787   tempLayer->setStaticScalarDatasetIndex( QgsMeshDatasetIndex() );
788   tempLayer->setStaticVectorDatasetIndex( QgsMeshDatasetIndex( 1, 0 ) );
789   results = testIdentifyMesh( tempLayer, 2400, 2400 );
790   QCOMPARE( results.size(), 2 );
791   QCOMPARE( results[0].mAttributes[ QStringLiteral( "Vector Magnitude" )], QStringLiteral( "3" ) );
792   QCOMPARE( results[0].mDerivedAttributes[ QStringLiteral( "Vector x-component" )], QStringLiteral( "1.8" ) );
793   QCOMPARE( results[0].mDerivedAttributes[ QStringLiteral( "Vector y-component" )], QStringLiteral( "2.4" ) );
794 }
795 
identifyVectorTile()796 void TestQgsMapToolIdentifyAction::identifyVectorTile()
797 {
798   //create a temporary layer
799   const QString vtPath = QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/vector_tile/{z}-{x}-{y}.pbf" );
800   QgsDataSourceUri dsUri;
801   dsUri.setParam( QStringLiteral( "type" ), QStringLiteral( "xyz" ) );
802   dsUri.setParam( QStringLiteral( "url" ), QUrl::fromLocalFile( vtPath ).toString() );
803   QgsVectorTileLayer *tempLayer = new QgsVectorTileLayer( dsUri.encodedUri(), QStringLiteral( "testlayer" ) );
804   QVERIFY( tempLayer->isValid() );
805 
806   const QgsCoordinateReferenceSystem srs( QStringLiteral( "EPSG:3857" ) );
807   canvas->setDestinationCrs( srs );
808   canvas->setExtent( tempLayer->extent() );
809   canvas->resize( 512, 512 );
810   canvas->setLayers( QList<QgsMapLayer *>() << tempLayer );
811   canvas->setCurrentLayer( tempLayer );
812 
813   QList<QgsMapToolIdentify::IdentifyResult> results;
814   results = testIdentifyVectorTile( tempLayer, 15186127, -2974969 );
815   QCOMPARE( results.size(), 1 );
816   QCOMPARE( results[0].mLayer, tempLayer );
817   QCOMPARE( results[0].mLabel, QStringLiteral( "place" ) );
818   QCOMPARE( results[0].mFeature.geometry().wkbType(), QgsWkbTypes::Point );
819   QCOMPARE( results[0].mFeature.attribute( QStringLiteral( "class" ) ).toString(), QStringLiteral( "country" ) );
820   QCOMPARE( results[0].mFeature.attribute( QStringLiteral( "name" ) ).toString(), QStringLiteral( "Australia" ) );
821 
822   delete tempLayer;
823 }
824 
identifyInvalidPolygons()825 void TestQgsMapToolIdentifyAction::identifyInvalidPolygons()
826 {
827   //create a temporary layer
828   std::unique_ptr< QgsVectorLayer > memoryLayer( new QgsVectorLayer( QStringLiteral( "Polygon?field=pk:int" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) ) );
829   QVERIFY( memoryLayer->isValid() );
830   QgsFeature f1( memoryLayer->dataProvider()->fields(), 1 );
831   f1.setAttribute( QStringLiteral( "pk" ), 1 );
832   // This geometry is an invalid polygon (3 distinct vertices).
833   // GEOS reported invalidity: Points of LinearRing do not form a closed linestring
834   f1.setGeometry( geomFromHexWKB(
835                     "010300000001000000030000000000000000000000000000000000000000000000000024400000000000000000000000000000244000000000000024400000000000000000"
836                   ) );
837   // TODO: check why we need the ->dataProvider() part, since
838   //       there's a QgsVectorLayer::addFeatures method too
839   //memoryLayer->addFeatures( QgsFeatureList() << f1 );
840   memoryLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 );
841 
842   canvas->setExtent( QgsRectangle( 0, 0, 10, 10 ) );
843   QList<QgsMapToolIdentify::IdentifyResult> identified;
844   identified = testIdentifyVector( memoryLayer.get(), 4, 6 );
845   QCOMPARE( identified.length(), 0 );
846   identified = testIdentifyVector( memoryLayer.get(), 6, 4 );
847   QCOMPARE( identified.length(), 1 );
848   QCOMPARE( identified[0].mFeature.attribute( "pk" ), QVariant( 1 ) );
849 
850 }
851 
852 
853 QGSTEST_MAIN( TestQgsMapToolIdentifyAction )
854 #include "testqgsmaptoolidentifyaction.moc"
855 
856 
857 
858 
859