Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP][BUGFIX][FEATURE][NEEDS-DOCS] Disable snapping on invisible features #6657

Closed
wants to merge 3 commits into from
Closed

[WIP][BUGFIX][FEATURE][NEEDS-DOCS] Disable snapping on invisible features #6657

wants to merge 3 commits into from

Conversation

lbartoletti
Copy link
Member

@lbartoletti lbartoletti commented Mar 21, 2018

Description

Bug founded by QWAT Team. Fixes #16838.

Solution: Filter on layer rendered.

New option to enable it or not:
option

Screenshot for a normal case:
normal

When this bug/feature is enabled:
option_enabled

Checklist

Reviewing is a process done by project maintainers, mostly on a volunteer basis. We try to keep the overhead as small as possible and appreciate if you help us to do so by completing the following items. Feel free to ask in a comment if you have troubles with any of them.

  • Commit messages are descriptive and explain the rationale for changes
  • Commits which fix bugs include fixes #11111 in the commit message next to the description
  • Commits which add new features are tagged with [FEATURE] in the commit message
  • Commits which change the UI or existing user workflows are tagged with [needs-docs] in the commit message and containt sufficient information in the commit message to be documented
  • I have read the QGIS Coding Standards and this PR complies with them
  • This PR passes all existing unit tests (test results will be reported by travis-ci after opening this PR)
  • New unit tests have been added for core changes
  • I have run the scripts/prepare-commit.sh script before each commit

Copy link
Collaborator

@nyalldawson nyalldawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately I don't think this approach will work - it's triggering a complete iteration through the layer (not just features in the view) for every candidate match -- it's going to be way too expensive to do.

@luipir
Copy link
Contributor

luipir commented Mar 21, 2018

I can't talk from a user point of view, but I feel that snapping should't be related with the fact that the layer is rendered or not (you may thing to snapToGrid, where grid is not a layer and nor is rendered).
If, from the usability point of view, this is the expected behaviour, I would expect that it would be configurable.
IMHO it's not a bug, but a feature.

@wonder-sk
Copy link
Member

Agreed with both Nyall (performance) and Luigi (configuration): in my opinion the default behavior should be that all features are considered by QgsSnappingUtils (if not, we are breaking the API!), and there should be a flag whether to consider just visible features which would be turned on in QgsMapCanvasSnappingUtils.

@andreasneumann
Copy link
Member

From a user point of view it is very confusing that invisible features (that aren't displayed for whatever reason) are acting as snap features. I don't agree with Luigi that it is a "feature".
It is probably a rare use-case that invisible features should act as snap features.

@saberraz
Copy link
Contributor

Is this only for snapping or does it cover Trace tool too?

@wonder-sk
Copy link
Member

To avoid confusion, here is how it should work IMHO:

  • QgsSnappingUtils: have a new flag whether to use all features or just the visible ones. default = all.
  • QgsMapCanvasSnappingUtils: the flag is set to just visible ones.

From user's point of view, in map canvas he/she only gets snapping to visible features. I don't think this needs to be configurable anywhere in the GUI... snapping to invisible features does not make much sense.

From developer's point of view, the spatial queries in QgsSnappingUtils work on all features, with extra option to use just visible features if needed. This class may be used in various analytical algorithms, so by default the behavior should not be affected by renderer configuration.

@andreasneumann
Copy link
Member

@wonder-sk thanks - agreed.

@lbartoletti
Copy link
Member Author

Thanks for your comments, I was still on it :) .
I wanted to add an option to enable or disable this portion ( bug or feature ;) )

Thanks @wonder-sk

@nyalldawson
Copy link
Collaborator

@lbartoletti

Something to keep in mind while reworking this is that renderer->filter() isn't comprehensive -- it's just a way to partially limit the features requested for the renderer.

Instead you need to check willRenderFeature instead.

@lbartoletti
Copy link
Member Author

@nyalldawson
I'm sorry, I was not able to use willRenderFeature, I had already started using a spatial filter. Did I miss something? Here's how I thought I'd use it.

      QgsFeature feat = candidateMatch.layer()->getFeature( candidateMatch.featureId() );
      QgsRenderContext ctx = QgsRenderContext::fromMapSettings( mapSettings );
      candidateMatch.layer()->renderer()->startRender( ctx, candidateMatch.layer()->fields() );
      visible = candidateMatch.layer()->renderer()->willRenderFeature( feat, ctx );
      candidateMatch.layer()->renderer()->stopRender( ctx );

Return always true.

@nyalldawson
Copy link
Collaborator

@lbartoletti

