1 /***************************************************************************
2     qgsmaptoolidentify.cpp  -  map tool for identifying features
3     ---------------------
4     begin                : January 2006
5     copyright            : (C) 2006 by Martin Dobias
6     email                : wonder.sk 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 "qgsapplication.h"
17 #include "qgscoordinateformatter.h"
18 #include "qgsdistancearea.h"
19 #include "qgsfeature.h"
20 #include "qgsfeatureiterator.h"
21 #include "qgsfeaturestore.h"
22 #include "qgsfields.h"
23 #include "qgsgeometry.h"
24 #include "qgsgeometryengine.h"
25 #include "qgsidentifymenu.h"
26 #include "qgslogger.h"
27 #include "qgsmapcanvas.h"
28 #include "qgsmaptoolidentify.h"
29 #include "qgsmaptopixel.h"
30 #include "qgsmessageviewer.h"
31 #include "qgsmeshlayer.h"
32 #include "qgsmeshlayertemporalproperties.h"
33 #include "qgsmaplayer.h"
34 #include "qgsrasterdataprovider.h"
35 #include "qgsrasterlayer.h"
36 #include "qgsrasteridentifyresult.h"
37 #include "qgscoordinatereferencesystem.h"
38 #include "qgsvectordataprovider.h"
39 #include "qgsvectorlayer.h"
40 #include "qgsvectorlayertemporalproperties.h"
41 #include "qgsvectortilelayer.h"
42 #include "qgsvectortilemvtdecoder.h"
43 #include "qgsvectortileutils.h"
44 #include "qgsproject.h"
45 #include "qgsrenderer.h"
46 #include "qgstiles.h"
47 #include "qgsgeometryutils.h"
48 #include "qgsgeometrycollection.h"
49 #include "qgscurve.h"
50 #include "qgscoordinateutils.h"
51 #include "qgsexception.h"
52 #include "qgssettings.h"
53 #include "qgsexpressioncontextutils.h"
54 #include "qgspointcloudlayer.h"
55 #include "qgspointcloudrenderer.h"
56 #include "qgspointcloudlayerrenderer.h"
57 #include "qgspointcloudlayerelevationproperties.h"
58 #include "qgssymbol.h"
59 #include "qgsmultilinestring.h"
60 
61 #include <QMouseEvent>
62 #include <QCursor>
63 #include <QPixmap>
64 #include <QStatusBar>
65 #include <QVariant>
66 
QgsMapToolIdentify(QgsMapCanvas * canvas)67 QgsMapToolIdentify::QgsMapToolIdentify( QgsMapCanvas *canvas )
68   : QgsMapTool( canvas )
69   , mIdentifyMenu( new QgsIdentifyMenu( mCanvas ) )
70   , mLastMapUnitsPerPixel( -1.0 )
71   , mCoordinatePrecision( 6 )
72 {
73   setCursor( QgsApplication::getThemeCursor( QgsApplication::Cursor::Identify ) );
74 }
75 
~QgsMapToolIdentify()76 QgsMapToolIdentify::~QgsMapToolIdentify()
77 {
78   delete mIdentifyMenu;
79 }
80 
canvasMoveEvent(QgsMapMouseEvent * e)81 void QgsMapToolIdentify::canvasMoveEvent( QgsMapMouseEvent *e )
82 {
83   Q_UNUSED( e )
84 }
85 
canvasPressEvent(QgsMapMouseEvent * e)86 void QgsMapToolIdentify::canvasPressEvent( QgsMapMouseEvent *e )
87 {
88   Q_UNUSED( e )
89 }
90 
canvasReleaseEvent(QgsMapMouseEvent * e)91 void QgsMapToolIdentify::canvasReleaseEvent( QgsMapMouseEvent *e )
92 {
93   Q_UNUSED( e )
94 }
95 
identify(int x,int y,const QList<QgsMapLayer * > & layerList,IdentifyMode mode,const QgsIdentifyContext & identifyContext)96 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, const QList<QgsMapLayer *> &layerList, IdentifyMode mode, const QgsIdentifyContext &identifyContext )
97 {
98   return identify( x, y, mode, layerList, AllLayers, identifyContext );
99 }
100 
identify(int x,int y,IdentifyMode mode,LayerType layerType,const QgsIdentifyContext & identifyContext)101 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, LayerType layerType, const QgsIdentifyContext &identifyContext )
102 {
103   return identify( x, y, mode, QList<QgsMapLayer *>(), layerType, identifyContext );
104 }
105 
identify(int x,int y,IdentifyMode mode,const QList<QgsMapLayer * > & layerList,LayerType layerType,const QgsIdentifyContext & identifyContext)106 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, const QList<QgsMapLayer *> &layerList, LayerType layerType, const QgsIdentifyContext &identifyContext )
107 {
108   return identify( QgsGeometry::fromPointXY( toMapCoordinates( QPoint( x, y ) ) ), mode, layerList, layerType, identifyContext );
109 }
110 
identify(const QgsGeometry & geometry,IdentifyMode mode,LayerType layerType,const QgsIdentifyContext & identifyContext)111 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( const QgsGeometry &geometry, IdentifyMode mode, LayerType layerType, const QgsIdentifyContext &identifyContext )
112 {
113   return identify( geometry, mode, QList<QgsMapLayer *>(), layerType, identifyContext );
114 }
115 
identify(const QgsGeometry & geometry,IdentifyMode mode,const QList<QgsMapLayer * > & layerList,LayerType layerType,const QgsIdentifyContext & identifyContext)116 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( const QgsGeometry &geometry, IdentifyMode mode, const QList<QgsMapLayer *> &layerList, LayerType layerType, const QgsIdentifyContext &identifyContext )
117 {
118   QList<IdentifyResult> results;
119 
120   mLastGeometry = geometry;
121   mLastExtent = mCanvas->extent();
122   mLastMapUnitsPerPixel = mCanvas->mapUnitsPerPixel();
123 
124   mCoordinatePrecision = QgsCoordinateUtils::calculateCoordinatePrecision( mLastMapUnitsPerPixel, mCanvas->mapSettings().destinationCrs() );
125 
126   if ( mode == DefaultQgsSetting )
127   {
128     QgsSettings settings;
129     mode = settings.enumValue( QStringLiteral( "Map/identifyMode" ), ActiveLayer );
130   }
131 
132   if ( mode == LayerSelection )
133   {
134     QPoint canvasPt = toCanvasCoordinates( geometry.asPoint() );
135     int x = canvasPt.x(), y = canvasPt.y();
136     QList<IdentifyResult> results = identify( x, y, TopDownAll, layerList, layerType, identifyContext );
137     QPoint globalPos = mCanvas->mapToGlobal( QPoint( x + 5, y + 5 ) );
138     return mIdentifyMenu->exec( results, globalPos );
139   }
140   else if ( mode == ActiveLayer && layerList.isEmpty() )
141   {
142     QgsMapLayer *layer = mCanvas->currentLayer();
143 
144     if ( !layer )
145     {
146       emit identifyMessage( tr( "No active layer. To identify features, you must choose an active layer." ) );
147       return results;
148     }
149     if ( !layer->flags().testFlag( QgsMapLayer::Identifiable ) )
150       return results;
151 
152     QApplication::setOverrideCursor( Qt::WaitCursor );
153 
154     identifyLayer( &results, layer, mLastGeometry, mLastExtent, mLastMapUnitsPerPixel, layerType, identifyContext );
155   }
156   else
157   {
158     QApplication::setOverrideCursor( Qt::WaitCursor );
159 
160     int layerCount;
161     if ( layerList.isEmpty() )
162       layerCount = mCanvas->layerCount();
163     else
164       layerCount = layerList.count();
165 
166 
167     for ( int i = 0; i < layerCount; i++ )
168     {
169 
170       QgsMapLayer *layer = nullptr;
171       if ( layerList.isEmpty() )
172         layer = mCanvas->layer( i );
173       else
174         layer = layerList.value( i );
175 
176       emit identifyProgress( i, mCanvas->layerCount() );
177       emit identifyMessage( tr( "Identifying on %1…" ).arg( layer->name() ) );
178 
179       if ( !layer->flags().testFlag( QgsMapLayer::Identifiable ) )
180         continue;
181 
182       if ( identifyLayer( &results, layer,  mLastGeometry, mLastExtent, mLastMapUnitsPerPixel, layerType, identifyContext ) )
183       {
184         if ( mode == TopDownStopAtFirst )
185           break;
186       }
187     }
188 
189     emit identifyProgress( mCanvas->layerCount(), mCanvas->layerCount() );
190     emit identifyMessage( tr( "Identifying done." ) );
191   }
192 
193   QApplication::restoreOverrideCursor();
194 
195   return results;
196 }
197 
setCanvasPropertiesOverrides(double searchRadiusMapUnits)198 void QgsMapToolIdentify::setCanvasPropertiesOverrides( double searchRadiusMapUnits )
199 {
200   mOverrideCanvasSearchRadius = searchRadiusMapUnits;
201 }
202 
restoreCanvasPropertiesOverrides()203 void QgsMapToolIdentify::restoreCanvasPropertiesOverrides()
204 {
205   mOverrideCanvasSearchRadius = -1;
206 }
207 
activate()208 void QgsMapToolIdentify::activate()
209 {
210   QgsMapTool::activate();
211 }
212 
deactivate()213 void QgsMapToolIdentify::deactivate()
214 {
215   QgsMapTool::deactivate();
216 }
217 
identifyLayer(QList<IdentifyResult> * results,QgsMapLayer * layer,const QgsPointXY & point,const QgsRectangle & viewExtent,double mapUnitsPerPixel,QgsMapToolIdentify::LayerType layerType,const QgsIdentifyContext & identifyContext)218 bool QgsMapToolIdentify::identifyLayer( QList<IdentifyResult> *results, QgsMapLayer *layer, const QgsPointXY &point, const QgsRectangle &viewExtent, double mapUnitsPerPixel, QgsMapToolIdentify::LayerType layerType, const QgsIdentifyContext &identifyContext )
219 {
220   return identifyLayer( results, layer, QgsGeometry::fromPointXY( point ), viewExtent, mapUnitsPerPixel, layerType, identifyContext );
221 }
222 
identifyLayer(QList<IdentifyResult> * results,QgsMapLayer * layer,const QgsGeometry & geometry,const QgsRectangle & viewExtent,double mapUnitsPerPixel,QgsMapToolIdentify::LayerType layerType,const QgsIdentifyContext & identifyContext)223 bool QgsMapToolIdentify::identifyLayer( QList<IdentifyResult> *results, QgsMapLayer *layer, const QgsGeometry &geometry, const QgsRectangle &viewExtent, double mapUnitsPerPixel, QgsMapToolIdentify::LayerType layerType, const QgsIdentifyContext &identifyContext )
224 {
225   if ( layer->type() == QgsMapLayerType::RasterLayer && layerType.testFlag( RasterLayer ) )
226   {
227     return identifyRasterLayer( results, qobject_cast<QgsRasterLayer *>( layer ), geometry, viewExtent, mapUnitsPerPixel, identifyContext );
228   }
229   else if ( layer->type() == QgsMapLayerType::VectorLayer && layerType.testFlag( VectorLayer ) )
230   {
231     return identifyVectorLayer( results, qobject_cast<QgsVectorLayer *>( layer ), geometry, identifyContext );
232   }
233   else if ( layer->type() == QgsMapLayerType::MeshLayer && layerType.testFlag( MeshLayer ) )
234   {
235     return identifyMeshLayer( results, qobject_cast<QgsMeshLayer *>( layer ), geometry, identifyContext );
236   }
237   else if ( layer->type() == QgsMapLayerType::VectorTileLayer && layerType.testFlag( VectorTileLayer ) )
238   {
239     return identifyVectorTileLayer( results, qobject_cast<QgsVectorTileLayer *>( layer ), geometry, identifyContext );
240   }
241   else if ( layer->type() == QgsMapLayerType::PointCloudLayer && layerType.testFlag( PointCloudLayer ) )
242   {
243     return identifyPointCloudLayer( results, qobject_cast<QgsPointCloudLayer *>( layer ), geometry, identifyContext );
244   }
245   else
246   {
247     return false;
248   }
249 }
250 
identifyVectorLayer(QList<QgsMapToolIdentify::IdentifyResult> * results,QgsVectorLayer * layer,const QgsPointXY & point,const QgsIdentifyContext & identifyContext)251 bool QgsMapToolIdentify::identifyVectorLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorLayer *layer, const QgsPointXY &point, const QgsIdentifyContext &identifyContext )
252 {
253   return identifyVectorLayer( results, layer, QgsGeometry::fromPointXY( point ), identifyContext );
254 }
255 
identifyMeshLayer(QList<QgsMapToolIdentify::IdentifyResult> * results,QgsMeshLayer * layer,const QgsGeometry & geometry,const QgsIdentifyContext & identifyContext)256 bool QgsMapToolIdentify::identifyMeshLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsMeshLayer *layer, const QgsGeometry &geometry, const QgsIdentifyContext &identifyContext )
257 {
258   const QgsPointXY point = geometry.asPoint();  // mesh layers currently only support identification by point
259   return identifyMeshLayer( results, layer, point, identifyContext );
260 }
261 
identifyMeshLayer(QList<QgsMapToolIdentify::IdentifyResult> * results,QgsMeshLayer * layer,const QgsPointXY & point,const QgsIdentifyContext & identifyContext)262 bool QgsMapToolIdentify::identifyMeshLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsMeshLayer *layer, const QgsPointXY &point, const QgsIdentifyContext &identifyContext )
263 {
264   QgsDebugMsgLevel( "point = " + point.toString(), 4 );
265   if ( !layer )
266     return false;
267 
268   double searchRadius = mOverrideCanvasSearchRadius < 0 ? searchRadiusMU( mCanvas ) : mOverrideCanvasSearchRadius;
269   bool isTemporal = identifyContext.isTemporal() && layer->temporalProperties()->isActive();
270 
271   QList<QgsMeshDatasetIndex> datasetIndexList;
272   int activeScalarGroup = layer->rendererSettings().activeScalarDatasetGroup();
273   int activeVectorGroup = layer->rendererSettings().activeVectorDatasetGroup();
274 
275   const QList<int> allGroup = layer->enabledDatasetGroupsIndexes();
276   if ( isTemporal ) //non active dataset group value are only accesible if temporal is active
277   {
278     const QgsDateTimeRange &time = identifyContext.temporalRange();
279     if ( activeScalarGroup >= 0 )
280       datasetIndexList.append( layer->activeScalarDatasetAtTime( time ) );
281     if ( activeVectorGroup >= 0 && activeVectorGroup != activeScalarGroup )
282       datasetIndexList.append( layer->activeVectorDatasetAtTime( time ) );
283 
284     for ( int groupIndex : allGroup )
285     {
286       if ( groupIndex != activeScalarGroup && groupIndex != activeVectorGroup )
287         datasetIndexList.append( layer->datasetIndexAtTime( time, groupIndex ) );
288     }
289   }
290   else
291   {
292     // only active dataset group
293     if ( activeScalarGroup >= 0 )
294       datasetIndexList.append( layer->staticScalarDatasetIndex() );
295     if ( activeVectorGroup >= 0 && activeVectorGroup != activeScalarGroup )
296       datasetIndexList.append( layer->staticVectorDatasetIndex() );
297 
298     // ...and static dataset group
299     for ( int groupIndex : allGroup )
300     {
301       if ( groupIndex != activeScalarGroup && groupIndex != activeVectorGroup )
302       {
303         if ( !layer->datasetGroupMetadata( groupIndex ).isTemporal() )
304           datasetIndexList.append( groupIndex );
305       }
306     }
307   }
308 
309   //create results
310   for ( const QgsMeshDatasetIndex &index : datasetIndexList )
311   {
312     if ( !index.isValid() )
313       continue;
314 
315     const QgsMeshDatasetGroupMetadata &groupMeta = layer->datasetGroupMetadata( index );
316     QMap< QString, QString > derivedAttributes;
317 
318     QMap<QString, QString> attribute;
319     if ( groupMeta.isScalar() )
320     {
321       const QgsMeshDatasetValue scalarValue = layer->datasetValue( index, point, searchRadius );
322       const double scalar = scalarValue.scalar();
323       attribute.insert( tr( "Scalar Value" ), std::isnan( scalar ) ? tr( "no data" ) : QLocale().toString( scalar ) );
324     }
325 
326     if ( groupMeta.isVector() )
327     {
328       const QgsMeshDatasetValue vectorValue = layer->datasetValue( index, point, searchRadius );
329       const double vectorX = vectorValue.x();
330       const double vectorY = vectorValue.y();
331       if ( std::isnan( vectorX ) || std::isnan( vectorY ) )
332         attribute.insert( tr( "Vector Value" ), tr( "no data" ) );
333       else
334       {
335         attribute.insert( tr( "Vector Magnitude" ), QLocale().toString( vectorValue.scalar() ) );
336         derivedAttributes.insert( tr( "Vector x-component" ), QLocale().toString( vectorY ) );
337         derivedAttributes.insert( tr( "Vector y-component" ), QLocale().toString( vectorX ) );
338       }
339     }
340 
341     const QgsMeshDatasetMetadata &meta = layer->datasetMetadata( index );
342 
343     if ( groupMeta.isTemporal() )
344       derivedAttributes.insert( tr( "Time Step" ), layer->formatTime( meta.time() ) );
345     derivedAttributes.insert( tr( "Source" ), groupMeta.uri() );
346 
347     QString resultName = groupMeta.name();
348     if ( isTemporal && ( index.group() == activeScalarGroup  || index.group() == activeVectorGroup ) )
349       resultName.append( tr( " (active)" ) );
350 
351     const IdentifyResult result( layer,
352                                  resultName,
353                                  attribute,
354                                  derivedAttributes );
355 
356     results->append( result );
357   }
358 
359   QMap<QString, QString> derivedGeometry;
360 
361   QgsPointXY vertexPoint = layer->snapOnElement( QgsMesh::Vertex, point, searchRadius );
362   if ( !vertexPoint.isEmpty() )
363   {
364     derivedGeometry.insert( tr( "Snapped Vertex Position X" ), QLocale().toString( vertexPoint.x() ) );
365     derivedGeometry.insert( tr( "Snapped Vertex Position Y" ), QLocale().toString( vertexPoint.y() ) );
366   }
367 
368   QgsPointXY faceCentroid = layer->snapOnElement( QgsMesh::Face, point, searchRadius );
369   if ( !faceCentroid.isEmpty() )
370   {
371     derivedGeometry.insert( tr( "Face Centroid X" ), QLocale().toString( faceCentroid.x() ) );
372     derivedGeometry.insert( tr( "Face Centroid Y" ), QLocale().toString( faceCentroid.y() ) );
373   }
374 
375   QgsPointXY pointOnEdge = layer->snapOnElement( QgsMesh::Edge, point, searchRadius );
376   if ( !pointOnEdge.isEmpty() )
377   {
378     derivedGeometry.insert( tr( "Point on Edge X" ), QLocale().toString( pointOnEdge.x() ) );
379     derivedGeometry.insert( tr( "Point on Edge Y" ), QLocale().toString( pointOnEdge.y() ) );
380   }
381 
382   const IdentifyResult result( layer,
383                                tr( "Geometry" ),
384                                derivedAttributesForPoint( QgsPoint( point ) ),
385                                derivedGeometry );
386 
387   results->append( result );
388 
389   return true;
390 }
391 
identifyVectorTileLayer(QList<QgsMapToolIdentify::IdentifyResult> * results,QgsVectorTileLayer * layer,const QgsGeometry & geometry,const QgsIdentifyContext & identifyContext)392 bool QgsMapToolIdentify::identifyVectorTileLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorTileLayer *layer, const QgsGeometry &geometry, const QgsIdentifyContext &identifyContext )
393 {
394   Q_UNUSED( identifyContext )
395   if ( !layer || !layer->isSpatial() )
396     return false;
397 
398   if ( !layer->isInScaleRange( mCanvas->mapSettings().scale() ) )
399   {
400     QgsDebugMsgLevel( QStringLiteral( "Out of scale limits" ), 2 );
401     return false;
402   }
403 
404   QgsTemporaryCursorOverride waitCursor( Qt::WaitCursor );
405 
406   QMap< QString, QString > commonDerivedAttributes;
407 
408   QgsGeometry selectionGeom = geometry;
409   bool isPointOrRectangle;
410   QgsPointXY point;
411   bool isSingleClick = selectionGeom.type() == QgsWkbTypes::PointGeometry;
412   if ( isSingleClick )
413   {
414     isPointOrRectangle = true;
415     point = selectionGeom.asPoint();
416 
417     commonDerivedAttributes = derivedAttributesForPoint( QgsPoint( point ) );
418   }
419   else
420   {
421     // we have a polygon - maybe it is a rectangle - in such case we can avoid costly insterestion tests later
422     isPointOrRectangle = QgsGeometry::fromRect( selectionGeom.boundingBox() ).isGeosEqual( selectionGeom );
423   }
424 
425   int featureCount = 0;
426 
427   std::unique_ptr<QgsGeometryEngine> selectionGeomPrepared;
428 
429   // toLayerCoordinates will throw an exception for an 'invalid' point.
430   // For example, if you project a world map onto a globe using EPSG 2163
431   // and then click somewhere off the globe, an exception will be thrown.
432   try
433   {
434     QgsRectangle r;
435     if ( isSingleClick )
436     {
437       double sr = mOverrideCanvasSearchRadius < 0 ? searchRadiusMU( mCanvas ) : mOverrideCanvasSearchRadius;
438       r = toLayerCoordinates( layer, QgsRectangle( point.x() - sr, point.y() - sr, point.x() + sr, point.y() + sr ) );
439     }
440     else
441     {
442       r = toLayerCoordinates( layer, selectionGeom.boundingBox() );
443 
444       if ( !isPointOrRectangle )
445       {
446         QgsCoordinateTransform ct( mCanvas->mapSettings().destinationCrs(), layer->crs(), mCanvas->mapSettings().transformContext() );
447         if ( ct.isValid() )
448           selectionGeom.transform( ct );
449 
450         // use prepared geometry for faster intersection test
451         selectionGeomPrepared.reset( QgsGeometry::createGeometryEngine( selectionGeom.constGet() ) );
452       }
453     }
454 
455     int tileZoom = QgsVectorTileUtils::scaleToZoomLevel( mCanvas->scale(), layer->sourceMinZoom(), layer->sourceMaxZoom() );
456     QgsTileMatrix tileMatrix = QgsTileMatrix::fromWebMercator( tileZoom );
457     QgsTileRange tileRange = tileMatrix.tileRangeFromExtent( r );
458 
459     for ( int row = tileRange.startRow(); row <= tileRange.endRow(); ++row )
460     {
461       for ( int col = tileRange.startColumn(); col <= tileRange.endColumn(); ++col )
462       {
463         QgsTileXYZ tileID( col, row, tileZoom );
464         QByteArray data = layer->getRawTile( tileID );
465         if ( data.isEmpty() )
466           continue;  // failed to get data
467 
468         QgsVectorTileMVTDecoder decoder;
469         if ( !decoder.decode( tileID, data ) )
470           continue;  // failed to decode
471 
472         QMap<QString, QgsFields> perLayerFields;
473         const QStringList layerNames = decoder.layers();
474         for ( const QString &layerName : layerNames )
475         {
476           QSet<QString> fieldNames = qgis::listToSet( decoder.layerFieldNames( layerName ) );
477           perLayerFields[layerName] = QgsVectorTileUtils::makeQgisFields( fieldNames );
478         }
479 
480         const QgsVectorTileFeatures features = decoder.layerFeatures( perLayerFields, QgsCoordinateTransform() );
481         const QStringList featuresLayerNames = features.keys();
482         for ( const QString &layerName : featuresLayerNames )
483         {
484           const QgsFields fFields = perLayerFields[layerName];
485           const QVector<QgsFeature> &layerFeatures = features[layerName];
486           for ( const QgsFeature &f : layerFeatures )
487           {
488             if ( f.geometry().intersects( r ) && ( !selectionGeomPrepared || selectionGeomPrepared->intersects( f.geometry().constGet() ) ) )
489             {
490               QMap< QString, QString > derivedAttributes = commonDerivedAttributes;
491               derivedAttributes.insert( tr( "Feature ID" ), FID_TO_STRING( f.id() ) );
492 
493               results->append( IdentifyResult( layer, layerName, fFields, f, derivedAttributes ) );
494 
495               featureCount++;
496             }
497           }
498         }
499       }
500     }
501 
502   }
503   catch ( QgsCsException &cse )
504   {
505     Q_UNUSED( cse )
506     // catch exception for 'invalid' point and proceed with no features found
507     QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
508   }
509 
510   return featureCount > 0;
511 }
512 
identifyPointCloudLayer(QList<QgsMapToolIdentify::IdentifyResult> * results,QgsPointCloudLayer * layer,const QgsGeometry & geometry,const QgsIdentifyContext & identifyContext)513 bool QgsMapToolIdentify::identifyPointCloudLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsPointCloudLayer *layer, const QgsGeometry &geometry, const QgsIdentifyContext &identifyContext )
514 {
515   Q_UNUSED( identifyContext )
516   QgsPointCloudRenderer *renderer = layer->renderer();
517 
518   QgsRenderContext context = QgsRenderContext::fromMapSettings( mCanvas->mapSettings() );
519   context.setCoordinateTransform( QgsCoordinateTransform( layer->crs(), mCanvas->mapSettings().destinationCrs(), mCanvas->mapSettings().transformContext() ) );
520 
521   const double searchRadiusMapUnits = mOverrideCanvasSearchRadius < 0 ? searchRadiusMU( mCanvas ) : mOverrideCanvasSearchRadius;
522 
523   const QVector<QVariantMap> points = renderer->identify( layer, context, geometry, searchRadiusMapUnits );
524 
525   fromPointCloudIdentificationToIdentifyResults( layer, points, *results );
526 
527   return true;
528 }
529 
derivedAttributesForPoint(const QgsPoint & point)530 QMap<QString, QString> QgsMapToolIdentify::derivedAttributesForPoint( const QgsPoint &point )
531 {
532   QMap< QString, QString > derivedAttributes;
533   derivedAttributes.insert( tr( "(clicked coordinate X)" ), formatXCoordinate( point ) );
534   derivedAttributes.insert( tr( "(clicked coordinate Y)" ), formatYCoordinate( point ) );
535   if ( point.is3D() )
536     derivedAttributes.insert( tr( "(clicked coordinate Z)" ), QLocale().toString( point.z(), 'f' ) );
537   return derivedAttributes;
538 }
539 
identifyVectorLayer(QList<QgsMapToolIdentify::IdentifyResult> * results,QgsVectorLayer * layer,const QgsGeometry & geometry,const QgsIdentifyContext & identifyContext)540 bool QgsMapToolIdentify::identifyVectorLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsVectorLayer *layer, const QgsGeometry &geometry, const QgsIdentifyContext &identifyContext )
541 {
542   if ( !layer || !layer->isSpatial() || !layer->dataProvider() )
543     return false;
544 
545   if ( !layer->isInScaleRange( mCanvas->mapSettings().scale() ) )
546   {
547     QgsDebugMsg( QStringLiteral( "Out of scale limits" ) );
548     return false;
549   }
550 
551   QString temporalFilter;
552   if ( identifyContext.isTemporal() )
553   {
554     if ( !layer->temporalProperties()->isVisibleInTemporalRange( identifyContext.temporalRange() ) )
555       return false;
556 
557     QgsVectorLayerTemporalContext temporalContext;
558     temporalContext.setLayer( layer );
559     temporalFilter = qobject_cast< const QgsVectorLayerTemporalProperties * >( layer->temporalProperties() )->createFilterString( temporalContext, identifyContext.temporalRange() );
560   }
561 
562   const bool fetchFeatureSymbols = layer->dataProvider()->capabilities() & QgsVectorDataProvider::FeatureSymbology;
563 
564   QApplication::setOverrideCursor( Qt::WaitCursor );
565 
566   QMap< QString, QString > commonDerivedAttributes;
567 
568   QgsGeometry selectionGeom = geometry;
569   bool isPointOrRectangle;
570   QgsPointXY point;
571   bool isSingleClick = selectionGeom.type() == QgsWkbTypes::PointGeometry;
572   if ( isSingleClick )
573   {
574     isPointOrRectangle = true;
575     point = selectionGeom.asPoint();
576 
577     commonDerivedAttributes = derivedAttributesForPoint( QgsPoint( point ) );
578   }
579   else
580   {
581     // we have a polygon - maybe it is a rectangle - in such case we can avoid costly insterestion tests later
582     isPointOrRectangle = QgsGeometry::fromRect( selectionGeom.boundingBox() ).isGeosEqual( selectionGeom );
583   }
584 
585   int featureCount = 0;
586 
587   QgsFeatureList featureList;
588   std::unique_ptr<QgsGeometryEngine> selectionGeomPrepared;
589 
590   // toLayerCoordinates will throw an exception for an 'invalid' point.
591   // For example, if you project a world map onto a globe using EPSG 2163
592   // and then click somewhere off the globe, an exception will be thrown.
593   try
594   {
595     QgsRectangle r;
596     if ( isSingleClick )
597     {
598       double sr = mOverrideCanvasSearchRadius < 0 ? searchRadiusMU( mCanvas ) : mOverrideCanvasSearchRadius;
599       r = toLayerCoordinates( layer, QgsRectangle( point.x() - sr, point.y() - sr, point.x() + sr, point.y() + sr ) );
600     }
601     else
602     {
603       r = toLayerCoordinates( layer, selectionGeom.boundingBox() );
604 
605       if ( !isPointOrRectangle )
606       {
607         QgsCoordinateTransform ct( mCanvas->mapSettings().destinationCrs(), layer->crs(), mCanvas->mapSettings().transformContext() );
608         if ( ct.isValid() )
609           selectionGeom.transform( ct );
610 
611         // use prepared geometry for faster intersection test
612         selectionGeomPrepared.reset( QgsGeometry::createGeometryEngine( selectionGeom.constGet() ) );
613       }
614     }
615 
616     QgsFeatureRequest featureRequest;
617     featureRequest.setFilterRect( r );
618     featureRequest.setFlags( QgsFeatureRequest::ExactIntersect | ( fetchFeatureSymbols ? QgsFeatureRequest::EmbeddedSymbols : QgsFeatureRequest::Flags() ) );
619     if ( !temporalFilter.isEmpty() )
620       featureRequest.setFilterExpression( temporalFilter );
621 
622     QgsFeatureIterator fit = layer->getFeatures( featureRequest );
623     QgsFeature f;
624     while ( fit.nextFeature( f ) )
625     {
626       if ( !selectionGeomPrepared || selectionGeomPrepared->intersects( f.geometry().constGet() ) )
627         featureList << QgsFeature( f );
628     }
629   }
630   catch ( QgsCsException &cse )
631   {
632     Q_UNUSED( cse )
633     // catch exception for 'invalid' point and proceed with no features found
634     QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
635   }
636 
637   bool filter = false;
638 
639   QgsRenderContext context( QgsRenderContext::fromMapSettings( mCanvas->mapSettings() ) );
640   context.setExpressionContext( mCanvas->createExpressionContext() );
641   context.expressionContext() << QgsExpressionContextUtils::layerScope( layer );
642   std::unique_ptr< QgsFeatureRenderer > renderer( layer->renderer() ? layer->renderer()->clone() : nullptr );
643   if ( renderer )
644   {
645     // setup scale for scale dependent visibility (rule based)
646     renderer->startRender( context, layer->fields() );
647     filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
648   }
649 
650   for ( const QgsFeature &feature : std::as_const( featureList ) )
651   {
652     QMap< QString, QString > derivedAttributes = commonDerivedAttributes;
653 
654     QgsFeatureId fid = feature.id();
655     context.expressionContext().setFeature( feature );
656 
657     if ( filter && !renderer->willRenderFeature( feature, context ) )
658       continue;
659 
660     featureCount++;
661 
662     // When not single click identify, pass an empty point so some derived attributes may still be computed
663     if ( !isSingleClick )
664       point = QgsPointXY();
665     derivedAttributes.unite( featureDerivedAttributes( feature, layer, toLayerCoordinates( layer, point ) ) );
666 
667     derivedAttributes.insert( tr( "Feature ID" ), fid < 0 ? tr( "new feature" ) : FID_TO_STRING( fid ) );
668 
669     results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), feature, derivedAttributes ) );
670   }
671 
672   if ( renderer )
673   {
674     renderer->stopRender( context );
675   }
676 
677   QgsDebugMsgLevel( "Feature count on identify: " + QString::number( featureCount ), 2 );
678 
679   QApplication::restoreOverrideCursor();
680   return featureCount > 0;
681 }
682 
closestVertexAttributes(const QgsAbstractGeometry & geometry,QgsVertexId vId,QgsMapLayer * layer,QMap<QString,QString> & derivedAttributes)683 void QgsMapToolIdentify::closestVertexAttributes( const QgsAbstractGeometry &geometry, QgsVertexId vId, QgsMapLayer *layer, QMap< QString, QString > &derivedAttributes )
684 {
685   if ( ! vId.isValid( ) )
686   {
687     // We should not get here ...
688     QgsDebugMsg( "Invalid vertex id!" );
689     return;
690   }
691 
692   QString str = QLocale().toString( vId.vertex + 1 );
693   derivedAttributes.insert( tr( "Closest vertex number" ), str );
694 
695   QgsPoint closestPoint = geometry.vertexAt( vId );
696 
697   QgsPointXY closestPointMapCoords = mCanvas->mapSettings().layerToMapCoordinates( layer, QgsPointXY( closestPoint.x(), closestPoint.y() ) );
698   derivedAttributes.insert( tr( "Closest vertex X" ), formatXCoordinate( closestPointMapCoords ) );
699   derivedAttributes.insert( tr( "Closest vertex Y" ), formatYCoordinate( closestPointMapCoords ) );
700 
701   if ( closestPoint.is3D() )
702   {
703     str = QLocale().toString( closestPoint.z(), 'g', 10 );
704     derivedAttributes.insert( tr( "Closest vertex Z" ), str );
705   }
706   if ( closestPoint.isMeasure() )
707   {
708     str = QLocale().toString( closestPoint.m(), 'g', 10 );
709     derivedAttributes.insert( tr( "Closest vertex M" ), str );
710   }
711 
712   if ( vId.type == Qgis::VertexType::Curve )
713   {
714     double radius, centerX, centerY;
715     QgsVertexId vIdBefore = vId;
716     --vIdBefore.vertex;
717     QgsVertexId vIdAfter = vId;
718     ++vIdAfter.vertex;
719     QgsGeometryUtils::circleCenterRadius( geometry.vertexAt( vIdBefore ), geometry.vertexAt( vId ),
720                                           geometry.vertexAt( vIdAfter ), radius, centerX, centerY );
721     derivedAttributes.insert( QStringLiteral( "Closest vertex radius" ), QLocale().toString( radius ) );
722   }
723 }
724 
closestPointAttributes(const QgsAbstractGeometry & geometry,const QgsPointXY & layerPoint,QMap<QString,QString> & derivedAttributes)725 void QgsMapToolIdentify::closestPointAttributes( const QgsAbstractGeometry &geometry, const QgsPointXY &layerPoint, QMap<QString, QString> &derivedAttributes )
726 {
727   QgsPoint closestPoint = QgsGeometryUtils::closestPoint( geometry, QgsPoint( layerPoint ) );
728 
729   derivedAttributes.insert( tr( "Closest X" ), formatXCoordinate( closestPoint ) );
730   derivedAttributes.insert( tr( "Closest Y" ), formatYCoordinate( closestPoint ) );
731 
732   if ( closestPoint.is3D() )
733   {
734     const QString str = QLocale().toString( closestPoint.z(), 'g', 10 );
735     derivedAttributes.insert( tr( "Interpolated Z" ), str );
736   }
737   if ( closestPoint.isMeasure() )
738   {
739     const QString str = QLocale().toString( closestPoint.m(), 'g', 10 );
740     derivedAttributes.insert( tr( "Interpolated M" ), str );
741   }
742 }
743 
formatCoordinate(const QgsPointXY & canvasPoint) const744 QString QgsMapToolIdentify::formatCoordinate( const QgsPointXY &canvasPoint ) const
745 {
746   return QgsCoordinateUtils::formatCoordinateForProject( QgsProject::instance(), canvasPoint, mCanvas->mapSettings().destinationCrs(),
747          mCoordinatePrecision );
748 }
749 
formatXCoordinate(const QgsPointXY & canvasPoint) const750 QString QgsMapToolIdentify::formatXCoordinate( const QgsPointXY &canvasPoint ) const
751 {
752   QString coordinate = formatCoordinate( canvasPoint );
753   return coordinate.split( QgsCoordinateFormatter::separator() ).at( 0 );
754 }
755 
formatYCoordinate(const QgsPointXY & canvasPoint) const756 QString QgsMapToolIdentify::formatYCoordinate( const QgsPointXY &canvasPoint ) const
757 {
758   QString coordinate = formatCoordinate( canvasPoint );
759   return coordinate.split( QgsCoordinateFormatter::separator() ).at( 1 );
760 }
761 
featureDerivedAttributes(const QgsFeature & feature,QgsMapLayer * layer,const QgsPointXY & layerPoint)762 QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( const QgsFeature &feature, QgsMapLayer *layer, const QgsPointXY &layerPoint )
763 {
764   // Calculate derived attributes and insert:
765   // measure distance or area depending on geometry type
766   QMap< QString, QString > derivedAttributes;
767 
768   // init distance/area calculator
769   QString ellipsoid = QgsProject::instance()->ellipsoid();
770   QgsDistanceArea calc;
771   calc.setEllipsoid( ellipsoid );
772   calc.setSourceCrs( layer->crs(), QgsProject::instance()->transformContext() );
773 
774   QgsWkbTypes::Type wkbType = QgsWkbTypes::NoGeometry;
775   QgsWkbTypes::GeometryType geometryType = QgsWkbTypes::NullGeometry;
776 
777   QgsVertexId vId;
778   QgsPoint closestPoint;
779   if ( feature.hasGeometry() )
780   {
781     geometryType = feature.geometry().type();
782     wkbType = feature.geometry().wkbType();
783     if ( !layerPoint.isEmpty() )
784     {
785       //find closest vertex to clicked point
786       closestPoint = QgsGeometryUtils::closestVertex( *feature.geometry().constGet(), QgsPoint( layerPoint ), vId );
787     }
788   }
789 
790 
791 
792   if ( QgsWkbTypes::isMultiType( wkbType ) )
793   {
794     QString str = QLocale().toString( static_cast<const QgsGeometryCollection *>( feature.geometry().constGet() )->numGeometries() );
795     derivedAttributes.insert( tr( "Parts" ), str );
796     if ( !layerPoint.isEmpty() )
797     {
798       str = QLocale().toString( vId.part + 1 );
799       derivedAttributes.insert( tr( "Part number" ), str );
800     }
801   }
802 
803   QgsUnitTypes::DistanceUnit cartesianDistanceUnits = QgsUnitTypes::unitType( layer->crs().mapUnits() ) == QgsUnitTypes::unitType( displayDistanceUnits() )
804       ? displayDistanceUnits() : layer->crs().mapUnits();
805   QgsUnitTypes::AreaUnit cartesianAreaUnits = QgsUnitTypes::unitType( QgsUnitTypes::distanceToAreaUnit( layer->crs().mapUnits() ) ) == QgsUnitTypes::unitType( displayAreaUnits() )
806       ? displayAreaUnits() : QgsUnitTypes::distanceToAreaUnit( layer->crs().mapUnits() );
807 
808   if ( geometryType == QgsWkbTypes::LineGeometry )
809   {
810     const QgsAbstractGeometry *geom = feature.geometry().constGet();
811 
812     double dist = calc.measureLength( feature.geometry() );
813     dist = calc.convertLengthMeasurement( dist, displayDistanceUnits() );
814     QString str;
815     if ( ellipsoid != geoNone() )
816     {
817       str = formatDistance( dist );
818       derivedAttributes.insert( tr( "Length (Ellipsoidal — %1)" ).arg( ellipsoid ), str );
819     }
820 
821     str = formatDistance( geom->length()
822                           * QgsUnitTypes::fromUnitToUnitFactor( layer->crs().mapUnits(), cartesianDistanceUnits ), cartesianDistanceUnits );
823     if ( QgsWkbTypes::hasZ( geom->wkbType() )
824          && QgsWkbTypes::flatType( QgsWkbTypes::singleType( geom->wkbType() ) ) == QgsWkbTypes::LineString )
825     {
826       // 3d linestring (or multiline)
827       derivedAttributes.insert( tr( "Length (Cartesian — 2D)" ), str );
828 
829       double totalLength3d = std::accumulate( geom->const_parts_begin(), geom->const_parts_end(), 0.0, []( double total, const QgsAbstractGeometry * part )
830       {
831         return total + qgsgeometry_cast< const QgsLineString * >( part )->length3D();
832       } );
833 
834       str = formatDistance( totalLength3d, cartesianDistanceUnits );
835       derivedAttributes.insert( tr( "Length (Cartesian — 3D)" ), str );
836     }
837     else
838     {
839       derivedAttributes.insert( tr( "Length (Cartesian)" ), str );
840     }
841 
842     str = QLocale().toString( geom->nCoordinates() );
843     derivedAttributes.insert( tr( "Vertices" ), str );
844     if ( !layerPoint.isEmpty() )
845     {
846       //add details of closest vertex to identify point
847       closestVertexAttributes( *geom, vId, layer, derivedAttributes );
848       closestPointAttributes( *geom, layerPoint, derivedAttributes );
849     }
850 
851     if ( const QgsCurve *curve = qgsgeometry_cast< const QgsCurve * >( geom ) )
852     {
853       // Add the start and end points in as derived attributes
854       QgsPointXY pnt = mCanvas->mapSettings().layerToMapCoordinates( layer, QgsPointXY( curve->startPoint().x(), curve->startPoint().y() ) );
855       str = formatXCoordinate( pnt );
856       derivedAttributes.insert( tr( "firstX", "attributes get sorted; translation for lastX should be lexically larger than this one" ), str );
857       str = formatYCoordinate( pnt );
858       derivedAttributes.insert( tr( "firstY" ), str );
859       pnt = mCanvas->mapSettings().layerToMapCoordinates( layer, QgsPointXY( curve->endPoint().x(), curve->endPoint().y() ) );
860       str = formatXCoordinate( pnt );
861       derivedAttributes.insert( tr( "lastX", "attributes get sorted; translation for firstX should be lexically smaller than this one" ), str );
862       str = formatYCoordinate( pnt );
863       derivedAttributes.insert( tr( "lastY" ), str );
864     }
865   }
866   else if ( geometryType == QgsWkbTypes::PolygonGeometry )
867   {
868     double area = calc.measureArea( feature.geometry() );
869     area = calc.convertAreaMeasurement( area, displayAreaUnits() );
870     QString str;
871     if ( ellipsoid != geoNone() )
872     {
873       str = formatArea( area );
874       derivedAttributes.insert( tr( "Area (Ellipsoidal — %1)" ).arg( ellipsoid ), str );
875     }
876     str = formatArea( feature.geometry().area()
877                       * QgsUnitTypes::fromUnitToUnitFactor( QgsUnitTypes::distanceToAreaUnit( layer->crs().mapUnits() ), cartesianAreaUnits ), cartesianAreaUnits );
878     derivedAttributes.insert( tr( "Area (Cartesian)" ), str );
879 
880     if ( ellipsoid != geoNone() )
881     {
882       double perimeter = calc.measurePerimeter( feature.geometry() );
883       perimeter = calc.convertLengthMeasurement( perimeter, displayDistanceUnits() );
884       str = formatDistance( perimeter );
885       derivedAttributes.insert( tr( "Perimeter (Ellipsoidal — %1)" ).arg( ellipsoid ), str );
886     }
887     str = formatDistance( feature.geometry().constGet()->perimeter()
888                           * QgsUnitTypes::fromUnitToUnitFactor( layer->crs().mapUnits(), cartesianDistanceUnits ), cartesianDistanceUnits );
889     derivedAttributes.insert( tr( "Perimeter (Cartesian)" ), str );
890 
891     str = QLocale().toString( feature.geometry().constGet()->nCoordinates() );
892     derivedAttributes.insert( tr( "Vertices" ), str );
893 
894     if ( !layerPoint.isEmpty() )
895     {
896       //add details of closest vertex to identify point
897       closestVertexAttributes( *feature.geometry().constGet(), vId, layer, derivedAttributes );
898       closestPointAttributes( *feature.geometry().constGet(), layerPoint, derivedAttributes );
899     }
900   }
901   else if ( geometryType == QgsWkbTypes::PointGeometry )
902   {
903     if ( QgsWkbTypes::flatType( wkbType ) == QgsWkbTypes::Point )
904     {
905       // Include the x and y coordinates of the point as a derived attribute
906       QgsPointXY pnt = mCanvas->mapSettings().layerToMapCoordinates( layer, feature.geometry().asPoint() );
907       QString str = formatXCoordinate( pnt );
908       derivedAttributes.insert( tr( "X" ), str );
909       str = formatYCoordinate( pnt );
910       derivedAttributes.insert( tr( "Y" ), str );
911 
912       if ( QgsWkbTypes::hasZ( wkbType ) )
913       {
914         str = QLocale().toString( static_cast<const QgsPoint *>( feature.geometry().constGet() )->z(), 'g', 10 );
915         derivedAttributes.insert( tr( "Z" ), str );
916       }
917       if ( QgsWkbTypes::hasM( wkbType ) )
918       {
919         str = QLocale().toString( static_cast<const QgsPoint *>( feature.geometry().constGet() )->m(), 'g', 10 );
920         derivedAttributes.insert( tr( "M" ), str );
921       }
922     }
923     else
924     {
925       //multipart
926 
927       if ( !layerPoint.isEmpty() )
928       {
929         //add details of closest vertex to identify point
930         const QgsAbstractGeometry *geom = feature.geometry().constGet();
931         closestVertexAttributes( *geom, vId, layer, derivedAttributes );
932       }
933     }
934   }
935 
936   if ( feature.embeddedSymbol() )
937   {
938     derivedAttributes.insert( tr( "Embedded Symbol" ), tr( "%1 (%2)" ).arg( QgsSymbol::symbolTypeToString( feature.embeddedSymbol()->type() ), feature.embeddedSymbol()->color().name() ) );
939   }
940 
941   return derivedAttributes;
942 }
943 
identifyRasterLayer(QList<IdentifyResult> * results,QgsRasterLayer * layer,const QgsGeometry & geometry,const QgsRectangle & viewExtent,double mapUnitsPerPixel,const QgsIdentifyContext & identifyContext)944 bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, QgsRasterLayer *layer, const QgsGeometry &geometry, const QgsRectangle &viewExtent, double mapUnitsPerPixel, const QgsIdentifyContext &identifyContext )
945 {
946   QgsPointXY point = geometry.asPoint();  // raster layers currently only support identification by point
947   return identifyRasterLayer( results, layer, point, viewExtent, mapUnitsPerPixel, identifyContext );
948 }
949 
identifyRasterLayer(QList<IdentifyResult> * results,QgsRasterLayer * layer,QgsPointXY point,const QgsRectangle & viewExtent,double mapUnitsPerPixel,const QgsIdentifyContext & identifyContext)950 bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, QgsRasterLayer *layer, QgsPointXY point, const QgsRectangle &viewExtent, double mapUnitsPerPixel, const QgsIdentifyContext &identifyContext )
951 {
952   QgsDebugMsg( "point = " + point.toString() );
953   if ( !layer )
954     return false;
955 
956   std::unique_ptr< QgsRasterDataProvider > dprovider( layer->dataProvider()->clone() );
957   if ( !dprovider )
958     return false;
959 
960   int capabilities = dprovider->capabilities();
961   if ( !( capabilities & QgsRasterDataProvider::Identify ) )
962     return false;
963 
964   if ( identifyContext.isTemporal() )
965   {
966     if ( !layer->temporalProperties()->isVisibleInTemporalRange( identifyContext.temporalRange() ) )
967       return false;
968 
969     dprovider->temporalCapabilities()->setRequestedTemporalRange( identifyContext.temporalRange() );
970   }
971 
972   QgsPointXY pointInCanvasCrs = point;
973   try
974   {
975     point = toLayerCoordinates( layer, point );
976   }
977   catch ( QgsCsException &cse )
978   {
979     Q_UNUSED( cse )
980     QgsDebugMsg( QStringLiteral( "coordinate not reprojectable: %1" ).arg( cse.what() ) );
981     return false;
982   }
983   QgsDebugMsg( QStringLiteral( "point = %1 %2" ).arg( point.x() ).arg( point.y() ) );
984 
985   if ( !layer->extent().contains( point ) )
986     return false;
987 
988   QMap< QString, QString > attributes, derivedAttributes;
989 
990   QgsRaster::IdentifyFormat format = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( QStringLiteral( "identify/format" ) ).toString() );
991 
992   // check if the format is really supported otherwise use first supported format
993   if ( !( QgsRasterDataProvider::identifyFormatToCapability( format ) & capabilities ) )
994   {
995     if ( capabilities & QgsRasterInterface::IdentifyFeature ) format = QgsRaster::IdentifyFormatFeature;
996     else if ( capabilities & QgsRasterInterface::IdentifyValue ) format = QgsRaster::IdentifyFormatValue;
997     else if ( capabilities & QgsRasterInterface::IdentifyHtml ) format = QgsRaster::IdentifyFormatHtml;
998     else if ( capabilities & QgsRasterInterface::IdentifyText ) format = QgsRaster::IdentifyFormatText;
999     else return false;
1000   }
1001 
1002   QgsRasterIdentifyResult identifyResult;
1003   // We can only use current map canvas context (extent, width, height) if layer is not reprojected,
1004   if ( dprovider->crs() != mCanvas->mapSettings().destinationCrs() )
1005   {
1006     // To get some reasonable response for point/line WMS vector layers we must
1007     // use a context with approximately a resolution in layer CRS units
1008     // corresponding to current map canvas resolution (for examplei UMN Mapserver
1009     // in msWMSFeatureInfo() -> msQueryByRect() is using requested pixel
1010     // + TOLERANCE (layer param) for feature selection)
1011     //
1012     QgsRectangle r;
1013     r.setXMinimum( pointInCanvasCrs.x() - mapUnitsPerPixel / 2. );
1014     r.setXMaximum( pointInCanvasCrs.x() + mapUnitsPerPixel / 2. );
1015     r.setYMinimum( pointInCanvasCrs.y() - mapUnitsPerPixel / 2. );
1016     r.setYMaximum( pointInCanvasCrs.y() + mapUnitsPerPixel / 2. );
1017     r = toLayerCoordinates( layer, r ); // will be a bit larger
1018     // Mapserver (6.0.3, for example) does not work with 1x1 pixel box
1019     // but that is fixed (the rect is enlarged) in the WMS provider
1020     identifyResult = dprovider->identify( point, format, r, 1, 1 );
1021   }
1022   else
1023   {
1024     // It would be nice to use the same extent and size which was used for drawing,
1025     // so that WCS can use cache from last draw, unfortunately QgsRasterLayer::draw()
1026     // is doing some tricks with extent and size to align raster to output which
1027     // would be difficult to replicate here.
1028     // Note: cutting the extent may result in slightly different x and y resolutions
1029     // and thus shifted point calculated back in QGIS WMS (using average resolution)
1030     //viewExtent = dprovider->extent().intersect( &viewExtent );
1031 
1032     // Width and height are calculated from not projected extent and we hope that
1033     // are similar to source width and height used to reproject layer for drawing.
1034     // TODO: may be very dangerous, because it may result in different resolutions
1035     // in source CRS, and WMS server (QGIS server) calcs wrong coor using average resolution.
1036     int width = static_cast< int >( std::round( viewExtent.width() / mapUnitsPerPixel ) );
1037     int height = static_cast< int >( std::round( viewExtent.height() / mapUnitsPerPixel ) );
1038 
1039     QgsDebugMsg( QStringLiteral( "viewExtent.width = %1 viewExtent.height = %2" ).arg( viewExtent.width() ).arg( viewExtent.height() ) );
1040     QgsDebugMsg( QStringLiteral( "width = %1 height = %2" ).arg( width ).arg( height ) );
1041     QgsDebugMsg( QStringLiteral( "xRes = %1 yRes = %2 mapUnitsPerPixel = %3" ).arg( viewExtent.width() / width ).arg( viewExtent.height() / height ).arg( mapUnitsPerPixel ) );
1042 
1043     identifyResult = dprovider->identify( point, format, viewExtent, width, height );
1044   }
1045 
1046   derivedAttributes.unite( derivedAttributesForPoint( QgsPoint( pointInCanvasCrs ) ) );
1047 
1048   if ( identifyResult.isValid() )
1049   {
1050     QMap<int, QVariant> values = identifyResult.results();
1051     QgsGeometry geometry;
1052     if ( format == QgsRaster::IdentifyFormatValue )
1053     {
1054       for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
1055       {
1056         QString valueString;
1057         if ( it.value().isNull() )
1058         {
1059           valueString = tr( "no data" );
1060         }
1061         else
1062         {
1063           QVariant value( it.value() );
1064           // The cast is legit. Quoting QT doc :
1065           // "Although this function is declared as returning QVariant::Type,
1066           // the return value should be interpreted as QMetaType::Type"
1067           if ( static_cast<QMetaType::Type>( value.type() ) == QMetaType::Float )
1068           {
1069             valueString = QgsRasterBlock::printValue( value.toFloat() );
1070           }
1071           else
1072           {
1073             valueString = QgsRasterBlock::printValue( value.toDouble() );
1074           }
1075         }
1076         attributes.insert( dprovider->generateBandName( it.key() ), valueString );
1077       }
1078       QString label = layer->name();
1079       results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
1080     }
1081     else if ( format == QgsRaster::IdentifyFormatFeature )
1082     {
1083       for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
1084       {
1085         QVariant value = it.value();
1086         if ( value.type() == QVariant::Bool && !value.toBool() )
1087         {
1088           // sublayer not visible or not queryable
1089           continue;
1090         }
1091 
1092         if ( value.type() == QVariant::String )
1093         {
1094           // error
1095           // TODO: better error reporting
1096           QString label = layer->subLayers().value( it.key() );
1097           attributes.clear();
1098           attributes.insert( tr( "Error" ), value.toString() );
1099 
1100           results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
1101           continue;
1102         }
1103 
1104         // list of feature stores for a single sublayer
1105         const QgsFeatureStoreList featureStoreList = value.value<QgsFeatureStoreList>();
1106 
1107         for ( const QgsFeatureStore &featureStore : featureStoreList )
1108         {
1109           const QgsFeatureList storeFeatures = featureStore.features();
1110           for ( const QgsFeature &feature : storeFeatures )
1111           {
1112             attributes.clear();
1113             // WMS sublayer and feature type, a sublayer may contain multiple feature types.
1114             // Sublayer name may be the same as layer name and feature type name
1115             // may be the same as sublayer. We try to avoid duplicities in label.
1116             QString sublayer = featureStore.params().value( QStringLiteral( "sublayer" ) ).toString();
1117             QString featureType = featureStore.params().value( QStringLiteral( "featureType" ) ).toString();
1118             // Strip UMN MapServer '_feature'
1119             featureType.remove( QStringLiteral( "_feature" ) );
1120             QStringList labels;
1121             if ( sublayer.compare( layer->name(), Qt::CaseInsensitive ) != 0 )
1122             {
1123               labels << sublayer;
1124             }
1125             if ( featureType.compare( sublayer, Qt::CaseInsensitive ) != 0 || labels.isEmpty() )
1126             {
1127               labels << featureType;
1128             }
1129 
1130             QMap< QString, QString > derAttributes = derivedAttributes;
1131             derAttributes.unite( featureDerivedAttributes( feature, layer, toLayerCoordinates( layer, point ) ) );
1132 
1133             IdentifyResult identifyResult( qobject_cast<QgsMapLayer *>( layer ), labels.join( QLatin1String( " / " ) ), featureStore.fields(), feature, derAttributes );
1134 
1135             identifyResult.mParams.insert( QStringLiteral( "getFeatureInfoUrl" ), featureStore.params().value( QStringLiteral( "getFeatureInfoUrl" ) ) );
1136             results->append( identifyResult );
1137           }
1138         }
1139       }
1140     }
1141     else // text or html
1142     {
1143       QgsDebugMsg( QStringLiteral( "%1 HTML or text values" ).arg( values.size() ) );
1144       for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
1145       {
1146         QString value = it.value().toString();
1147         attributes.clear();
1148         attributes.insert( QString(), value );
1149 
1150         QString label = layer->subLayers().value( it.key() );
1151         results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
1152       }
1153     }
1154   }
1155   else
1156   {
1157     attributes.clear();
1158     QString value = identifyResult.error().message( QgsErrorMessage::Text );
1159     attributes.insert( tr( "Error" ), value );
1160     QString label = tr( "Identify error" );
1161     results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
1162   }
1163 
1164   return true;
1165 }
1166 
displayDistanceUnits() const1167 QgsUnitTypes::DistanceUnit QgsMapToolIdentify::displayDistanceUnits() const
1168 {
1169   return mCanvas->mapUnits();
1170 }
1171 
displayAreaUnits() const1172 QgsUnitTypes::AreaUnit QgsMapToolIdentify::displayAreaUnits() const
1173 {
1174   return QgsUnitTypes::distanceToAreaUnit( mCanvas->mapUnits() );
1175 }
1176 
formatDistance(double distance) const1177 QString QgsMapToolIdentify::formatDistance( double distance ) const
1178 {
1179   return formatDistance( distance, displayDistanceUnits() );
1180 }
1181 
formatArea(double area) const1182 QString QgsMapToolIdentify::formatArea( double area ) const
1183 {
1184   return formatArea( area, displayAreaUnits() );
1185 }
1186 
formatDistance(double distance,QgsUnitTypes::DistanceUnit unit) const1187 QString QgsMapToolIdentify::formatDistance( double distance, QgsUnitTypes::DistanceUnit unit ) const
1188 {
1189   QgsSettings settings;
1190   bool baseUnit = settings.value( QStringLiteral( "qgis/measure/keepbaseunit" ), true ).toBool();
1191 
1192   return QgsDistanceArea::formatDistance( distance, 3, unit, baseUnit );
1193 }
1194 
formatArea(double area,QgsUnitTypes::AreaUnit unit) const1195 QString QgsMapToolIdentify::formatArea( double area, QgsUnitTypes::AreaUnit unit ) const
1196 {
1197   QgsSettings settings;
1198   bool baseUnit = settings.value( QStringLiteral( "qgis/measure/keepbaseunit" ), true ).toBool();
1199 
1200   return QgsDistanceArea::formatArea( area, 3, unit, baseUnit );
1201 }
1202 
formatChanged(QgsRasterLayer * layer)1203 void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
1204 {
1205   QList<IdentifyResult> results;
1206   if ( identifyRasterLayer( &results, layer, mLastGeometry, mLastExtent, mLastMapUnitsPerPixel ) )
1207   {
1208     emit changedRasterResults( results );
1209   }
1210 }
1211 
fromPointCloudIdentificationToIdentifyResults(QgsPointCloudLayer * layer,const QVector<QVariantMap> & identified,QList<QgsMapToolIdentify::IdentifyResult> & results)1212 void QgsMapToolIdentify::fromPointCloudIdentificationToIdentifyResults( QgsPointCloudLayer *layer, const QVector<QVariantMap> &identified, QList<QgsMapToolIdentify::IdentifyResult> &results )
1213 {
1214   int id = 1;
1215   const QgsPointCloudLayerElevationProperties *elevationProps = qobject_cast< const QgsPointCloudLayerElevationProperties *>( layer->elevationProperties() );
1216   for ( const QVariantMap &pt : identified )
1217   {
1218     QMap<QString, QString> ptStr;
1219     QString classification;
1220     for ( auto attrIt = pt.constBegin(); attrIt != pt.constEnd(); ++attrIt )
1221     {
1222       if ( attrIt.key().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0
1223            && ( !qgsDoubleNear( elevationProps->zScale(), 1 ) || !qgsDoubleNear( elevationProps->zOffset(), 0 ) ) )
1224       {
1225         // Apply elevation properties
1226         ptStr[ tr( "Z (original)" ) ] = attrIt.value().toString();
1227         ptStr[ tr( "Z (adjusted)" ) ] = QString::number( attrIt.value().toDouble() * elevationProps->zScale() + elevationProps->zOffset() );
1228       }
1229       else if ( attrIt.key().compare( QLatin1String( "Classification" ), Qt::CaseInsensitive ) == 0 )
1230       {
1231         classification = QgsPointCloudDataProvider::translatedLasClassificationCodes().value( attrIt.value().toInt() );
1232         ptStr[ attrIt.key() ] = QStringLiteral( "%1 (%2)" ).arg( attrIt.value().toString(), classification );
1233       }
1234       else
1235       {
1236         ptStr[attrIt.key()] = attrIt.value().toString();
1237       }
1238     }
1239     QgsMapToolIdentify::IdentifyResult res( layer, classification.isEmpty() ? QString::number( id ) : QStringLiteral( "%1 (%2)" ).arg( id ).arg( classification ), ptStr, QMap<QString, QString>() );
1240     results.append( res );
1241     ++id;
1242   }
1243 }
1244