8

I am looking for a PyQGIS 3 method for reversing the direction of a MultiLineString feature. If there is more than one part, the order of the parts should be reversed.

I have found a similar question - Switching line direction in QGIS - for LineString/Polyline - which only works for QGIS 2 - and modified it slightly for use in QGIS 3.

However, I can't work out how to adapt the approach for MultiLineStrings.

from qgis.utils import iface
from qgis.core import QgsGeometry

layer = qgis.utils.iface.mapCanvas().currentLayer() feature = layer.getFeature(1) # feature id geom = feature.geometry() nodes = geom.asPolyline() nodes.reverse() newgeom = QgsGeometry.fromPolylineXY(nodes) layer.startEditing() layer.changeGeometry(feature.id(), newgeom) layer.commitChanges()

In theory, the above approach could be used with

newgeom = QgsGeometry.fromMultiPolylineXY(<QgsMultiPolylineXY>)

but I can't see how to generate the necessary QgsMultiPolylineXY object.

I have also noted the reversed() function on the QGSMultiLineCurve class, but I couldn't attach the generated GSMultiLineCurve to the original feature.

I'm not interested in UI-based approaches, as the features to be reversed are determined programmatically.

Taras
  • 32,823
  • 4
  • 66
  • 137
Tom Brennan
  • 4,787
  • 6
  • 26

1 Answers1

11

I will focus on the geometry creation part, because the feature editing part is fully explained in many places, including the example inside the question.

I will use some Python list comprehension, but the key here is the use of the QgsAbstractGeometry type QgsMultiLineString, which is iterable and converts easily from/to list type.

From the QGIS API, you will see that each QgsGeometry is a container for the "real" QgsAbstractGeometry:

A geometry is the spatial representation of a feature. Since QGIS 2.10, QgsGeometry acts as a generic container for geometry objects. QgsGeometry is implicitly shared, so making copies of geometries is inexpensive. The geometry container class can also be stored inside a QVariant object.

The actual geometry representation is stored as a QgsAbstractGeometry within the container, and can be accessed via the get() method or set using the set() method.

#First, will create an example multilinestring geometry
lines = [[[0,0],[1,1],[2,2]],[[0,1],[1,2],[2,3]],[[0,2],[1,3],[2,4]]]
mls = QgsMultiLineString()
for i in [QgsLineString([QgsPoint(x,y) for x,y in i]) for i in lines]:
    _ = mls.addGeometry(i)

old_geometry = QgsGeometry(mls)

QgsGeometry can be converted to QgsAbstractGeometry with the

.get() method, so the real example begins HERE:

mls1 = old_geometry.get() mls2 = QgsMultiLineString()

For each reversed linestring, visited in reverse order

for i in [QgsLineString([*i][::-1]) for i in [*mls1][::-1]]: _ = mls2.addGeometry(i) # add it to new geometry

new_geometry = QgsGeometry(mls2) old_geometry #[out]: <QgsGeometry: MultiLineString ((0 0, 1 1, 2 2),(0 1, 1 2, 2 3),(0 2, 1 3, 2 4))> new_geometry #[out]: <QgsGeometry: MultiLineString ((2 4, 1 3, 0 2),(2 3, 1 2, 0 1),(2 2, 1 1, 0 0))>

Note here, that QgsMultiLineString objects are iterable so, you can expand it to a list of LineStrings using [*list] Python syntax.

In the developer forum, they say it is recommended to use QgsMultiLineString, QgsLineString, QgsPoint,... etc when creating geometries, one advantage is the easy conversion from/to list conversion.

One thing to be aware of is that if you are using existing features, rather than a newly created one as per the example above, don't try to shortcut the "cloning" process. Given a feature f, use, for example:

geom = f.geometry()
mls1 = geom.get()

rather than

mls1 = f.geometry().get()

Otherwise, you will get a crash due to Python garbage collectin.

Taras
  • 32,823
  • 4
  • 66
  • 137
Javier JC
  • 882
  • 6
  • 10