[GIS] How does one modify selected geometry with a function in qgis python console

editingpythonqgis

My example use-case is that I want to redistribute vertices along lines as in:

I've written some functions to do some of the work:

import math

def redistribute_vertices_num(line,num):
    return LineString([line.interpolate(i/float(num),normalized=True) for i in range(num+1)]) 

def redistribute_vertices_dist(line,dist):
    return LineString([line.interpolate(i*float(dist)) for i in range(int(math.ceil(line.length/dist)))])

I would like to apply these functions (or other, modified functions) to the selected objects in the current layer to modify the layer. I'm looking for a general function like this:

edit_layer_selection_geometry(redistribute_vertices_dist,500)
# or 
def my_function(line):
    redistribute_vertices_dist(line,500)
edit_layer_selection_geometry(my_function)

…that might do something like the processing in How can I create a line with three points with Python in QGIS? or How can I switch line direction in QGIS? on the active layer with the user-defined function.

def edit_layer_selection_geometry(myfun, extra_arguments...):
    layer = qgis.utils.iface.mapCanvas().currentLayer()
    # check for selected_layer, editability ?
    for feature in layer.selectedFeatures():
        newgeom=myfun(feature,extra_arguments)
        # update feature with geometry
        layer.changeGeometry(feature.id(),newgeom)
    ...
    # update layer stuff (extents?)

I'm looking for the edit_layer_selection_geometry() or an equivalent functionality, and I'm happy to modify the potential 'myfun' functions to fit requirements of the general function.

I've put this in my .qgis2/mycode.py directory:

from qgis.core import (QgsFeature, QgsGeometry,
                       QgsVectorLayer, QgsMapLayerRegistry,
                       QgsField)
from PyQt4.QtCore import QVariant
from qgis.utils import iface
import math



def redistribute_geometry_vertices(geom,dist):
    mylen=geom.length()
    line=geom.asPolyline()
    return QgsGeometry.fromPolyline([geom.interpolate(i*float(dist)).asPoint() for i in range(int(math.ceil(mylen/dist)))])

def redistribute_selection(dist):
    """Redistribute the vertices along a polyline """
    layer = iface.mapCanvas().currentLayer()
    for feature in layer.selectedFeatures():
        geom = feature.geometry()
        newgeom=redistribute_geometry_vertices(geom,dist)
        layer.changeGeometry(feature.id(),newgeom)

Then I can solve the use-case by selecting the layer and feature, opening the python console and typing:

import mycode
mycode.redistribute_selection(0.1)

Best Answer

I modified the redistribute_selection() function above to make ~/.qgis2/python/mytools.py :

from qgis.core import (QgsFeature, QgsGeometry,
                       QgsVectorLayer, QgsMapLayerRegistry,
                       QgsField)
from PyQt4.QtCore import QVariant
from qgis.utils import iface
import math
import numpy

def redistribute_geometry_vertices(geom,dist):
    mylen=geom.length()
    line=geom.asPolyline()
    return QgsGeometry.fromPolyline([geom.interpolate(i).asPoint() for i in numpy.arange(0,mylen+dist,dist)])

def redistribute_geometry_vertices_num(geom,num):
    mylen=geom.length()
    line=geom.asPolyline()
    return QgsGeometry.fromPolyline([geom.interpolate(i).asPoint() for i in numpy.linspace(0,mylen,num)])



def reverseGeometryVertices(geom):
    nodes = geom.asPolyline()
    nodes.reverse() 
    return  QgsGeometry.fromPolyline(nodes)

def selection_update_geometry(userFunction, *args):
    """Redistribute the vertices along a polyline """
    layer = iface.mapCanvas().currentLayer()
    for feature in layer.selectedFeatures():
        geom = feature.geometry()
        if geom.isMultipart():
            newgeom = QgsGeometry.fromMultiPolyline([userFunction(part,*args).asPolyline() for part in geom.asGeometryCollection()])
        else:
            newgeom=userFunction(geom,*args)
        layer.changeGeometry(feature.id(),newgeom)
        iface.mapCanvas().refresh()

Then I can type:

import mytools
mytools.selection_update_geometry(mytools.redistribute_geometry_vertices,.05)
mytools.selection_update_geometry(mytools.redistribute_geometry_vertices_num,5)
mytools.selection_update_geometry(mytools.reverseGeometryNodes)

enter image description here

There isn't any error handling in these functions (like checking if a layer is selected, editable, that the feature is a line, or that the user-supplied function returns compatible geometry) but they make it fairly easy to modify the geometry.

Related Question