That should work - maybe the map settings isn't correctly constructed? Check how the identify/select tools operate - they do a similar thing.

@nyalldawson
Copy link
Collaborator

Even with the spatial filter this is still going to be very slow. You need to avoid all feature requests when checking a match, because even a feature request for a single matching feature is expensive.

@nyalldawson
Copy link
Collaborator

A couple of other tips:

  • Don't start/stop render on every match, try to do it once for each layer in advance and once after completing the snap. Start/stop render can also be expensive, and if you start/stop it multiple times you lose all benefit of prepared expressions, etc.
  • use a clone of the layer renderer, not the layers renderer itself

@lbartoletti lbartoletti changed the title [WIP][BUGFIX] Disable snapping on invisible features [WIP][BUGFIX][FEATURE][NEEDS-DOCS] Disable snapping on invisible features Mar 22, 2018
@lbartoletti
Copy link
Member Author

Thanks @nyalldawson willRenderFeature works, I have adapted identify tools.

@lbartoletti
Copy link
Member Author

@nyalldawson Maybe, I have founded a bug with willRenderFeature.

Some cases works, some returns always true.

I have made a reproductible example here with a video.

Any idea?

Copy link
Member

@3nids 3nids left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some small details...Otherwise, it's a long awaited BUGFIX....not a feature ;) ...

@@ -53,7 +53,7 @@ class CORE_EXPORT QgsSnappingUtils : public QObject
public:

//! Constructor for QgsSnappingUtils
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr );
QgsSnappingUtils( QObject *parent SIP_TRANSFERTHIS = nullptr, bool enableSnappingForInvisibleFeature = true );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to keep the set method only to set the option (i.e. remove it) or if we keep it in the constructor, can we use an enum instead of a bool then?

*
* \since QGIS 3.2
*/
void setEnableSnappingForInvisibleFeature( bool enableIt );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/enableIt/enable/


#include <QApplication>
#include <QProgressDialog>

QgsMapCanvasSnappingUtils::QgsMapCanvasSnappingUtils( QgsMapCanvas *canvas, QObject *parent )
: QgsSnappingUtils( parent )
: QgsSnappingUtils( parent, QgsSettings().value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using an enum would be nicer here since you could use
QgsSettings().enumValue( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), DoNotSnapOnInvisible );

Copy link
Collaborator

@nyalldawson nyalldawson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hate to keep throwing up issues here - but this code NEEDS to be fast and anything which potentially impacts the speed of snapping is a no-go for me. Fetching features one-at-a-time from a provider is very inefficient, so that needs to be reworked.

When that's addressed it also needs unit tests before it's merge-able.

@@ -970,6 +970,7 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl, const QList<QgsOpti

mSnappingMarkerColorButton->setColor( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_color" ), QColor( Qt::magenta ) ).value<QColor>() );
mSnappingTooltipsCheckbox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_tooltip" ), false ).toBool() );
mEnableSnappingOnInvisibleFeatureCheckbox->setChecked( mSettings->value( QStringLiteral( "/qgis/digitizing/snap_invisible_feature" ), false ).toBool() );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add this new setting to resources/qgis_global_settings.ini

if ( candidateMatch.layer() )
{
bool filter = false;
QgsRenderContext context( QgsRenderContext::fromMapSettings( mapSettings ) );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please move this up from _isMatchAVisibleLayer so that the render context and renderer is only constructed once per layer per snap? startRender is expensive so this is also a performance killer.

filter = renderer->capabilities() & QgsFeatureRenderer::Filter;
}

QgsFeature feat = candidateMatch.layer()->getFeature( candidateMatch.featureId() );
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to be a performance killer too - can you find a way to avoid fetching individual features and instead do a single feature fetch for the whole canvas extent at once?

@3nids
Copy link
Member

3nids commented Mar 26, 2018

I am just thinking of another approach: what about saving the info along when building the index. Then, if a layer gets its symbology edited, you update the index (just the visible boolean flag).

@wonder-sk does it make sense?

@lbartoletti
Copy link
Member Author

@nyalldawson
I haven't forgotten about your performance request. Before working on it, I wanted to make sure that it could work "like this".

@3nids I was also thinking about something like that.

@pblottiere
Copy link
Member

Maybe, I have founded a bug with willRenderFeature.
Some cases works, some returns always true.
I have made a reproductible example here with a video.

@lbartoletti I think this issue is fixed in this PR: #6679. Can you give it a try and let me know if you have some time? Thanks!

@wonder-sk
Copy link
Member

Agreed with @nyalldawson and @3nids - the checks whether features will be rendered should be IMHO done when building the index, it would be otherwise too slow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants