[QGIS-Developer] Geometry operations in Python

Nyall Dawson nyall.dawson at gmail.com
Wed Jan 24 15:34:39 PST 2018


On 24 January 2018 at 20:11, Daan Goedkoop <dgoedkoop at gmx.net> wrote:

> It sometimes needs to reverse line directions. I used to do it like this:
>
> return QgsGeometry.fromPolyline(geom.asPolyline().reverse())
>
> Q1. Do I understand it correctly, that fromPolyline returns a
> QgsPointXY list, thus destroying any M/Z values?

Yes. Avoid these as much as possible. asPolyline() is also quite slow,
so that's another good reason to avoid it.

>
> What I do now, and what seems to work, is this:
>
> return QgsGeometry(geom.constGet().reversed())
>
> Q2. Is this the preferred way?

Yes. It's very fast, will work for Circular Strings and Compound
Curves, and will maintain Z/M values.

> Q3. Is it better to use constGet() or normal get()?

Use constGet() wherever possible (i.e. you're not modifying the
returned value). The rationale here is:

- QgsGeometry objects are implicitly shared, so copying them is very
fast and performs only a shallow copy.
- Calling constGet() on a QgsGeometry returns the underlying geometry
primitive without forcing a slow "detach" (deep copy) of the geometry.
So use that if you can.
- Call get() only when you need to modify the geometry primitive, e.g.
geometry.get().transform( my_coord_transform ). It'll force the deep
copy, but you'll need to do this anyway since you're modifying the
copy.

> The second issue are multi-part geometries. I have tried something
> like this, after selecting a simple line in a .shp layer:
>
>>> qgis.utils.iface.activeLayer().selectedFeatures()[0].geometry().constGet().wkbType()
> 5 (note: multipart line. I don't understand why, but ok)

It's probably a MultiLineString layer type.

>>> qgis.utils.iface.activeLayer().selectedFeatures()[0].geometry().constGet().numGeometries()
> 0
>>> qgis.utils.iface.activeLayer().selectedFeatures()[0].geometry().constGet().geometryN(0)
> <crashes QGIS entirely>

I *think* you're unlucky here and that the feature's geometry is a
multi line string consisting of 0 parts. That's why numGeometries()
returns 0, and geometryN( 0 ) tries to access an out-of-range object
and crashes (which we should fix and throw a Python exception
instead).

Try calling .asWkt() on your geometry to see what it actually is.

> So apparently this doesn't work. So now I use an approach like this:
>
> list = qgis.utils.iface.activeLayer().selectedFeatures()[0].geometry().asGeometryCollection()
> mls = QgsMultiLineString()
> for line in list:
>    mls.addGeometry(line.constGet().reversed())
> newgeom = QgsGeometry(mls)
>
> Q4. Is this the recommended way of doing things?

Yes  - looks good to me. asGeometryCollection() is nice and efficient,
and won't lose curves or Z/M types. I'm not sure if it's any quicker
or any more readable, but a slightly different approach would be:

list = qgis.utils.iface.activeLayer().selectedFeatures()[0].geometry().asGeometryCollection()
reversed_lines = []
for line in list:
   reversed_lines .append(QgsGeometry(line.constGet().reversed())
newgeom = QgsGeometry.collectGeometry(reversed_lines )

(QgsGeometry.collectGeometry() is nice and fast too)

> Can someone shed a light on this?

Hopefully that does!

Nyall


More information about the QGIS-Developer mailing list