Fix search tolerance when doing identification in 3D map view

Until now the identification from 3D map view used tolerance based
on the current view of the main 2D map canvas - that was giving often
unexpected results if the 2D map canvas had significantly different
zoom level from the 3D map view.
This commit is contained in:
Martin Dobias 2018-09-18 23:38:27 +02:00
parent e3f63d8811
commit 2da705864b
8 changed files with 91 additions and 4 deletions

View File

@ -161,6 +161,31 @@ Call the right method depending on layer type
QMap< QString, QString > derivedAttributesForPoint( const QgsPoint &point );
%Docstring
Returns derived attributes map for a clicked point in map coordinates. May be 2D or 3D point.
%End
void setCanvasPropertiesOverrides( double searchRadiusMapUnits );
%Docstring
Overrides some map canvas properties inside the map tool for the upcoming identify requests.
This is useful when the identification is triggered by some other piece of GUI like a 3D map view
and some properties like search radius need to be adjusted so that identification returns correct
results. Currently only search radius may be overridden.
When the custom identification has finished, restoreCanvasPropertiesOverrides() should
be called to erase any overrides.
.. seealso:: :py:func:`restoreCanvasProperties`
.. versionadded:: 3.4
%End
void restoreCanvasPropertiesOverrides();
%Docstring
Clears canvas properties overrides previously set with setCanvasPropertiesOverrides()
.. seealso:: :py:func:`setCanvasPropertiesOverrides`
.. versionadded:: 3.4
%End
};

View File

@ -210,6 +210,20 @@ void Qgs3DMapScene::unregisterPickHandler( Qgs3DMapScenePickHandler *pickHandler
}
}
float Qgs3DMapScene::worldSpaceError( float epsilon, float distance )
{
Qt3DRender::QCamera *camera = mCameraController->camera();
float fov = camera->fieldOfView();
QRect rect = mCameraController->viewport();
float screenSizePx = std::max( rect.width(), rect.height() ); // TODO: is this correct?
// in qgschunkedentity_p.cpp there is inverse calculation (world space error to screen space error)
// with explanation of the math.
float frustumWidthAtDistance = 2 * distance * tan( fov / 2 );
float err = frustumWidthAtDistance * epsilon / screenSizePx;
return err;
}
QgsChunkedEntity::SceneState _sceneState( QgsCameraController *cameraController )
{
Qt3DRender::QCamera *camera = cameraController->camera();

View File

@ -83,6 +83,12 @@ class _3D_EXPORT Qgs3DMapScene : public Qt3DCore::QEntity
//! Unregisters previously registered pick handler. Pick handler is not deleted. Also removes object picker components from 3D entities.
void unregisterPickHandler( Qgs3DMapScenePickHandler *pickHandler );
/**
* Given screen error (in pixels) and distance from camera (in 3D world coordinates), this function
* estimates the error in world space. Takes into account camera's field of view and the screen (3D view) size.
*/
float worldSpaceError( float epsilon, float distance );
signals:
//! Emitted when the current terrain entity is replaced by a new one
void terrainEntityChanged();

View File

@ -96,9 +96,16 @@ void Qgs3DMapToolIdentify::onTerrainPicked( Qt3DRender::QPickEvent *event )
QgsGeometry geom = QgsGeometry::fromPointXY( QgsPointXY( mapCoords.x(), mapCoords.y() ) );
// estimate search radius
Qgs3DMapScene *scene = mCanvas->scene();
double searchRadiusMM = QgsMapTool::searchRadiusMM();
double pixelsPerMM = mCanvas->logicalDpiX() / 25.4;
double searchRadiusPx = searchRadiusMM * pixelsPerMM;
double searchRadiusMapUnits = scene->worldSpaceError( searchRadiusPx, event->distance() );
QgsMapToolIdentifyAction *identifyTool2D = QgisApp::instance()->identifyMapTool();
identifyTool2D->identifyAndShowResults( geom );
identifyTool2D->identifyAndShowResults( geom, searchRadiusMapUnits );
}
void Qgs3DMapToolIdentify::onTerrainEntityChanged()

