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

Geometry validation of editing session #8103

Merged
merged 126 commits into from
Oct 16, 2018
Merged

Conversation

m-kuhn
Copy link
Member

@m-kuhn m-kuhn commented Oct 3, 2018

Introduces geometry validation of the current edit session.

The approach is like this:

  • There are single geometry checks, currently a single on which is "is valid". These checks are executed whenever a geometry on a layer is modified / added / deleted. So if a user introduces a self-intersection, he will be notified about this immediately and can fix it right away. As soon as it's fixed, the message disappears.

  • There are topology checks. Currently gaps, overlaps and missing vertices of neighbouring geometries can be enabled. These checks are triggered whenever a user tries to save a layer (or triggers them manually). They are then reported and will also need to be fixed, before the user is able to save the layer. These errors also propose fixes (thanks to the geometry check implementation) or can be manually resolved.

  • If no checks are enabled, no change in behaviour is noticeable in QGIS, so the whole functionality is opt-in.

peek 2018-10-03 14-32

This definitely is a lot of code to land at a late stage and I am fully aware of that. Much of the work in here can actually be considered "pre-freeze-bugfixing-and-cleanup" like porting APIs to QGIS 3 style and making things threadsafe. In order of work it just made more sense to do them before the actual implementation (or just while stumbling upon things while working).

@m-kuhn m-kuhn changed the title Geometry validator code 1 Geometry validation of editing session Oct 3, 2018
@nyalldawson
Copy link
Collaborator

An initial glance shows that this includes #8034 -- can we resolve the convo there separately? I don't want to split that discussion

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 4, 2018

of course, I'll just adapt here whenever there is a conclusion

@m-kuhn m-kuhn force-pushed the geometryValidatorCode_1 branch 2 times, most recently from e537fbd to cedea4f Compare October 8, 2018 06:03
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.

Code review only -- I haven't tested this in action ;)

