-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Add a distanceWithin method to the QgsGeometryEngine virtual class #45057
Conversation
@@ -184,6 +184,13 @@ class CORE_EXPORT QgsGeometryEngine | |||
*/ | |||
virtual double distance( const QgsAbstractGeometry *geom, QString *errorMsg = nullptr ) const = 0; | |||
|
|||
/** | |||
* Checks if \geom is within \maxdistance distance from this * geometry |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* Checks if \geom is within \maxdistance distance from this * geometry | |
* Checks if \a geom is within \a maxdistance distance from this geometry |
/** | ||
* Checks if \geom is within \maxdistance distance from this * geometry | ||
* | ||
* \since QGIS 3.21 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* \since QGIS 3.21 | |
* \since QGIS 3.22 |
@@ -903,7 +903,7 @@ bool QgsVectorLayerFeatureIterator::postProcessFeature( QgsFeature &feature ) | |||
|
|||
if ( result && mDistanceWithinEngine && feature.hasGeometry() ) | |||
{ | |||
result = mDistanceWithinEngine->distance( feature.geometry().constGet() ) <= mDistanceWithin; | |||
result = mDistanceWithinEngine->distanceWithin( feature.geometry().constGet(), mDistanceWithin ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I admit it's more readable than before, but is that also optimized somehow compared with the previous version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the intention is that optimisations will be added in a follow up (potentially by calling a newer GEOS method)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly
1dcd7da
to
5e887f5
Compare
Accepted suggested correction and rebased against current master branch |
5e887f5
to
3f3750a
Compare
@nyalldawson looking at the "Extract Within Distance" processing algorithm I've found the QgsGeos::prepareGeometry() function called 1550 times when asked to extract, from a layer of 331 polygons, all the ones within 10 units distance from a layer having a single geometry. I would have expected at most 332 "prepare" calls, but got 1550. That's about 5 times as many calls to prepare than expected. I suspect the speed problem has simpler solutions than implementing a proper "DistanceWithin" method... |
With further debugging I've found that extracting features from a 310 features layer being within X distance from a 1 feature layer results in 310 initializations of the VectorLayerFeatureIterator (one iterator per feature?). We get a single iterator if we extract features from the 1-feature layer being within X distance from the 310 features layer. I'd expect the contrary being true. Anyway, there are definitely multiple preparations of the same geometry, from the logs:
You can see geometries tend to be prepared even 4-5 times each! |
With The second preparation is again coming from Third time preparation of SAME polygon comes again from an assignment operator (of QgsFeatureRequest). Fourth preparation comes from Fifth preparation comes from |
Is there a reason why |
I pushed a commit turning cacheGeos and prepareGeometry methods idempotent, to reduce the preparations. |
I moved the idempotent GEOS caches in its own PR: #45147 |
a02045d
to
be2f681
Compare
Unfortunately the idempotent GEOS preparation methods do not reduce the number of preparations of the same geometries, |
Now backtraces with debug symbols: First geometry preparation:
Second prepare geometry call:
What's happening is that the QgsFeatureRequest assignment operator is invoked by |
Right -- this is a potential optimisation which we should consider. Right now if you create a geometry engine from a QgsGeometry object you'll always get a new QgsGeos object, even though QgsGeometry objects themselves are implicitly shared and only invoke shallow copies (sharing the same underlying QgsGeometryPrivate object). Like you've found, this means that we often convert the exact same QgsGeometryPrivate objects to QgsGeos multiple times, and prepare each of these separately before discarding the results. We should consider whether it's worth storing the QgsGeos representation inside QgsGeometryPrivate instead, so that we avoid the cost of multiple QGIS -> GEOS conversions and then everyone benefits from the first time this geometry is prepared. There's a few steps to this:
and ALL other code paths using this same feature would already have a prepared geometry ready to go. This would lead to HUGE speed ups across the board in QGIS, especially for geometry-based QGIS expressions, aggregates, etc. Interested to hear your thoughts on this! |
And use it from QgsVectorLayerFeatureIterator References qgis#472 The current implementation is really just a wrapper around distance() but opens the door for future improvements
be2f681
to
f82507e
Compare
@nyalldawson your solution sounds good to me, although I'm still not clear about the interaction between AbstractGeometry and Geometry and GeometryEngine at this stage. How about merging this PR and starting the refactoring work in another ? I'm thinking that allowing non-repreparing copies of GeometryEngine could be a lighter change for the time being, which would mean using shared pointers instead of unique pointers for the geosGeom member |
@strk sure, done! I'm going to file a bug report collecting information on this, and I'll attach the datasets I was having issues with... Maybe there's something unique about them which will give further clues as to the poor performance |
@nyalldawson that's perfect, looking forward for your dataset ! Meanwhile I'll continue with my little test to see how number of preparation can be reduced with shared_ptr |
References #472