QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgsquickidentifykit.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsquickidentifykit.cpp
3  ---------------------
4  Date : 30.8.2016
5  Copyright : (C) 2016 by Matthias Kuhn
6  Email : matthias (at) opengis.ch
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 "qgsmessagelog.h"
17 #include "qgsproject.h"
18 #include "qgslogger.h"
19 #include "qgsrenderer.h"
20 #include "qgsvectorlayer.h"
21 
22 #include "qgsquickidentifykit.h"
23 #include "qgsquickmapsettings.h"
25 
26 #include "qgis.h"
27 
29  : QObject( parent )
30 {
31 }
32 
34 {
35  return mMapSettings;
36 }
37 
39 {
40  if ( mapSettings == mMapSettings )
41  return;
42 
43  mMapSettings = mapSettings;
44  emit mapSettingsChanged();
45 }
46 
48 {
50 
51  if ( !mMapSettings )
52  {
53  QgsDebugMsg( QStringLiteral( "Unable to use IdentifyKit without mapSettings property set." ) );
54  return results;
55  }
56  QgsPointXY mapPoint = mMapSettings->mapSettings().mapToPixel().toMapCoordinates( point.toPoint() );
57 
58  if ( layer )
59  {
60  QgsFeatureList featureList = identifyVectorLayer( layer, mapPoint );
61  for ( const QgsFeature &feature : featureList )
62  {
63  results.append( QgsQuickFeatureLayerPair( feature, layer ) );
64  }
65  QgsDebugMsg( QStringLiteral( "IdentifyKit identified %1 results for layer %2" ).arg( results.count() ).arg( layer->name() ) );
66  }
67  else
68  {
69  for ( QgsMapLayer *layer : mMapSettings->mapSettings().layers() )
70  {
71  if ( mMapSettings->project() && !layer->flags().testFlag( QgsMapLayer::Identifiable ) )
72  continue;
73 
74  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
75  if ( vl )
76  {
77  QgsFeatureList featureList = identifyVectorLayer( vl, mapPoint );
78 
79  for ( const QgsFeature &feature : featureList )
80  {
81  results.append( QgsQuickFeatureLayerPair( feature, vl ) );
82  }
83  }
84  if ( mIdentifyMode == IdentifyMode::TopDownStopAtFirst && !results.isEmpty() )
85  {
86  QgsDebugMsg( QStringLiteral( "IdentifyKit identified %1 results with TopDownStopAtFirst mode." ).arg( results.count() ) );
87  return results;
88  }
89  }
90 
91  QgsDebugMsg( QStringLiteral( "IdentifyKit identified %1 results" ).arg( results.count() ) );
92  }
93 
94  return results;
95 }
96 
97 static QgsQuickFeatureLayerPair _closestFeature( const QgsQuickFeatureLayerPairs &results, const QgsMapSettings &mapSettings, const QPointF &point, double searchRadius )
98 {
99  QgsPointXY mapPoint = mapSettings.mapToPixel().toMapCoordinates( point.toPoint() );
100  QgsGeometry mapPointGeom( QgsGeometry::fromPointXY( mapPoint ) );
101 
102  double distMinPoint = 1e10, distMinLine = 1e10, distMinPolygon = 1e10;
103  int iMinPoint = -1, iMinLine = -1, iMinPolygon = -1;
104  for ( int i = 0; i < results.count(); ++i )
105  {
106  const QgsQuickFeatureLayerPair &res = results.at( i );
107  QgsGeometry geom( res.feature().geometry() );
108  try
109  {
110  geom.transform( mapSettings.layerTransform( res.layer() ) );
111  }
112  catch ( QgsCsException &e )
113  {
114  Q_UNUSED( e )
115  // Caught an error in transform
116  continue;
117  }
118 
119  double dist = geom.distance( mapPointGeom );
120  QgsWkbTypes::GeometryType type = QgsWkbTypes::geometryType( geom.wkbType() );
121  if ( type == QgsWkbTypes::PointGeometry )
122  {
123  if ( dist < distMinPoint )
124  {
125  iMinPoint = i;
126  distMinPoint = dist;
127  }
128  }
129  else if ( type == QgsWkbTypes::LineGeometry )
130  {
131  if ( dist < distMinLine )
132  {
133  iMinLine = i;
134  distMinLine = dist;
135  }
136  }
137  else // polygons
138  {
139  if ( dist < distMinPolygon )
140  {
141  iMinPolygon = i;
142  distMinPolygon = dist;
143  }
144  }
145  }
146 
147  // we give priority to points, then lines, then polygons
148  // the rationale is that points in polygon (or on a line) would have nearly
149  // always non-zero distance while polygon surrounding it has zero distance,
150  // so it would be difficult to identify it
151  if ( iMinPoint != -1 && distMinPoint <= searchRadius )
152  return results.at( iMinPoint );
153  else if ( iMinLine != -1 && distMinLine <= searchRadius )
154  return results.at( iMinLine );
155  else if ( iMinPolygon != -1 )
156  return results.at( iMinPolygon );
157  else
158  return QgsQuickFeatureLayerPair();
159 }
160 
162 {
163  QgsQuickFeatureLayerPairs results = identify( point, layer );
164  return _closestFeature( results, mMapSettings->mapSettings(), point, searchRadiusMU() );
165 }
166 
167 QgsFeatureList QgsQuickIdentifyKit::identifyVectorLayer( QgsVectorLayer *layer, const QgsPointXY &point ) const
168 {
169  QgsFeatureList results;
170 
171  if ( !layer || !layer->isSpatial() )
172  return results;
173 
174  if ( !layer->isInScaleRange( mMapSettings->mapSettings().scale() ) )
175  return results;
176 
177  QgsFeatureList featureList;
178 
179  // toLayerCoordinates will throw an exception for an 'invalid' point.
180  // For example, if you project a world map onto a globe using EPSG 2163
181  // and then click somewhere off the globe, an exception will be thrown.
182  try
183  {
184  // create the search rectangle
185  double searchRadius = searchRadiusMU();
186 
187  QgsRectangle r;
188  r.setXMinimum( point.x() - searchRadius );
189  r.setXMaximum( point.x() + searchRadius );
190  r.setYMinimum( point.y() - searchRadius );
191  r.setYMaximum( point.y() + searchRadius );
192 
193  r = toLayerCoordinates( layer, r );
194 
195  QgsFeatureRequest req;
196  req.setFilterRect( r );
197  req.setLimit( mFeaturesLimit );
199 
200  QgsFeatureIterator fit = layer->getFeatures( req );
201  QgsFeature f;
202  while ( fit.nextFeature( f ) )
203  featureList << QgsFeature( f );
204  }
205  catch ( QgsCsException &cse )
206  {
207  QgsDebugMsg( QStringLiteral( "Invalid point, proceed without a found features." ) );
208  Q_UNUSED( cse )
209  }
210 
211  bool filter = false;
212 
213  QgsRenderContext context( QgsRenderContext::fromMapSettings( mMapSettings->mapSettings() ) );
214  context.expressionContext() << QgsExpressionContextUtils::layerScope( layer );
215  QgsFeatureRenderer *renderer = layer->renderer();
216  if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
217  {
218  // setup scale for scale dependent visibility (rule based)
219  renderer->startRender( context, layer->fields() );
220  filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
221  }
222 
223  for ( const QgsFeature &feature : featureList )
224  {
225  context.expressionContext().setFeature( feature );
226 
227  if ( filter && !renderer->willRenderFeature( const_cast<QgsFeature &>( feature ), context ) )
228  continue;
229 
230  results.append( feature );
231  }
232 
233  if ( renderer && renderer->capabilities() & QgsFeatureRenderer::ScaleDependent )
234  {
235  renderer->stopRender( context );
236  }
237 
238  return results;
239 }
240 
241 double QgsQuickIdentifyKit::searchRadiusMU( const QgsRenderContext &context ) const
242 {
243  return mSearchRadiusMm * context.scaleFactor() * context.mapToPixel().mapUnitsPerPixel();
244 }
245 
246 double QgsQuickIdentifyKit::searchRadiusMU() const
247 {
249  return searchRadiusMU( context );
250 }
251 
252 QgsRectangle QgsQuickIdentifyKit::toLayerCoordinates( QgsMapLayer *layer, const QgsRectangle &rect ) const
253 {
254  return mMapSettings->mapSettings().mapToLayerCoordinates( layer, rect );
255 }
256 
258 {
259  return mSearchRadiusMm;
260 }
261 
262 void QgsQuickIdentifyKit::setSearchRadiusMm( double searchRadiusMm )
263 {
264  if ( qgsDoubleNear( mSearchRadiusMm, searchRadiusMm ) )
265  return;
266 
267  mSearchRadiusMm = searchRadiusMm;
268  emit searchRadiusMmChanged();
269 }
270 
272 {
273  return mFeaturesLimit;
274 }
275 
277 {
278  if ( mFeaturesLimit == limit )
279  return;
280 
281  mFeaturesLimit = limit;
282  emit featuresLimitChanged();
283 }
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
virtual QgsFeatureRenderer::Capabilities capabilities()
Returns details about internals of this renderer.
Definition: qgsrenderer.h:274
@ ScaleDependent
Depends on scale if feature will be rendered (rule based )
Definition: qgsrenderer.h:257
@ Filter
Features may be filtered, i.e. some features may not be rendered (categorized, rule based ....
Definition: qgsrenderer.h:256
virtual bool willRenderFeature(const QgsFeature &feature, QgsRenderContext &context) const
Returns whether the renderer will render a feature or not.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
Definition: qgsrenderer.cpp:94
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setLimit(long limit)
Set the maximum number of features to request.
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
static QgsGeometry fromPointXY(const QgsPointXY &point) SIP_HOLDGIL
Creates a new geometry from a QgsPointXY object.
Base class for all map layer types.
Definition: qgsmaplayer.h:85
QString name
Definition: qgsmaplayer.h:88
bool isInScaleRange(double scale) const
Tests whether the layer should be visible at the specified scale.
QgsMapLayer::LayerFlags flags() const
Returns the flags for this layer.
@ Identifiable
If the layer is identifiable using the identify map tool and as a WMS layer.
Definition: qgsmaplayer.h:154
The QgsMapSettings class contains configuration for rendering of the map.
double scale() const
Returns the calculated map scale.
QgsCoordinateTransform layerTransform(const QgsMapLayer *layer) const
Returns the coordinate transform from layer's CRS to destination CRS.
const QgsMapToPixel & mapToPixel() const
QList< QgsMapLayer * > layers() const
Returns the list of layers which will be rendered in the map.
QgsPointXY mapToLayerCoordinates(const QgsMapLayer *layer, QgsPointXY point) const
transform point coordinates from output CRS to layer's CRS
double mapUnitsPerPixel() const
Returns current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transform device coordinates to map (world) coordinates.
A class to represent a 2D point.
Definition: qgspointxy.h:44
double y
Definition: qgspointxy.h:48
Q_GADGET double x
Definition: qgspointxy.h:47
Pair of QgsFeature and QgsVectorLayer.
QgsFeature feature
Feature that belongs to layer.
Q_GADGET QgsVectorLayer * layer
Vector layer to which the feature belongs.
void searchRadiusMmChanged()
Search radius for the identify functions.
void mapSettingsChanged()
Map settings.
int featuresLimit
Maximum number of features returned from the QgsQuickIdentifyKit::identify()
void setSearchRadiusMm(double searchRadiusMm)
Search radius for the identify functions.
void setFeaturesLimit(int limit)
Maximum number of features returned from the QgsQuickIdentifyKit::identify()
void featuresLimitChanged()
Maximum number of features returned from the QgsQuickIdentifyKit::identify()
QgsQuickMapSettings * mapSettings
Map settings.
QgsQuickIdentifyKit(QObject *parent=nullptr)
Constructor of new identify kit.
double searchRadiusMm
Search radius for the identify functions.
Q_INVOKABLE QgsQuickFeatureLayerPairs identify(const QPointF &point, QgsVectorLayer *layer=nullptr)
Gets all features in the search radius.
Q_INVOKABLE QgsQuickFeatureLayerPair identifyOne(const QPointF &point, QgsVectorLayer *layer=nullptr)
Gets the closest feature to the point within the search radius.
void setMapSettings(QgsQuickMapSettings *mapSettings)
Map settings.
The QgsQuickMapSettings class encapsulates QgsMapSettings class to offer settings of configuration of...
QgsMapSettings mapSettings() const
Clone map settings.
QgsProject * project
A project property should be used as a primary source of project all other components in the applicat...
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:140
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:145
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
Represents a vector layer which manages a vector based data sets.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
static GeometryType geometryType(Type type) SIP_HOLDGIL
Returns the geometry type for a WKB type, e.g., both MultiPolygon and CurvePolygon would have a Polyg...
Definition: qgswkbtypes.h:938
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:316
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:614
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QList< QgsQuickFeatureLayerPair > QgsQuickFeatureLayerPairs