View File

@ -212,9 +212,11 @@ void QgsMapToolIdentifyAction::deactivate()
QgsMapToolIdentify::deactivate();
}
void QgsMapToolIdentifyAction::identifyAndShowResults( const QgsGeometry &geom )
void QgsMapToolIdentifyAction::identifyAndShowResults( const QgsGeometry &geom, double searchRadiusMapUnits )
{
setCanvasPropertiesOverrides( searchRadiusMapUnits );
mSelectionHandler->setSelectedGeometry( geom );
restoreCanvasPropertiesOverrides();
}
void QgsMapToolIdentifyAction::clearResults()

View File

@ -62,7 +62,7 @@ class APP_EXPORT QgsMapToolIdentifyAction : public QgsMapToolIdentify
void deactivate() override;
//! Triggers map identification of at the given location and outputs results in GUI
void identifyAndShowResults( const QgsGeometry &geom );
void identifyAndShowResults( const QgsGeometry &geom, double searchRadiusMapUnits );
//! Clears any previous results from the GUI
void clearResults();
//! Looks up feature by its ID and outputs the result in GUI

View File

@ -179,6 +179,16 @@ QList<QgsMapToolIdentify::IdentifyResult> QgsMapToolIdentify::identify( const Qg
return results;
}
void QgsMapToolIdentify::setCanvasPropertiesOverrides( double searchRadiusMapUnits )
{
mOverrideCanvasSearchRadius = searchRadiusMapUnits;
}
void QgsMapToolIdentify::restoreCanvasPropertiesOverrides()
{
mOverrideCanvasSearchRadius = -1;
}
void QgsMapToolIdentify::activate()
{
QgsMapTool::activate();
@ -270,7 +280,7 @@ bool QgsMapToolIdentify::identifyVectorLayer( QList<QgsMapToolIdentify::Identify
QgsRectangle r;
if ( isSingleClick )
{
double sr = searchRadiusMU( mCanvas );
double sr = mOverrideCanvasSearchRadius < 0 ? searchRadiusMU( mCanvas ) : mOverrideCanvasSearchRadius;
r = toLayerCoordinates( layer, QgsRectangle( point.x() - sr, point.y() - sr, point.x() + sr, point.y() + sr ) );
}
else

View File

@ -168,6 +168,27 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
//! Returns derived attributes map for a clicked point in map coordinates. May be 2D or 3D point.
QMap< QString, QString > derivedAttributesForPoint( const QgsPoint &point );
/**
* Overrides some map canvas properties inside the map tool for the upcoming identify requests.
*
* This is useful when the identification is triggered by some other piece of GUI like a 3D map view
* and some properties like search radius need to be adjusted so that identification returns correct
* results. Currently only search radius may be overridden.
*
* When the custom identification has finished, restoreCanvasPropertiesOverrides() should
* be called to erase any overrides.
* \see restoreCanvasProperties()
* \since QGIS 3.4
*/
void setCanvasPropertiesOverrides( double searchRadiusMapUnits );
/**
* Clears canvas properties overrides previously set with setCanvasPropertiesOverrides()
* \see setCanvasPropertiesOverrides()
* \since QGIS 3.4
*/
void restoreCanvasPropertiesOverrides();
private:
bool identifyLayer( QList<QgsMapToolIdentify::IdentifyResult> *results, QgsMapLayer *layer, const QgsGeometry &geometry, const QgsRectangle &viewExtent, double mapUnitsPerPixel, QgsMapToolIdentify::LayerType layerType = AllLayers );
@ -238,6 +259,8 @@ class GUI_EXPORT QgsMapToolIdentify : public QgsMapTool
QgsRectangle mLastExtent;
int mCoordinatePrecision;
double mOverrideCanvasSearchRadius = -1;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsMapToolIdentify::LayerType )