|
QGIS API Documentation
master-3f58142
|
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