QGIS API Documentation  master-3f58142
src/gui/qgsmaptoolidentify.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002     qgsmaptoolidentify.cpp  -  map tool for identifying features
00003     ---------------------
00004     begin                : January 2006
00005     copyright            : (C) 2006 by Martin Dobias
00006     email                : wonder.sk at gmail dot com
00007  ***************************************************************************
00008  *                                                                         *
00009  *   This program is free software; you can redistribute it and/or modify  *
00010  *   it under the terms of the GNU General Public License as published by  *
00011  *   the Free Software Foundation; either version 2 of the License, or     *
00012  *   (at your option) any later version.                                   *
00013  *                                                                         *
00014  ***************************************************************************/
00015 
00016 #include "qgscursors.h"
00017 #include "qgsdistancearea.h"
00018 #include "qgsfeature.h"
00019 #include "qgsfeaturestore.h"
00020 #include "qgsfield.h"
00021 #include "qgsgeometry.h"
00022 #include "qgslogger.h"
00023 #include "qgsmapcanvas.h"
00024 #include "qgsmaptoolidentify.h"
00025 #include "qgsmaptopixel.h"
00026 #include "qgsmessageviewer.h"
00027 #include "qgsmaplayer.h"
00028 #include "qgsrasterlayer.h"
00029 #include "qgsrasteridentifyresult.h"
00030 #include "qgscoordinatereferencesystem.h"
00031 #include "qgsvectordataprovider.h"
00032 #include "qgsvectorlayer.h"
00033 #include "qgsproject.h"
00034 #include "qgsmaplayerregistry.h"
00035 #include "qgsrendererv2.h"
00036 
00037 #include <QSettings>
00038 #include <QMessageBox>
00039 #include <QMouseEvent>
00040 #include <QCursor>
00041 #include <QPixmap>
00042 #include <QStatusBar>
00043 #include <QVariant>
00044 
00045 QgsMapToolIdentify::QgsMapToolIdentify( QgsMapCanvas* canvas )
00046     : QgsMapTool( canvas )
00047 {
00048   // set cursor
00049   QPixmap myIdentifyQPixmap = QPixmap(( const char ** ) identify_cursor );
00050   mCursor = QCursor( myIdentifyQPixmap, 1, 1 );
00051 }
00052 
00053 QgsMapToolIdentify::~QgsMapToolIdentify()
00054 {
00055 }
00056 
00057 void QgsMapToolIdentify::canvasMoveEvent( QMouseEvent * e )
00058 {
00059   Q_UNUSED( e );
00060 }
00061 
00062 void QgsMapToolIdentify::canvasPressEvent( QMouseEvent * e )
00063 {
00064   Q_UNUSED( e );
00065 }
00066 
00067 void QgsMapToolIdentify::canvasReleaseEvent( QMouseEvent * e )
00068 {
00069   Q_UNUSED( e );
00070 }
00071 
00072 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, QList<QgsMapLayer *> layerList, IdentifyMode mode )
00073 {
00074   return identify( x, y, mode, layerList, AllLayers );
00075 }
00076 
00077 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, LayerType layerType )
00078 {
00079   return identify( x, y, mode, QList<QgsMapLayer*>(), layerType );
00080 }
00081 
00082 QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( int x, int y, IdentifyMode mode, QList<QgsMapLayer*> layerList, LayerType layerType )
00083 {
00084   QList<IdentifyResult> results;
00085 
00086   mLastPoint = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
00087   mLastExtent = mCanvas->extent();
00088   mLastMapUnitsPerPixel = mCanvas->mapUnitsPerPixel();
00089 
00090   if ( !mCanvas || mCanvas->isDrawing() )
00091   {
00092     return results;
00093   }
00094 
00095   if ( mode == DefaultQgsSetting )
00096   {
00097     QSettings settings;
00098     mode = static_cast<IdentifyMode>( settings.value( "/Map/identifyMode", 0 ).toInt() );
00099   }
00100 
00101   if ( mode == ActiveLayer && layerList.isEmpty() )
00102   {
00103     QgsMapLayer *layer = mCanvas->currentLayer();
00104 
00105     if ( !layer )
00106     {
00107       emit identifyMessage( tr( "No active layer. To identify features, you must choose an active layer." ) );
00108       return results;
00109     }
00110 
00111     QApplication::setOverrideCursor( Qt::WaitCursor );
00112 
00113     identifyLayer( &results, layer, mLastPoint, mLastExtent, mLastMapUnitsPerPixel, layerType );
00114   }
00115   else
00116   {
00117     QApplication::setOverrideCursor( Qt::WaitCursor );
00118 
00119     QStringList noIdentifyLayerIdList = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
00120 
00121     int layerCount;
00122     if ( layerList.isEmpty() )
00123       layerCount = mCanvas->layerCount();
00124     else
00125       layerCount = layerList.count();
00126 
00127 
00128     for ( int i = 0; i < layerCount; i++ )
00129     {
00130 
00131       QgsMapLayer *layer ;
00132       if ( layerList.isEmpty() )
00133         layer = mCanvas->layer( i );
00134       else
00135         layer = layerList.value( i );
00136 
00137       emit identifyProgress( i, mCanvas->layerCount() );
00138       emit identifyMessage( tr( "Identifying on %1..." ).arg( layer->name() ) );
00139 
00140       if ( noIdentifyLayerIdList.contains( layer->id() ) )
00141         continue;
00142 
00143       if ( identifyLayer( &results, layer,  mLastPoint, mLastExtent, mLastMapUnitsPerPixel, layerType ) )
00144       {
00145         if ( mode == TopDownStopAtFirst )
00146           break;
00147       }
00148     }
00149 
00150     emit identifyProgress( mCanvas->layerCount(), mCanvas->layerCount() );
00151     emit identifyMessage( tr( "Identifying done." ) );
00152   }
00153 
00154   QApplication::restoreOverrideCursor();
00155 
00156   return results;
00157 }
00158 
00159 void QgsMapToolIdentify::activate()
00160 {
00161   QgsMapTool::activate();
00162 }
00163 
00164 void QgsMapToolIdentify::deactivate()
00165 {
00166   QgsMapTool::deactivate();
00167 }
00168 
00169 bool QgsMapToolIdentify::identifyLayer( QList<IdentifyResult> *results, QgsMapLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel, LayerType layerType )
00170 {
00171   if ( layer->type() == QgsMapLayer::RasterLayer && ( layerType == AllLayers || layerType == RasterLayer ) )
00172   {
00173     return identifyRasterLayer( results, qobject_cast<QgsRasterLayer *>( layer ), point, viewExtent, mapUnitsPerPixel );
00174   }
00175   else if ( layer->type() == QgsMapLayer::VectorLayer && ( layerType == AllLayers || layerType == VectorLayer ) )
00176   {
00177     return identifyVectorLayer( results, qobject_cast<QgsVectorLayer *>( layer ), point );
00178   }
00179   else
00180   {
00181     return false;
00182   }
00183 }
00184 
00185 bool QgsMapToolIdentify::identifyVectorLayer( QList<IdentifyResult> *results, QgsVectorLayer *layer, QgsPoint point )
00186 {
00187   if ( !layer )
00188     return false;
00189 
00190   if ( layer->hasScaleBasedVisibility() &&
00191        ( layer->minimumScale() > mCanvas->mapRenderer()->scale() ||
00192          layer->maximumScale() <= mCanvas->mapRenderer()->scale() ) )
00193   {
00194     QgsDebugMsg( "Out of scale limits" );
00195     return false;
00196   }
00197 
00198   QMap< QString, QString > derivedAttributes;
00199 
00200   derivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
00201 
00202   // load identify radius from settings
00203   QSettings settings;
00204   double identifyValue = settings.value( "/Map/identifyRadius", QGis::DEFAULT_IDENTIFY_RADIUS ).toDouble();
00205 
00206   if ( identifyValue <= 0.0 )
00207     identifyValue = QGis::DEFAULT_IDENTIFY_RADIUS;
00208 
00209   int featureCount = 0;
00210 
00211   QgsFeatureList featureList;
00212 
00213   // toLayerCoordinates will throw an exception for an 'invalid' point.
00214   // For example, if you project a world map onto a globe using EPSG 2163
00215   // and then click somewhere off the globe, an exception will be thrown.
00216   try
00217   {
00218     // create the search rectangle
00219     double searchRadius = mCanvas->extent().width() * ( identifyValue / 100.0 );
00220 
00221     QgsRectangle r;
00222     r.setXMinimum( point.x() - searchRadius );
00223     r.setXMaximum( point.x() + searchRadius );
00224     r.setYMinimum( point.y() - searchRadius );
00225     r.setYMaximum( point.y() + searchRadius );
00226 
00227     r = toLayerCoordinates( layer, r );
00228 
00229     QgsFeatureIterator fit = layer->getFeatures( QgsFeatureRequest().setFilterRect( r ).setFlags( QgsFeatureRequest::ExactIntersect ) );
00230     QgsFeature f;
00231     while ( fit.nextFeature( f ) )
00232       featureList << QgsFeature( f );
00233   }
00234   catch ( QgsCsException & cse )
00235   {
00236     Q_UNUSED( cse );
00237     // catch exception for 'invalid' point and proceed with no features found
00238     QgsDebugMsg( QString( "Caught CRS exception %1" ).arg( cse.what() ) );
00239   }
00240 
00241   QgsFeatureList::iterator f_it = featureList.begin();
00242 
00243   bool filter = false;
00244 
00245   QgsFeatureRendererV2* renderer = layer->rendererV2();
00246   if ( renderer && renderer->capabilities() & QgsFeatureRendererV2::ScaleDependent )
00247   {
00248     // setup scale for scale dependent visibility (rule based)
00249     renderer->startRender( *( mCanvas->mapRenderer()->rendererContext() ), layer );
00250     filter = renderer->capabilities() & QgsFeatureRendererV2::Filter;
00251   }
00252 
00253   for ( ; f_it != featureList.end(); ++f_it )
00254   {
00255     QgsFeatureId fid = f_it->id();
00256 
00257     if ( filter && !renderer->willRenderFeature( *f_it ) )
00258       continue;
00259 
00260     featureCount++;
00261 
00262     derivedAttributes.unite( featureDerivedAttributes( &( *f_it ), layer ) );
00263 
00264     derivedAttributes.insert( tr( "feature id" ), fid < 0 ? tr( "new feature" ) : FID_TO_STRING( fid ) );
00265 
00266     results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), *f_it, derivedAttributes ) );
00267   }
00268 
00269   if ( renderer && renderer->capabilities() & QgsFeatureRendererV2::ScaleDependent )
00270   {
00271     renderer->stopRender( *( mCanvas->mapRenderer()->rendererContext() ) );
00272   }
00273 
00274   QgsDebugMsg( "Feature count on identify: " + QString::number( featureCount ) );
00275 
00276   return featureCount > 0;
00277 }
00278 
00279 QMap< QString, QString > QgsMapToolIdentify::featureDerivedAttributes( QgsFeature *feature, QgsMapLayer *layer )
00280 {
00281   // Calculate derived attributes and insert:
00282   // measure distance or area depending on geometry type
00283   QMap< QString, QString > derivedAttributes;
00284 
00285   // init distance/area calculator
00286   QString ellipsoid = QgsProject::instance()->readEntry( "Measure", "/Ellipsoid", GEO_NONE );
00287   QgsDistanceArea calc;
00288   calc.setEllipsoidalMode( mCanvas->hasCrsTransformEnabled() );
00289   calc.setEllipsoid( ellipsoid );
00290   calc.setSourceCrs( layer->crs().srsid() );
00291 
00292   QGis::WkbType wkbType = QGis::WKBNoGeometry;
00293   QGis::GeometryType geometryType = QGis::NoGeometry;
00294 
00295   if ( feature->geometry() )
00296   {
00297     geometryType = feature->geometry()->type();
00298     wkbType = feature->geometry()->wkbType();
00299   }
00300 
00301   if ( geometryType == QGis::Line )
00302   {
00303     double dist = calc.measure( feature->geometry() );
00304     QGis::UnitType myDisplayUnits;
00305     convertMeasurement( calc, dist, myDisplayUnits, false );
00306     QString str = calc.textUnit( dist, 3, myDisplayUnits, false );  // dist and myDisplayUnits are out params
00307     derivedAttributes.insert( tr( "Length" ), str );
00308     if ( wkbType == QGis::WKBLineString || wkbType == QGis::WKBLineString25D )
00309     {
00310       // Add the start and end points in as derived attributes
00311       QgsPoint pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, feature->geometry()->asPolyline().first() );
00312       str = QLocale::system().toString( pnt.x(), 'g', 10 );
00313       derivedAttributes.insert( tr( "firstX", "attributes get sorted; translation for lastX should be lexically larger than this one" ), str );
00314       str = QLocale::system().toString( pnt.y(), 'g', 10 );
00315       derivedAttributes.insert( tr( "firstY" ), str );
00316       pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, feature->geometry()->asPolyline().last() );
00317       str = QLocale::system().toString( pnt.x(), 'g', 10 );
00318       derivedAttributes.insert( tr( "lastX", "attributes get sorted; translation for firstX should be lexically smaller than this one" ), str );
00319       str = QLocale::system().toString( pnt.y(), 'g', 10 );
00320       derivedAttributes.insert( tr( "lastY" ), str );
00321     }
00322   }
00323   else if ( geometryType == QGis::Polygon )
00324   {
00325     double area = calc.measure( feature->geometry() );
00326     double perimeter = calc.measurePerimeter( feature->geometry() );
00327     QGis::UnitType myDisplayUnits;
00328     convertMeasurement( calc, area, myDisplayUnits, true );  // area and myDisplayUnits are out params
00329     QString str = calc.textUnit( area, 3, myDisplayUnits, true );
00330     derivedAttributes.insert( tr( "Area" ), str );
00331     convertMeasurement( calc, perimeter, myDisplayUnits, false );  // perimeter and myDisplayUnits are out params
00332     str = calc.textUnit( perimeter, 3, myDisplayUnits, false );
00333     derivedAttributes.insert( tr( "Perimeter" ), str );
00334   }
00335   else if ( geometryType == QGis::Point &&
00336             ( wkbType == QGis::WKBPoint || wkbType == QGis::WKBPoint25D ) )
00337   {
00338     // Include the x and y coordinates of the point as a derived attribute
00339     QgsPoint pnt = mCanvas->mapRenderer()->layerToMapCoordinates( layer, feature->geometry()->asPoint() );
00340     QString str = QLocale::system().toString( pnt.x(), 'g', 10 );
00341     derivedAttributes.insert( "X", str );
00342     str = QLocale::system().toString( pnt.y(), 'g', 10 );
00343     derivedAttributes.insert( "Y", str );
00344   }
00345 
00346   return derivedAttributes;
00347 }
00348 
00349 bool QgsMapToolIdentify::identifyRasterLayer( QList<IdentifyResult> *results, QgsRasterLayer *layer, QgsPoint point, QgsRectangle viewExtent, double mapUnitsPerPixel )
00350 {
00351   QgsDebugMsg( "point = " + point.toString() );
00352   if ( !layer ) return false;
00353 
00354   QgsRasterDataProvider *dprovider = layer->dataProvider();
00355   int capabilities = dprovider->capabilities();
00356   if ( !dprovider || !( capabilities & QgsRasterDataProvider::Identify ) )
00357   {
00358     return false;
00359   }
00360 
00361   try
00362   {
00363     point = toLayerCoordinates( layer, point );
00364   }
00365   catch ( QgsCsException &cse )
00366   {
00367     Q_UNUSED( cse );
00368     QgsDebugMsg( QString( "coordinate not reprojectable: %1" ).arg( cse.what() ) );
00369     return false;
00370   }
00371   QgsDebugMsg( QString( "point = %1 %2" ).arg( point.x() ).arg( point.y() ) );
00372 
00373   if ( !layer->extent().contains( point ) ) return false;
00374 
00375   QMap< QString, QString > attributes, derivedAttributes;
00376 
00377   QMap<int, QVariant> values;
00378 
00379   QgsRaster::IdentifyFormat format = QgsRasterDataProvider::identifyFormatFromName( layer->customProperty( "identify/format" ).toString() );
00380 
00381   // check if the format is really supported otherwise use first supported format
00382   if ( !( QgsRasterDataProvider::identifyFormatToCapability( format ) & capabilities ) )
00383   {
00384     if ( capabilities & QgsRasterInterface::IdentifyFeature ) format = QgsRaster::IdentifyFormatFeature;
00385     else if ( capabilities & QgsRasterInterface::IdentifyValue ) format = QgsRaster::IdentifyFormatValue;
00386     else if ( capabilities & QgsRasterInterface::IdentifyHtml ) format = QgsRaster::IdentifyFormatHtml;
00387     else if ( capabilities & QgsRasterInterface::IdentifyText ) format = QgsRaster::IdentifyFormatText;
00388     else return false;
00389   }
00390 
00391   // We can only use context (extent, width, height) if layer is not reprojected,
00392   // otherwise we don't know source resolution (size).
00393   if ( mCanvas->hasCrsTransformEnabled() && dprovider->crs() != mCanvas->mapRenderer()->destinationCrs() )
00394   {
00395     viewExtent = toLayerCoordinates( layer, viewExtent );
00396     values = dprovider->identify( point, format ).results();
00397   }
00398   else
00399   {
00400     // It would be nice to use the same extent and size which was used for drawing,
00401     // so that WCS can use cache from last draw, unfortunately QgsRasterLayer::draw()
00402     // is doing some tricks with extent and size to allign raster to output which
00403     // would be difficult to replicate here.
00404     // Note: cutting the extent may result in slightly different x and y resolutions
00405     // and thus shifted point calculated back in QGIS WMS (using average resolution)
00406     //viewExtent = dprovider->extent().intersect( &viewExtent );
00407 
00408     // Width and height are calculated from not projected extent and we hope that
00409     // are similar to source width and height used to reproject layer for drawing.
00410     // TODO: may be very dangerous, because it may result in different resolutions
00411     // in source CRS, and WMS server (QGIS server) calcs wrong coor using average resolution.
00412     int width = qRound( viewExtent.width() / mapUnitsPerPixel );
00413     int height = qRound( viewExtent.height() / mapUnitsPerPixel );
00414 
00415     QgsDebugMsg( QString( "viewExtent.width = %1 viewExtent.height = %2" ).arg( viewExtent.width() ).arg( viewExtent.height() ) );
00416     QgsDebugMsg( QString( "width = %1 height = %2" ).arg( width ).arg( height ) );
00417     QgsDebugMsg( QString( "xRes = %1 yRes = %2 mapUnitsPerPixel = %3" ).arg( viewExtent.width() / width ).arg( viewExtent.height() / height ).arg( mapUnitsPerPixel ) );
00418 
00419     values = dprovider->identify( point, format, viewExtent, width, height ).results();
00420   }
00421 
00422   derivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
00423 
00424   //QString type = tr( "Raster" );
00425   QgsGeometry geometry;
00426   if ( format == QgsRaster::IdentifyFormatValue )
00427   {
00428     foreach ( int bandNo, values.keys() )
00429     {
00430       QString valueString;
00431       if ( values.value( bandNo ).isNull() )
00432       {
00433         valueString = tr( "no data" );
00434       }
00435       else
00436       {
00437         double value = values.value( bandNo ).toDouble();
00438         valueString = QgsRasterBlock::printValue( value );
00439       }
00440       attributes.insert( dprovider->generateBandName( bandNo ), valueString );
00441     }
00442     QString label = layer->name();
00443     results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
00444   }
00445   else if ( format == QgsRaster::IdentifyFormatFeature )
00446   {
00447     foreach ( int i, values.keys() )
00448     {
00449       QVariant value = values.value( i );
00450       if ( value.type() == QVariant::Bool && !value.toBool() )
00451       {
00452         // sublayer not visible or not queryable
00453         continue;
00454       }
00455 
00456       if ( value.type() == QVariant::String )
00457       {
00458         // error
00459         // TODO: better error reporting
00460         QString label = layer->subLayers().value( i );
00461         attributes.clear();
00462         attributes.insert( tr( "Error" ), value.toString() );
00463 
00464         results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
00465         continue;
00466       }
00467 
00468       // list of feature stores for a single sublayer
00469       QgsFeatureStoreList featureStoreList = values.value( i ).value<QgsFeatureStoreList>();
00470 
00471       foreach ( QgsFeatureStore featureStore, featureStoreList )
00472       {
00473         foreach ( QgsFeature feature, featureStore.features() )
00474         {
00475           attributes.clear();
00476           // WMS sublayer and feature type, a sublayer may contain multiple feature types.
00477           // Sublayer name may be the same as layer name and feature type name
00478           // may be the same as sublayer. We try to avoid duplicities in label.
00479           QString sublayer = featureStore.params().value( "sublayer" ).toString();
00480           QString featureType = featureStore.params().value( "featureType" ).toString();
00481           // Strip UMN MapServer '_feature'
00482           featureType.remove( "_feature" );
00483           QStringList labels;
00484           if ( sublayer.compare( layer->name(), Qt::CaseInsensitive ) != 0 )
00485           {
00486             labels << sublayer;
00487           }
00488           if ( featureType.compare( sublayer, Qt::CaseInsensitive ) != 0 || labels.isEmpty() )
00489           {
00490             labels << featureType;
00491 
00492 
00493           }
00494 
00495           QMap< QString, QString > derAttributes = derivedAttributes;
00496           derAttributes.unite( featureDerivedAttributes( &feature, layer ) );
00497 
00498           IdentifyResult identifyResult( qobject_cast<QgsMapLayer *>( layer ), labels.join( " / " ), featureStore.fields(), feature, derAttributes );
00499 
00500           identifyResult.mParams.insert( "getFeatureInfoUrl", featureStore.params().value( "getFeatureInfoUrl" ) );
00501           results->append( identifyResult );
00502         }
00503       }
00504     }
00505   }
00506   else // text or html
00507   {
00508     QgsDebugMsg( QString( "%1 html or text values" ).arg( values.size() ) );
00509     foreach ( int bandNo, values.keys() )
00510     {
00511       QString value = values.value( bandNo ).toString();
00512       attributes.clear();
00513       attributes.insert( "", value );
00514 
00515       QString label = layer->subLayers().value( bandNo );
00516       results->append( IdentifyResult( qobject_cast<QgsMapLayer *>( layer ), label, attributes, derivedAttributes ) );
00517     }
00518   }
00519 
00520   return true;
00521 }
00522 
00523 void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea )
00524 {
00525   // Helper for converting between meters and feet
00526   // The parameter &u is out only...
00527 
00528   // Get the canvas units
00529   QGis::UnitType myUnits = mCanvas->mapUnits();
00530 
00531   calc.convertMeasurement( measure, myUnits, displayUnits(), isArea );
00532   u = myUnits;
00533 }
00534 
00535 QGis::UnitType QgsMapToolIdentify::displayUnits()
00536 {
00537   return mCanvas->mapUnits();
00538 }
00539 
00540 void QgsMapToolIdentify::formatChanged( QgsRasterLayer *layer )
00541 {
00542   QgsDebugMsg( "Entered" );
00543   QList<IdentifyResult> results;
00544   if ( identifyRasterLayer( &results, layer, mLastPoint, mLastExtent, mLastMapUnitsPerPixel ) )
00545   {
00546     emit changedRasterResults( results );
00547   }
00548 }
00549 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines