QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgsmaptoolidentify.cpp
Go to the documentation of this file.
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 "qgscursors.h"
18 #include "qgsdistancearea.h"
19 #include "qgsfeature.h"
20 #include "qgsfeaturestore.h"
21 #include "qgsfield.h"
22 #include "qgsgeometry.h"
23 #include "qgsidentifymenu.h"
24 #include "qgslogger.h"
25 #include "qgsmapcanvas.h"
26 #include "qgsmaptoolidentify.h"
27 #include "qgsmaptopixel.h"
28 #include "qgsmessageviewer.h"
29 #include "qgsmaplayer.h"
30 #include "qgsrasterlayer.h"
33 #include "qgsvectordataprovider.h"
34 #include "qgsvectorlayer.h"
35 #include "qgsproject.h"
36 #include "qgsmaplayerregistry.h"
37 #include "qgsrendererv2.h"
38 
39 #include <QSettings>
40 #include <QMouseEvent>
41 #include <QCursor>
42 #include <QPixmap>
43 #include <QStatusBar>
44 #include <QVariant>
45 #include <QMenu>
46 
48  : QgsMapTool( canvas )
49  , mIdentifyMenu( new QgsIdentifyMenu( mCanvas ) )
50  , mLastMapUnitsPerPixel( -1.0 )
51 {
52  // set cursor
53  QPixmap myIdentifyQPixmap = QPixmap(( const char ** ) identify_cursor );
54  mCursor = QCursor( myIdentifyQPixmap, 1, 1 );
55 }
56 
58 {
59  delete mIdentifyMenu;
60 }
61 
62 void QgsMapToolIdentify::canvasMoveEvent( QMouseEvent * e )
63 {
64  Q_UNUSED( e );
65 }
66 
67 void QgsMapToolIdentify::canvasPressEvent( QMouseEvent * e )
68 {
69  Q_UNUSED( e );
70 }
71 
73 {
74  Q_UNUSED( e );
75 }
76 
77 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, QList<QgsMapLayer *> layerList, IdentifyMode mode )
78 {
79  return identify( x, y, mode, layerList, AllLayers );
80 }
81 
82 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, LayerType layerType )
83 {
84  return identify( x, y, mode, QList<QgsMapLayer*>(), layerType );
85 }
86 
87 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, QList<QgsMapLayer*> layerList, LayerType layerType )
88 {
89  QList<IdentifyResult> results;
90 
91  mLastPoint = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
92  mLastExtent = mCanvas->extent();
93  mLastMapUnitsPerPixel = mCanvas->mapUnitsPerPixel();
94 
95  if ( mode == DefaultQgsSetting )
96  {
97  QSettings settings;
98  mode = static_cast<IdentifyMode>( settings.value( "/Map/identifyMode", 0 ).toInt() );
99  }
100 
101  if ( mode == LayerSelection )
102  {
103  QList<IdentifyResult> results = identify( x, y, TopDownAll, layerList, layerType );
104  QPoint globalPos = mCanvas->mapToGlobal( QPoint( x + 5, y + 5 ) );
105  return mIdentifyMenu->exec( results, globalPos );
106  }
107  else if ( mode == ActiveLayer && layerList.isEmpty() )
108  {
109  QgsMapLayer *layer = mCanvas->currentLayer();
110 
111  if ( !layer )
112  {
113  emit identifyMessage( tr( "No active layer. To identify features, you must choose an active layer." ) );
114  return results;
115  }
116 
117  QApplication::setOverrideCursor( Qt::WaitCursor );
118 
119  identifyLayer( &results, layer, mLastPoint, mLastExtent, mLastMapUnitsPerPixel, layerType );
120  }
121  else
122  {
123  QApplication::setOverrideCursor( Qt::WaitCursor );
124 
125  QStringList noIdentifyLayerIdList = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
126 
127  int layerCount;
128  if ( layerList.isEmpty() )
129  layerCount = mCanvas->layerCount();
130  else
131  layerCount = layerList.count();
132 
133 
134  for ( int i = 0; i < layerCount; i++ )
135  {
136 
137  QgsMapLayer *layer;
138  if ( layerList.isEmpty() )
139  layer = mCanvas->layer( i );
140  else
141  layer = layerList.value( i );
142 
143  emit identifyProgress( i, mCanvas->layerCount() );
144  emit identifyMessage( tr( "Identifying on %1..." ).arg( layer->name() ) );
145 
146  if ( noIdentifyLayerIdList.contains( layer->id() ) )
147  continue;
148 
149  if ( identifyLayer( &results, layer, mLastPoint, mLastExtent, mLastMapUnitsPerPixel, layerType ) )
150  {
151  if ( mode == TopDownStopAtFirst )
152  break;
153  }
154  }
155 
157  emit identifyMessage( tr( "Identifying done." ) );
158  }
159 
160  QApplication::restoreOverrideCursor();
161 
162  return results;
163 }
164 
166 {
168 }
169 
171 {
173 }
174 
175 bool QgsMapToolIdentify::identifyLayer( QList<IdentifyResult> *results, QgsMapLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, LayerType layerType )
176 {
177  if ( layer->type() == QgsMapLayer::RasterLayer && layerType.testFlag( RasterLayer ) )
178  {
179  return identifyRasterLayer( results, qobject_cast<QgsRasterLayer *>( layer ), point, viewExtent, mapUnitsPerPixel );
180  }
181  else if ( layer->type() == QgsMapLayer::VectorLayer && layerType.testFlag( VectorLayer ) )
182  {
183  return identifyVectorLayer( results, qobject_cast<QgsVectorLayer *>( layer ), point );
184  }
185  else
186  {
187  return false;
188  }
189 }
190 
191 bool QgsMapToolIdentify::identifyVectorLayer( QList<IdentifyResult> *results, QgsVectorLayer *layer, QgsPoint point )
192 {
193  if ( !layer || !layer->hasGeometryType() )
194  return false;
195 
196  if ( layer->hasScaleBasedVisibility() &&
197  ( layer->minimumScale() > mCanvas->mapSettings().scale() ||
198  layer->maximumScale() <= mCanvas->mapSettings().scale() ) )
199  {
200  QgsDebugMsg( "Out of scale limits" );
201  return false;
202  }
203 
204  QApplication::setOverrideCursor( Qt::WaitCursor );
205 
206  QMap< QString, QString > commonDerivedAttributes;
207 
208  commonDerivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
209 
210  int featureCount = 0;
211 
212  QgsFeatureList featureList;
213 
214  // toLayerCoordinates will throw an exception for an 'invalid' point.
215  // For example, if you project a world map onto a globe using EPSG 2163
216  // and then click somewhere off the globe, an exception will be thrown.
217  try
218  {
219  // create the search rectangle
220  double searchRadius = searchRadiusMU( mCanvas );
221 
222  QgsRectangle r;
223  r.setXMinimum( point.x() - searchRadius );
224  r.setXMaximum( point.x() + searchRadius );
225  r.setYMinimum( point.y() - searchRadius );
226  r.setYMaximum( point.y() + searchRadius );
227 
228  r = toLayerCoordinates( layer, r );
229 
230  QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( r ).setFlags( QgsFeatureRequest::ExactIntersect ) );
231  QgsFeature f;
232  while ( fit.nextFeature( f ) )
233  featureList << QgsFeature( f );
234  }
235  catch ( QgsCsException & cse )
236  {
237  Q_UNUSED( cse );
238  // catch exception for 'invalid' point and proceed with no features found
239  QgsDebugMsg( QString( "Caught CRS exception %1" ).arg( cse.what() ) );
240  }
241 
242  QgsFeatureList::iterator f_it = featureList.begin();
243 
244  bool filter = false;
245 
247  QgsFeatureRendererV2* renderer = layer->rendererV2();
248  if ( renderer && renderer->capabilities() & QgsFeatureRendererV2::ScaleDependent )
249  {
250  // setup scale for scale dependent visibility (rule based)
251  renderer->startRender( context, layer->pendingFields() );
252  filter = renderer->capabilities() & QgsFeatureRendererV2::Filter;
253  }
254 
255  for ( ; f_it != featureList.end(); ++f_it )
256  {
257  QMap< QString, QString > derivedAttributes = commonDerivedAttributes;
258 
259  QgsFeatureId fid = f_it->id();
260 
261  if ( filter && !renderer->willRenderFeature( *f_it ) )
262  continue;
263 
264  featureCount++;
265 
266  derivedAttributes.unite( featureDerivedAttributes( &( *f_it ), layer ) );
267 
268  derivedAttributes.insert( tr( "feature id" ), fid < 0 ? tr( "new feature" ) : FID_TO_STRING( fid ) );
269 
270  results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), *f_it, derivedAttributes ) );
271  }
272 
273  if ( renderer && renderer->capabilities() & QgsFeatureRendererV2::ScaleDependent )
274  {
275  renderer->stopRender( context );
276  }
277 
278  QgsDebugMsg( "Feature count on identify: " + QString::number( featureCount ) );
279 
280  QApplication::restoreOverrideCursor();
281  return featureCount > 0;
282 }
283 
284 QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer )
285 {
286  // Calculate derived attributes and insert:
287  // measure distance or area depending on geometry type
288  QMap< QString, QString > derivedAttributes;
289 
290  // init distance/area calculator
291  QString ellipsoid = QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE );
292  QgsDistanceArea calc;
294  calc.setEllipsoid( ellipsoid );
295  calc.setSourceCrs( layer->crs().srsid() );
296 
298  QGis::GeometryType geometryType = QGis::NoGeometry;
299 
300  if ( feature->constGeometry() )
301  {
302  geometryType = feature->constGeometry()->type();
303  wkbType = feature->constGeometry()->wkbType();
304  }
305 
306  if ( geometryType == QGis::Line )
307  {
308  double dist = calc.measure( feature->constGeometry() );
309  QGis::UnitType myDisplayUnits;
310  convertMeasurement( calc, dist, myDisplayUnits, false );
311  QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
312  derivedAttributes.insert( tr( "Length" ), str );
313  if ( wkbType == QGis::WKBLineString || wkbType == QGis::WKBLineString25D )
314  {
315  // Add the start and end points in as derived attributes
316  QgsPoint pnt = mCanvas->mapSettings().layerToMapCoordinates( layer, feature->constGeometry()->asPolyline().first() );
317  str = QLocale::system().toString( pnt.x(), 'g', 10 );
318  derivedAttributes.insert( tr( "firstX", "attributes get sorted; translation for lastX should be lexically larger than this one" ), str );
319  str = QLocale::system().toString( pnt.y(), 'g', 10 );
320  derivedAttributes.insert( tr( "firstY" ), str );
321  pnt = mCanvas->mapSettings().layerToMapCoordinates( layer, feature->constGeometry()->asPolyline().last() );
322  str = QLocale::system().toString( pnt.x(), 'g', 10 );
323  derivedAttributes.insert( tr( "lastX", "attributes get sorted; translation for firstX should be lexically smaller than this one" ), str );
324  str = QLocale::system().toString( pnt.y(), 'g', 10 );
325  derivedAttributes.insert( tr( "lastY" ), str );
326  }
327  }
328  else if ( geometryType == QGis::Polygon )
329  {
330  double area = calc.measure( feature->constGeometry() );
331  double perimeter = calc.measurePerimeter( feature->constGeometry() );
332  QGis::UnitType myDisplayUnits;
333  convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
334  QString str = calc.textUnit( area, 3, myDisplayUnits, true );
335  derivedAttributes.insert( tr( "Area" ), str );
336  convertMeasurement( calc, perimeter, myDisplayUnits, false ); // perimeter and myDisplayUnits are out params
337  str = calc.textUnit( perimeter, 3, myDisplayUnits, false );
338  derivedAttributes.insert( tr( "Perimeter" ), str );
339  }
340  else if ( geometryType == QGis::Point &&
341  ( wkbType == QGis::WKBPoint || wkbType == QGis::WKBPoint25D ) )
342  {
343  // Include the x and y coordinates of the point as a derived attribute
344  QgsPoint pnt = mCanvas->mapSettings().layerToMapCoordinates( layer, feature->constGeometry()->asPoint() );
345  QString str = QLocale::system().toString( pnt.x(), 'g', 10 );
346  derivedAttributes.insert( "X", str );
347  str = QLocale::system().toString( pnt.y(), 'g', 10 );
348  derivedAttributes.insert( "Y", str );
349  }
350 
351  return derivedAttributes;
352 }
353 
354 bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, QgsRasterLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel )
355 {
356  QgsDebugMsg( "point = " + point.toString() );
357  if ( !layer )
358  return false;
359 
360  QgsRasterDataProvider *dprovider = layer->dataProvider();
361  if ( !dprovider )
362  return false;
363 
364  int capabilities = dprovider->capabilities();
365  if ( !( capabilities & QgsRasterDataProvider::Identify ) )
366  return false;
367 
368  QgsPoint pointInCanvasCrs = point;
369  try
370  {
371  point = toLayerCoordinates( layer, point );
372  }
373  catch ( QgsCsException &cse )
374  {
375  Q_UNUSED( cse );
376  QgsDebugMsg( QString( "coordinate not reprojectable: %1" ).arg( cse.what() ) );
377  return false;
378  }
379  QgsDebugMsg( QString( "point = %1 %2" ).arg( point.x() ).arg( point.y() ) );
380 
381  if ( !layer->extent().contains( point ) )
382  return false;
383 
384  QMap< QString, QString > attributes, derivedAttributes;
385 
386  QgsRaster::IdentifyFormat format = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( "identify/format" ).toString() );
387 
388  // check if the format is really supported otherwise use first supported format
389  if ( !( QgsRasterDataProvider::identifyFormatToCapability( format ) & capabilities ) )
390  {
392  else if ( capabilities & QgsRasterInterface::IdentifyValue ) format = QgsRaster::IdentifyFormatValue;
393  else if ( capabilities & QgsRasterInterface::IdentifyHtml ) format = QgsRaster::IdentifyFormatHtml;
394  else if ( capabilities & QgsRasterInterface::IdentifyText ) format = QgsRaster::IdentifyFormatText;
395  else return false;
396  }
397 
398  QgsRasterIdentifyResult identifyResult;
399  // We can only use current map canvas context (extent, width, height) if layer is not reprojected,
400  if ( mCanvas->hasCrsTransformEnabled() && dprovider->crs() != mCanvas->mapSettings().destinationCrs() )
401  {
402  // To get some reasonable response for point/line WMS vector layers we must
403  // use a context with approximately a resolution in layer CRS units
404  // corresponding to current map canvas resolution (for examplei UMN Mapserver
405  // in msWMSFeatureInfo() -> msQueryByRect() is using requested pixel
406  // + TOLERANCE (layer param) for feature selection)
407  //
408  QgsRectangle r;
409  r.setXMinimum( pointInCanvasCrs.x() - mapUnitsPerPixel / 2. );
410  r.setXMaximum( pointInCanvasCrs.x() + mapUnitsPerPixel / 2. );
411  r.setYMinimum( pointInCanvasCrs.y() - mapUnitsPerPixel / 2. );
412  r.setYMaximum( pointInCanvasCrs.y() + mapUnitsPerPixel / 2. );
413  r = toLayerCoordinates( layer, r ); // will be a bit larger
414  // Mapserver (6.0.3, for example) does not work with 1x1 pixel box
415  // but that is fixed (the rect is enlarged) in the WMS provider
416  identifyResult = dprovider->identify( point, format, r, 1, 1 );
417  }
418  else
419  {
420  // It would be nice to use the same extent and size which was used for drawing,
421  // so that WCS can use cache from last draw, unfortunately QgsRasterLayer::draw()
422  // is doing some tricks with extent and size to allign raster to output which
423  // would be difficult to replicate here.
424  // Note: cutting the extent may result in slightly different x and y resolutions
425  // and thus shifted point calculated back in QGIS WMS (using average resolution)
426  //viewExtent = dprovider->extent().intersect( &viewExtent );
427 
428  // Width and height are calculated from not projected extent and we hope that
429  // are similar to source width and height used to reproject layer for drawing.
430  // TODO: may be very dangerous, because it may result in different resolutions
431  // in source CRS, and WMS server (QGIS server) calcs wrong coor using average resolution.
432  int width = qRound( viewExtent.width() / mapUnitsPerPixel );
433  int height = qRound( viewExtent.height() / mapUnitsPerPixel );
434 
435  QgsDebugMsg( QString( "viewExtent.width = %1 viewExtent.height = %2" ).arg( viewExtent.width() ).arg( viewExtent.height() ) );
436  QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) );
437  QgsDebugMsg( QString( "xRes = %1 yRes = %2 mapUnitsPerPixel = %3" ).arg( viewExtent.width() / width ).arg( viewExtent.height() / height ).arg( mapUnitsPerPixel ) );
438 
439  identifyResult = dprovider->identify( point, format, viewExtent, width, height );
440  }
441 
442  derivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
443 
444  if ( identifyResult.isValid() )
445  {
446  QMap<int, QVariant> values = identifyResult.results();
447  QgsGeometry geometry;
448  if ( format == QgsRaster::IdentifyFormatValue )
449  {
450  foreach ( int bandNo, values.keys() )
451  {
452  QString valueString;
453  if ( values.value( bandNo ).isNull() )
454  {
455  valueString = tr( "no data" );
456  }
457  else
458  {
459  double value = values.value( bandNo ).toDouble();
460  valueString = QgsRasterBlock::printValue( value );
461  }
462  attributes.insert( dprovider->generateBandName( bandNo ), valueString );
463  }
464  QString label = layer->name();
465  results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
466  }
467  else if ( format == QgsRaster::IdentifyFormatFeature )
468  {
469  foreach ( int i, values.keys() )
470  {
471  QVariant value = values.value( i );
472  if ( value.type() == QVariant::Bool && !value.toBool() )
473  {
474  // sublayer not visible or not queryable
475  continue;
476  }
477 
478  if ( value.type() == QVariant::String )
479  {
480  // error
481  // TODO: better error reporting
482  QString label = layer->subLayers().value( i );
483  attributes.clear();
484  attributes.insert( tr( "Error" ), value.toString() );
485 
486  results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
487  continue;
488  }
489 
490  // list of feature stores for a single sublayer
491  QgsFeatureStoreList featureStoreList = values.value( i ).value<QgsFeatureStoreList>();
492 
493  foreach ( QgsFeatureStore featureStore, featureStoreList )
494  {
495  foreach ( QgsFeature feature, featureStore.features() )
496  {
497  attributes.clear();
498  // WMS sublayer and feature type, a sublayer may contain multiple feature types.
499  // Sublayer name may be the same as layer name and feature type name
500  // may be the same as sublayer. We try to avoid duplicities in label.
501  QString sublayer = featureStore.params().value( "sublayer" ).toString();
502  QString featureType = featureStore.params().value( "featureType" ).toString();
503  // Strip UMN MapServer '_feature'
504  featureType.remove( "_feature" );
505  QStringList labels;
506  if ( sublayer.compare( layer->name(), Qt::CaseInsensitive ) != 0 )
507  {
508  labels << sublayer;
509  }
510  if ( featureType.compare( sublayer, Qt::CaseInsensitive ) != 0 || labels.isEmpty() )
511  {
512  labels << featureType;
513  }
514 
515  QMap< QString, QString > derAttributes = derivedAttributes;
516  derAttributes.unite( featureDerivedAttributes( &feature, layer ) );
517 
518  IdentifyResult identifyResult( qobject_cast<QgsMapLayer *>( layer ), labels.join( " / " ), featureStore.fields(), feature, derAttributes );
519 
520  identifyResult.mParams.insert( "getFeatureInfoUrl", featureStore.params().value( "getFeatureInfoUrl" ) );
521  results->append( identifyResult );
522  }
523  }
524  }
525  }
526  else // text or html
527  {
528  QgsDebugMsg( QString( "%1 html or text values" ).arg( values.size() ) );
529  foreach ( int bandNo, values.keys() )
530  {
531  QString value = values.value( bandNo ).toString();
532  attributes.clear();
533  attributes.insert( "", value );
534 
535  QString label = layer->subLayers().value( bandNo );
536  results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
537  }
538  }
539  }
540  else
541  {
542  attributes.clear();
543  QString value = identifyResult.error().message( QgsErrorMessage::Text );
544  attributes.insert( tr( "Error" ), value );
545  QString label = tr( "Identify error" );
546  results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
547  }
548 
549  return true;
550 }
551 
552 void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea )
553 {
554  // Helper for converting between meters and feet
555  // The parameter &u is out only...
556 
557  // Get the canvas units
558  QGis::UnitType myUnits = mCanvas->mapUnits();
559 
560  calc.convertMeasurement( measure, myUnits, displayUnits(), isArea );
561  u = myUnits;
562 }
563 
564 QGis::UnitType QgsMapToolIdentify::displayUnits()
565 {
566  return mCanvas->mapUnits();
567 }
568 
570 {
571  QgsDebugMsg( "Entered" );
572  QList<IdentifyResult> results;
573  if ( identifyRasterLayer( &results, layer, mLastPoint, mLastExtent, mLastMapUnitsPerPixel ) )
574  {
575  emit changedRasterResults( results );
576  }
577 }
578