After my experiences with a custom Python linestyle expression function (Wavy Edge on polygon in QGIS using geometry generators) I modified the code to support Polygons with holes, MultiPolygons and MultiLineStrings as well. See the following picture using the same Geometry generator styling for all layer types:
smooth(customLineStyle( $geometry,'45 30,45 0,45 -30,45 0'),5)
Unfortunately, QGIS crashes with MultiLineStrings in Windows 10 (QGIS 3.14.16-Pi).
I don't know, is it a bug or is there something wrong with my code (see below)?
import itertools, math, re
from qgis.core import qgsfunction,QgsPoint,QgsGeometry,QgsWkbTypes,QgsPointXY
def createCustomLine(geom,param):
geom = QgsGeometry(geom)
dst = geom.length()
vertices = geom.asPolyline()
start = (vertices[0].x(),vertices[0].y())
end = (vertices[-1].x(),vertices[-1].y())
l = [list(map(float,x.split(' '))) for x in param.split(',')]
steps,offsets = zip(l)
d = sum(steps)
newdst = dst / int(dst / d)
newsteps = [x / d newdst for x in steps]
d = sum(newsteps)
cnt = int(dst / d)
distances = list(itertools.chain.from_iterable(itertools.repeat(newsteps,cnt)))[0:-1]
alloffsets = list(itertools.chain.from_iterable(itertools.repeat(offsets,cnt)))
alloffsets.insert(0,0)
distances = list(itertools.accumulate(distances))
points2d = [(lambda g,d: (g.x(), g.y(),d))(geom.interpolate(d).asPoint(),d) for d in distances]
distances.insert(0,0)
points2d.insert(0,start) # prepend start point
points = [QgsPoint(start[0],start[1])]
for i,pt in enumerate(points2d[1:]):
if distances[i+1] > distances[i]:
corrAngle = -90
else:
corrAngle = 90
qgsPt = QgsPoint(pt[0],pt[1])
points.append(qgsPt.project(alloffsets[i+1],QgsPoint(points2d[i][0],points2d[i][1]).azimuth(qgsPt) + corrAngle))
points.append(QgsPoint(end[0],end[1])) # append end point
return QgsGeometry.fromPolyline(points)
def createCustomPolygon(geom,param):
wkt = geom.asWkt()
geometries = [QgsGeometry.fromWkt('LineString(%s)' % g) for g in re.findall('[^()]+',wkt) if g != ','][1:]
geom = geometries[0]
rings = geometries[1:]
geom = createCustomLine(geom,param)
geom = geom.convertToType(QgsWkbTypes.PolygonGeometry)
if rings:
for ring in rings:
ring = createCustomLine(ring,param)
ptArray = [QgsPointXY(pt.x(),pt.y()) for pt in list(ring.vertices())]
geom.addRing(ptArray)
return geom
@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def customLineStyle(geom,param,feature,parent):
rings = None
if geom.wkbType() == QgsWkbTypes.LineString:
newGeom = createCustomLine(geom,param)
elif geom.wkbType() == QgsWkbTypes.MultiLineString:
wkt = "MultiLineString("+",".join([re.findall('^[^(]+(.+)',createCustomLine(part,param).asWkt())[0] for part in geom.parts()])+")"
newGeom = QgsGeometry.fromWkt(wkt)
elif geom.wkbType() == QgsWkbTypes.Polygon:
newGeom = createCustomPolygon(geom,param)
elif geom.wkbType() == QgsWkbTypes.MultiPolygon:
wkt = "MultiPolygon("+",".join([re.findall('^[^(]+(.+)',createCustomPolygon(part,param).asWkt())[0] for part in geom.parts()])+")"
newGeom = QgsGeometry.fromWkt(wkt)
return newGeom