@@ -76,10 +77,18 @@ class ANALYSIS_EXPORT QgsGeometryGapCheckError : public QgsGeometryCheckError
class ANALYSIS_EXPORT QgsGeometryGapCheck : public QgsGeometryCheck
{
public:
enum ResolutionMethod { MergeLongestEdge, NoChange };
Copy link
Collaborator

Choose a reason for hiding this comment

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

Dox

@@ -73,11 +76,32 @@ void QgsGeometryMissingVertexCheck::fixError( const QMap<QString, QgsFeaturePool
{
error->setFixed( method );
}
if ( method == AddMissingVertex )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would a switch be more appropriate here? (for future build warnings)

Copy link
Member Author

Choose a reason for hiding this comment

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

it's an integer that's passed in because the API is from the base class where the enum is unknown.
Would you cast to the proper enum for the switch?

Copy link
Member

Choose a reason for hiding this comment

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

@@ -57,15 +63,15 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap<QString, QgsFeaturePool
QString errMsg;
if ( geomEngineA->overlaps( layerFeatureB.geometry().constGet(), &errMsg ) )
{
QgsAbstractGeometry *interGeom = geomEngineA->intersection( layerFeatureB.geometry().constGet() );
std::unique_ptr<QgsAbstractGeometry> interGeom( geomEngineA->intersection( layerFeatureB.geometry().constGet() ) );
Copy link
Collaborator

Choose a reason for hiding this comment

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

There's a lot of places (like this) where QgsGeometryCheckerUtils::createGeomEngine is called and then the resulting engine is used to perform intersects/overlaps checks/etc. But the engine is never prepared, negating most of the benefits of using it. I think QgsGeometryCheckerUtils::createGeomEngine should probably always prepare the created engine before returning it to simplify this -- it'll greatly speed up the whole checking process.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do we always want to prepare an engine or are there cases where it's slower? And in the second case, how will a developer using the API know that he has got a case where preparing is worse than not preparing?

@@ -25,13 +25,34 @@
class ANALYSIS_EXPORT QgsGeometryOverlapCheckError : public QgsGeometryCheckError
{
public:

struct OverLappedFeature
Copy link
Collaborator

Choose a reason for hiding this comment

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

OverLapped -> Overlapped

// We keep all connections around in a list, so if in the future all checks get disabled
// we can kill those connections to be sure the layer does not even get a tiny bit of overhead.
checkInformation.connections
<< connect( layer, &QgsVectorLayer::featureAdded, this, [this, layer]( QgsFeatureId fid )
Copy link
Collaborator

Choose a reason for hiding this comment

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

Unique connections do not work for lambdas -- check Qt docs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense even without reading the docs.
These are here just for safety, it shouldn't normally happen. However I just replaced it with another safety measure to be on the safe side if this is suddenly called in other circumstances in the future.

{
emit geometryCheckStarted( layer, fid );

QgsFeature feature = layer->getFeature( fid );
Copy link
Collaborator

Choose a reason for hiding this comment

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

attributes not required

QgsRectangle area;
while ( it.nextFeature( feature ) )
{
area.combineExtentWith( feature.geometry().boundingBox() );
Copy link
Collaborator

Choose a reason for hiding this comment

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

Random thought -- maybe we need a feature request flag for "bounding boxes only". It'd certainly speed up a lot of code like this.

Copy link
Member Author

Choose a reason for hiding this comment

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

Random thought response: I'd like to have "derived values" in requests, like "request specific virtual fields".

request.setFlags(QgsFeatureRequest::NoGeometry)
request.addDerivedValue("bbox", "bounds($geometry)")

This would be very flexible, although I admit for this particular use case a custom flag could be justified

public:
struct FeatureError
{
FeatureError()
Copy link
Collaborator

Choose a reason for hiding this comment

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

= default

typedef QList<FeatureError> FeatureErrors;

QgsGeometryValidationService( QgsProject *project );
~QgsGeometryValidationService() = default;
Copy link
Collaborator

Choose a reason for hiding this comment

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

not required

@nyalldawson nyalldawson added Feature Freeze Exempt Feature Freeze exemption granted labels Oct 9, 2018
@nyalldawson nyalldawson added this to the 3.4.0 milestone Oct 9, 2018
public:
enum ResolutionMethod
{
NoChange,
AddMissingVertex
};
Q_ENUM( ResolutionMethod )
Copy link
Member

Choose a reason for hiding this comment

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

just thinking we could use a template for collect/fix errors that would take the resolution method enums?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess the code for Q_GADGET, Q_ENUM etc. cannot be put into a template, and a template with only half the code is not soooo nice.


if ( mCurrentLayer )
{
disconnect( mCurrentLayer, &QgsVectorLayer::editingStarted, this, &QgsGeometryValidationDock::onLayerEditingStatusChanged );
Copy link
Member

Choose a reason for hiding this comment

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

I think this is causing a crash when reloading project.
I guess we need to remove the connection when the layer is removed from the layer tree?
Or can we use some smart pointer here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you have a stack trace?

Copy link
Member Author

Choose a reason for hiding this comment

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

Latest commit should have changed that anyway

Copy link
Member

Choose a reason for hiding this comment

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

it was on a current layer changed from the layer tree, don't have it anymore.
just out of curiosity, why can't we use a scoped pointer here?

Copy link
Member Author

Choose a reason for hiding this comment

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

A QPointer would probably work as well

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 16, 2018

Thanks a lot for the reviews @nyalldawson and @3nids !

@m-kuhn m-kuhn merged commit 190f938 into qgis:master Oct 16, 2018
@m-kuhn m-kuhn deleted the geometryValidatorCode_1 branch October 16, 2018 09:34
@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 16, 2018

So, let's give this a test drive in the wild. Any feedback much welcome!!

@agiudiceandrea
Copy link
Contributor

agiudiceandrea commented Oct 24, 2018

@m-kuhn I'm experiencing (on W7 64 bit) a strange behaviour upon startup: a "Geometry Validation" ghost window appears and suddenly disappears (see in the red box)

DSD!

image

@nirvn
Copy link
Contributor

nirvn commented Oct 25, 2018

Ah, I was wondering what caused this regression, I see it on my machine too.

@agiudiceandrea
Copy link
Contributor

@nirvn in qgis-dev 3.3.0-97 (osgeo4w 64) the ghost window didn't appear, in 3.3.0-100, 101 and 102 it appears

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 25, 2018

I'll have a look

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 25, 2018

Does it happen on every start of QGIS?

What I can reproduce here is, that when I set the dock visible once, close QGIS and reopen it, it will appear at startup and disappear a bit later (in the dock area, but I guess I've put it there once manually, that's why it's not floating here). However, this is a one-time issue and the next time it will not show up until I enable it again manually.

I'm still looking how to avoid that, but I'm not completely sure it's the same issue.

@agiudiceandrea
Copy link
Contributor

Does it happen on every start of QGIS?

Yes. Also with a new profile or deleting the QGIS folder in %appdata%.

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 25, 2018

And also without a new profile?

@agiudiceandrea
Copy link
Contributor

I can confirm the bug in 3.4 too.

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 29, 2018

@agiudiceandrea

And also without a new profile?

@agiudiceandrea
Copy link
Contributor

agiudiceandrea commented Oct 29, 2018

I don't understand exactly your question. Anyway the ghost window appears upon every startup with old profiles or new profiles.
I've filed a bug report https://issues.qgis.org/issues/20245

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 29, 2018

the ghost window appears upon every startup with old profiles

that was the question, thanks

@borysiasty
Copy link
Member

borysiasty commented Oct 29, 2018

@m-kuhn there is also another problem. I can't find the dock on the panels list (neither in Lin nor Win).
Didn't you forget to addDockWidget( Qt::RightDockWidgetArea, mGeometryValidationDock ); ?


EDIT: Sorry, the problem is different. The dock appears at feature insertion, but only if the project is saved. If I just start a fresh project, add a memory layer, setup its validation properties, insert invalid feature and commit, it is quietly saved. If I then save the project, reopen and continue editing, the dock works well.
It's why I thought the dock is somehow not added to the GUI...

EDIT 2: Even if the dock is not necessary to be placed in the panels menu, I can see it in QGIS 3.4 and not in master.

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 29, 2018

Yeah, activating the tests without reloading the project would be really nice.

But you think there's still something to change with adding the dock? Do you see the issue @agiudiceandrea is experiencing too?

@borysiasty
Copy link
Member

borysiasty commented Oct 30, 2018

@m-kuhn the ghost dock seems to be dependant on the window state saved in QSettings. I can see it also on Debian in some user profiles:

  • In a fresh profile, there is no ghost, and the dock is not listed in the menu (what is expected as it's not added yet). But it is listed in the customization menu.

  • In another profile, there is no ghost, and the dock IS listed. Apparently it comes from the settings. So if the reason for not adding it to the dock areas was to not clutter the docks menu, it seems to be pointless: once the dock gets saved into settings, it will be listed anyway. Moreover, I can see advantages of having it listed: first, users are not confused by a disappearing dock. Second, you can always open it manually and e.g test topological issues without saving the layer.

  • In yet another profile, the dock is not listed, but there is the floating ghost for a while (probably until mGeometryValidationDock->close(); is called). Now I added it explicitly to Qt::RightDockWidgetArea, and this profile behaves exactly like the previous one.

So it seems adding the dock solves the problem.

@borysiasty
Copy link
Member

borysiasty commented Oct 30, 2018

@m-kuhn - please note all the observations above were made without your today's commits (especially the early hide instead of late close). Unfortunately I can't test these changes, as I accidentally fixed my only ghost-affected profile in the meantime.

@m-kuhn
Copy link
Member Author

m-kuhn commented Oct 31, 2018

@borysiasty please tell me if you have new insights!

Second, you can always open it manually and e.g test topological issues without saving the layer.

That's not possible (yet?), the layer needs to be configured for this.

@borysiasty
Copy link
Member

borysiasty commented Nov 1, 2018

@borysiasty please tell me if you have new insights!

I didn't manage to call the ghost anymore. Neither before nor after your changes. I'm sorry I've overwritten that affected project. I'm an idiot! ;-)

That's not possible (yet?), the layer needs to be configured for this.

Yes of course. I just meant: without adding the dock, you need to save the layer in order to trigger the topology test and eventually open the dock. Once it's opened, you can trigger the test just by the button. It's just a minor inconsistency.

@jef-n
Copy link
Member

jef-n commented Nov 1, 2018

I'm an idiot! ;-)

Hey, careful, don't insult fellow developers.

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

Successfully merging this pull request may close these issues.

None yet

7 participants