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