-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
Fix elevation profile on closed polyline (fix #58450) #58949
Conversation
🪟 Windows buildsDownload Windows builds of this PR for testing. 🪟 Windows Qt6 buildsDownload Windows Qt6 builds of this PR for testing. |
12b229f
to
939ea06
Compare
|
||
for ( QgsLineString &part : profileLine->splitToDisjointXYParts() ) | ||
{ | ||
mProfileCurve.reset( &part ); |
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.
This is rather ugly -- we are assigning a unique_ptr a value which it does not have ownership on. I would suggest instead doing something like:
QVector< QgsLineString * > parts = profileLine->splitToDisjointXYParts();
while ( !parts.isEmpty() )
{
mProfileCurve.reset( parts.takeFirst() );
(This will require modifying splitToDisjointXYParts to return pointers in the list, which is arguably more flexible anyway)
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.
It is ugly. I didn't want to have splitToDisjointXYParts()
return a list of pointers, to avoid allocations.
Returning a list of unique_ptr
doesn't work with QVector
(it copies elements on mutation), so I switched to std::vector
. Sadly now SIP complains that "splitToDisjointXYParts has an unsupported return type", so I've excluded it from the Python bindings for now.
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.
It is ugly. I didn't want to have splitToDisjointXYParts() return a list of pointers, to avoid allocations.
Trust me -- In this case, you'll actually end up with an API which AVOIDS more allocations by returning pointers here. This is because of the mix of geometry classes in QGIS, and the different ways these are used. There's many classes which require QgsGeometry objects (or methods ONLY available from QgsGeometry), so we end up having to create QgsGeometry objects taking ownership of QgsAbstractGeometry in order to call these. A prime example would be something like QgsFeature -- in order to set the geometry for a feature, we need a QgsGeometry value, and that can ONLY be created with a heap allocated QgsAbstractGeometry.
So by returning values from a method like splitToDisjointXYParts
we'll end up having to clone those QgsAbstractGeometry values in order to transfer them to a QgsGeometry... which means we're heap allocating anyway PLUS incurring the cost of the clone.
I agree it's not ideal, but we're dealing with a bunch of constraints here which we just have to live with, eg
- Qt API isn't designed around modern c++ memory handling, and FORCES us to use a lot of raw pointers and manual memory management to fit in with their parent/child object model
- The SIP Python binding generator has NO concept of modern c++, and FORCES us to use raw pointers and manual memory management if we want to expose methods to PyQGIS
- We have a stable API constraint forcing us to live with the API decisions from back in the 3.0 era, so we're even constrained by QGIS API itself 🙀
I would LOVE to be able to use unique_ptr throughout our API in order to modernise things, but the sad reality of QGIS development is that we just CAN'T do this right now.
Returning a list of unique_ptr doesn't work with QVector (it copies elements on mutation), so I switched to std::vector. Sadly now SIP complains that "splitToDisjointXYParts has an unsupported return type", so I've excluded it from the Python bindings for now.
As per my comments above, this is something you just need to learn to compromise on, and hold back the vomit 😉 and expose as an unsafe QList< pointer *>
return value (along with the SIP_FACTORY
annotation to indicate that the caller takes ownership of the objects).
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.
Thanks for your rationale on the allocations, that makes sense.
I really don't like returning raw pointers, and lists of them are even worse. But if you think it's the only way for now, I'll change the code. Hopefully one day SIP will support smart pointers and we can refactor at least some of the ugliness away.
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.
Thanks for the flexibility!
Hopefully one day SIP will support smart pointers and we can refactor at least some of the ugliness away.
It's possible we could add support for std::vector< std::unique_ptr< T > >
return types via some new conversion code in https://github.com/qgis/QGIS/blob/master/python/core/conversions.sip, but AFAIK no-one has attempted that yet...
f100174
to
90c9fa3
Compare
f0a20e4
to
26d6b6d
Compare
Description
Currently, the elevation profile code for vector layers (i.e. polylines) goes through each part of the target curve and uses
lineLocatePoint(QgsPoint)
to see how far a point is on the curve. This, however, can't work for closed curves (or in general curves which use a single point multiple times), since there are multiple answers for "how far along the curve is this point?".This PR fixes the problem by splitting the profile curve into multiple segments which don't self-intersect, and processing each independently. This should fix the issue for all affected polylines while having no effect for previously-working inputs.
Fixes #58450