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