1
/***************************************************************************
2
qgsmaptoolidentify.cpp - map tool for identifying features
5
copyright : (C) 2006 by Martin Dobias
6
email : wonder.sk at gmail dot com
7
***************************************************************************
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. *
14
***************************************************************************/
17
#include "qgscursors.h"
18
#include "qgsdistancearea.h"
19
#include "qgsfeature.h"
21
#include "qgsgeometry.h"
22
#include "qgslogger.h"
23
#include "qgsidentifyresults.h"
24
#include "qgsmapcanvas.h"
25
#include "qgsmaptopixel.h"
26
#include "qgsmessageviewer.h"
27
#include "qgsmaptoolidentify.h"
28
#include "qgsrasterlayer.h"
29
#include "qgscoordinatereferencesystem.h"
30
#include "qgsvectordataprovider.h"
31
#include "qgsvectorlayer.h"
32
#include "qgsproject.h"
33
#include "qgsmaplayerregistry.h"
37
#include <QMessageBox>
38
#include <QMouseEvent>
43
QgsMapToolIdentify::QgsMapToolIdentify( QgsMapCanvas* canvas )
44
: QgsMapTool( canvas )
47
QPixmap myIdentifyQPixmap = QPixmap(( const char ** ) identify_cursor );
48
mCursor = QCursor( myIdentifyQPixmap, 1, 1 );
51
QgsMapToolIdentify::~QgsMapToolIdentify()
59
QgsIdentifyResults *QgsMapToolIdentify::results()
62
mResults = new QgsIdentifyResults( mCanvas, mCanvas->window() );
67
void QgsMapToolIdentify::canvasMoveEvent( QMouseEvent * e )
71
void QgsMapToolIdentify::canvasPressEvent( QMouseEvent * e )
75
void QgsMapToolIdentify::canvasReleaseEvent( QMouseEvent * e )
77
if ( !mCanvas || mCanvas->isDrawing() )
85
int identifyMode = settings.value( "/Map/identifyMode", 0 ).toInt();
89
if ( identifyMode == 0 )
91
QgsMapLayer *layer = mCanvas->currentLayer();
95
QMessageBox::warning( mCanvas,
96
tr( "No active layer" ),
97
tr( "To identify features, you must choose an active layer by clicking on its name in the legend" ) );
101
QApplication::setOverrideCursor( Qt::WaitCursor );
103
res = identifyLayer( layer, e->x(), e->y() );
105
QApplication::restoreOverrideCursor();
109
connect( this, SIGNAL( identifyProgress( int, int ) ), QgisApp::instance(), SLOT( showProgress( int, int ) ) );
110
connect( this, SIGNAL( identifyMessage( QString ) ), QgisApp::instance(), SLOT( showStatusMessage( QString ) ) );
112
QApplication::setOverrideCursor( Qt::WaitCursor );
114
QStringList noIdentifyLayerIdList = QgsProject::instance()->readListEntry( "Identify", "/disabledLayers" );
116
for ( int i = 0; i < mCanvas->layerCount(); i++ )
118
QgsMapLayer *layer = mCanvas->layer( i );
120
emit identifyProgress( i, mCanvas->layerCount() );
121
emit identifyMessage( tr( "Identifying on %1..." ).arg( layer->name() ) );
123
if ( noIdentifyLayerIdList.contains( layer->getLayerID() ) )
126
if ( identifyLayer( layer, e->x(), e->y() ) )
129
if ( identifyMode == 1 )
134
emit identifyProgress( mCanvas->layerCount(), mCanvas->layerCount() );
135
emit identifyMessage( tr( "Identifying done." ) );
137
disconnect( this, SIGNAL( identifyProgress( int, int ) ), QgisApp::instance(), SLOT( showProgress( int, int ) ) );
138
disconnect( this, SIGNAL( identifyMessage( QString ) ), QgisApp::instance(), SLOT( showStatusMessage( QString ) ) );
140
QApplication::restoreOverrideCursor();
150
QSettings mySettings;
151
bool myDockFlag = mySettings.value( "/qgis/dockIdentifyResults", false ).toBool();
160
QgisApp::instance()->statusBar()->showMessage( tr( "No features at this position found." ) );
164
void QgsMapToolIdentify::activate()
166
results()->activate();
167
QgsMapTool::activate();
170
void QgsMapToolIdentify::deactivate()
172
results()->deactivate();
173
QgsMapTool::deactivate();
176
bool QgsMapToolIdentify::identifyLayer( QgsMapLayer *layer, int x, int y )
180
if ( layer->type() == QgsMapLayer::RasterLayer )
182
res = identifyRasterLayer( qobject_cast<QgsRasterLayer *>( layer ), x, y );
186
res = identifyVectorLayer( qobject_cast<QgsVectorLayer *>( layer ), x, y );
193
bool QgsMapToolIdentify::identifyVectorLayer( QgsVectorLayer *layer, int x, int y )
198
QMap< QString, QString > attributes, derivedAttributes;
200
QgsPoint point = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
202
derivedAttributes.insert( tr( "(clicked coordinate)" ), point.toString() );
204
// load identify radius from settings
206
double identifyValue = settings.value( "/Map/identifyRadius", QGis::DEFAULT_IDENTIFY_RADIUS ).toDouble();
207
QString ellipsoid = settings.value( "/qgis/measure/ellipsoid", "WGS84" ).toString();
209
if ( identifyValue <= 0.0 )
210
identifyValue = QGis::DEFAULT_IDENTIFY_RADIUS;
212
int featureCount = 0;
213
const QgsFieldMap& fields = layer->pendingFields();
215
// init distance/area calculator
216
QgsDistanceArea calc;
217
calc.setProjectionsEnabled( mCanvas->hasCrsTransformEnabled() ); // project?
218
calc.setEllipsoid( ellipsoid );
219
calc.setSourceCrs( layer->srs().srsid() );
221
QgsFeatureList featureList;
223
// toLayerCoordinates will throw an exception for an 'invalid' point.
224
// For example, if you project a world map onto a globe using EPSG 2163
225
// and then click somewhere off the globe, an exception will be thrown.
228
// create the search rectangle
229
double searchRadius = mCanvas->extent().width() * ( identifyValue / 100.0 );
232
r.setXMinimum( point.x() - searchRadius );
233
r.setXMaximum( point.x() + searchRadius );
234
r.setYMinimum( point.y() - searchRadius );
235
r.setYMaximum( point.y() + searchRadius );
237
r = toLayerCoordinates( layer, r );
239
layer->select( layer->pendingAllAttributesList(), r, true, true );
241
while ( layer->nextFeature( f ) )
242
featureList << QgsFeature( f );
244
catch ( QgsCsException & cse )
247
// catch exception for 'invalid' point and proceed with no features found
248
QgsDebugMsg( QString( "Caught CRS exception %1" ).arg( cse.what() ) );
251
QgsFeatureList::iterator f_it = featureList.begin();
253
for ( ; f_it != featureList.end(); ++f_it )
257
int fid = f_it->id();
258
QString displayField, displayValue;
259
QMap<QString, QString> attributes, derivedAttributes;
261
const QgsAttributeMap& attr = f_it->attributeMap();
263
for ( QgsAttributeMap::const_iterator it = attr.begin(); it != attr.end(); ++it )
265
QString attributeName = layer->attributeDisplayName( it.key() );
266
QString attributeValue = it->isNull() ? "NULL" : it->toString();
268
switch ( layer->editType( it.key() ) )
270
case QgsVectorLayer::Hidden:
273
case QgsVectorLayer::ValueMap:
274
attributeValue = layer->valueMap( it.key() ).key( it->toString(), QString( "(%1)" ).arg( it->toString() ) );
281
if ( fields[it.key()].name() == layer->displayField() )
283
displayField = attributeName;
284
displayValue = attributeValue;
287
attributes.insert( attributeName, attributeValue );
290
// Calculate derived attributes and insert:
291
// measure distance or area depending on geometry type
292
if ( layer->geometryType() == QGis::Line )
294
double dist = calc.measure( f_it->geometry() );
295
QGis::UnitType myDisplayUnits;
296
convertMeasurement( calc, dist, myDisplayUnits, false );
297
QString str = calc.textUnit( dist, 3, myDisplayUnits, false ); // dist and myDisplayUnits are out params
298
derivedAttributes.insert( tr( "Length" ), str );
299
if ( f_it->geometry()->wkbType() == QGis::WKBLineString )
301
// Add the start and end points in as derived attributes
302
str = QLocale::system().toString( f_it->geometry()->asPolyline().first().x(), 'g', 10 );
303
derivedAttributes.insert( tr( "firstX", "attributes get sorted; translation for lastX should be lexically larger than this one" ), str );
304
str = QLocale::system().toString( f_it->geometry()->asPolyline().first().y(), 'g', 10 );
305
derivedAttributes.insert( tr( "firstY" ), str );
306
str = QLocale::system().toString( f_it->geometry()->asPolyline().last().x(), 'g', 10 );
307
derivedAttributes.insert( tr( "lastX", "attributes get sorted; translation for firstX should be lexically smaller than this one" ), str );
308
str = QLocale::system().toString( f_it->geometry()->asPolyline().last().y(), 'g', 10 );
309
derivedAttributes.insert( tr( "lastY" ), str );
312
else if ( layer->geometryType() == QGis::Polygon )
314
double area = calc.measure( f_it->geometry() );
315
QGis::UnitType myDisplayUnits;
316
convertMeasurement( calc, area, myDisplayUnits, true ); // area and myDisplayUnits are out params
317
QString str = calc.textUnit( area, 3, myDisplayUnits, true );
318
derivedAttributes.insert( tr( "Area" ), str );
320
else if ( layer->geometryType() == QGis::Point )
322
// Include the x and y coordinates of the point as a derived attribute
324
str = QLocale::system().toString( f_it->geometry()->asPoint().x(), 'g', 10 );
325
derivedAttributes.insert( "X", str );
326
str = QLocale::system().toString( f_it->geometry()->asPoint().y(), 'g', 10 );
327
derivedAttributes.insert( "Y", str );
330
derivedAttributes.insert( tr( "feature id" ), fid < 0 ? tr( "new feature" ) : QString::number( fid ) );
332
results()->addFeature( layer, fid, displayField, displayValue, attributes, derivedAttributes );
335
QgsDebugMsg( "Feature count on identify: " + QString::number( featureCount ) );
337
return featureCount > 0;
340
bool QgsMapToolIdentify::identifyRasterLayer( QgsRasterLayer *layer, int x, int y )
347
QgsRasterDataProvider *dprovider = layer->dataProvider();
348
if ( dprovider && ( dprovider->capabilities() & QgsRasterDataProvider::Identify ) == 0 )
351
QMap< QString, QString > attributes, derivedAttributes;
352
QgsPoint idPoint = mCanvas->getCoordinateTransform()->toMapCoordinates( x, y );
355
if ( layer->providerKey() == "wms" )
357
type = tr( "WMS layer" );
359
//if WMS layer does not cover the view origin,
360
//we need to map the view pixel coordinates
361
//to WMS layer pixel coordinates
362
QgsRectangle viewExtent = mCanvas->extent();
363
QgsRectangle layerExtent = layer->extent();
364
double mapUnitsPerPixel = mCanvas->mapUnitsPerPixel();
365
if ( mapUnitsPerPixel > 0 && viewExtent.intersects( layerExtent ) )
367
double xMinView = viewExtent.xMinimum();
368
double yMaxView = viewExtent.yMaximum();
369
double xMinLayer = layerExtent.xMinimum();
370
double yMaxLayer = layerExtent.yMaximum();
373
xMinView < xMinLayer ? floor( x - ( xMinLayer - xMinView ) / mapUnitsPerPixel ) : x,
374
yMaxView > yMaxLayer ? floor( y - ( yMaxView - yMaxLayer ) / mapUnitsPerPixel ) : y
377
attributes.insert( tr( "Feature info" ), layer->identifyAsText( idPoint ) );
386
type = tr( "Raster" );
387
res = layer->extent().contains( idPoint ) && layer->identify( idPoint, attributes );
392
derivedAttributes.insert( tr( "(clicked coordinate)" ), idPoint.toString() );
393
results()->addFeature( layer, -1, type, "", attributes, derivedAttributes );
400
void QgsMapToolIdentify::convertMeasurement( QgsDistanceArea &calc, double &measure, QGis::UnitType &u, bool isArea )
402
// Helper for converting between meters and feet
403
// The parameter &u is out only...
405
QGis::UnitType myUnits = mCanvas->mapUnits();
406
if (( myUnits == QGis::Degrees || myUnits == QGis::Feet ) &&
407
calc.ellipsoid() != "NONE" &&
408
calc.hasCrsTransformEnabled() )
410
// Measuring on an ellipsoid returns meters, and so does using projections???
411
myUnits = QGis::Meters;
412
QgsDebugMsg( "We're measuring on an ellipsoid or using projections, the system is returning meters" );
415
// Get the units for display
417
QString myDisplayUnitsTxt = settings.value( "/qgis/measure/displayunits", "meters" ).toString();
419
// Only convert between meters and feet
420
if ( myUnits == QGis::Meters && myDisplayUnitsTxt == "feet" )
422
QgsDebugMsg( QString( "Converting %1 meters" ).arg( QString::number( measure ) ) );
428
QgsDebugMsg( QString( "to %1 feet" ).arg( QString::number( measure ) ) );
429
myUnits = QGis::Feet;
431
if ( myUnits == QGis::Feet && myDisplayUnitsTxt == "meters" )
433
QgsDebugMsg( QString( "Converting %1 feet" ).arg( QString::number( measure ) ) );
439
QgsDebugMsg( QString( "to %1 meters" ).arg( QString::number( measure ) ) );
440
myUnits = QGis::Meters;