TransWikia.com

Wavy Edge on polygon in QGIS using geometry generators

Geographic Information Systems Asked on October 5, 2021

I would like to have wavy edges on a polygon similar to what @christoph shows in this answer for wavy lines.
https://gis.stackexchange.com/a/354873/94350
I have gotten his great python expression function working well on lines but have been unsuccessful adapting it to polygons.

from qgis.core import qgsfunction,QgsExpressionContextUtils,QgsExpression,QgsProject,QgsPoint,QgsGeometry
@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def make_zigzagline(geom,dist,offset,feature,parent):
    """
    <style>
    span { color: red }

    </style>
    <h2>converts a linestring to a zig-zag line</h2>

    make_zigzagline(<span>geometry</span>,<span>distance(s)</span>,<span>offset</span>)<br/>

    <table>
        <tr><td><span>geometry</span></td><td>linestring geometry</td></tr>
        <tr><td><span>distance(s)</span></td><td>linear point distances (single number or a string of comma separated numbers)</td></tr>
        <tr><td><span>offset</span></td><td>perpendicular offset</td></tr>
    </table>
    <br/><br/>
    Examples:
    <ul>
        <li>make_zigzagline($geometry,'15,30',15) -> zig-zag line</li>
        <li>make_zigzagline($geometry,15,15) -> zig-zag line</li>
    </ul>

    Use smooth function to create wavelines:<br/><br/>
    Example:
    <ul><li>smooth(make_zigzagline($geometry,'15,30',15),3)</li></ul>
    """

    if not type(dist) is str:
        dist = str(dist)

    dist = [float(n) for n in dist.split(',')]
    l = geom.length()
    dist_sum = 0
    distances = []
    while dist_sum + round(sum(dist),2) < l:
        for d in dist:
            dist_sum += d
            distances.append(dist_sum)

    # interpolate points on linestring
    points2d = [(lambda g: (g.x(), g.y()))(geom.interpolate(d).asPoint()) for d in distances]
    vertices = geom.asPolyline()
    start = (vertices[0].x(),vertices[0].y())
    end = (vertices[-1].x(),vertices[-1].y())

    points2d.insert(0,start) # prepend start point
    points = [QgsPoint(start[0],start[1])]
    i = 0
    n = 0
    b = -90
    for point in points2d[1:]:
        pt1 = QgsPoint(points2d[i][0],points2d[i][1])
        pt2 = QgsPoint(point[0],point[1])
        a = pt1.azimuth(pt2) + b
        pt = pt2.project(offset, a)
        points.append(pt)
        i += 1
        n += 1
        if n == len(dist):
            n = 0
            b = -b

    points.append(QgsPoint(end[0],end[1])) # append end point
    return QgsGeometry.fromPolyline(points)

As a bonus I would like to be able to vary the size of each wave to create something like this:
wavy edge

2 Answers

And here's another quick and dirty solution for custom line styles with even more styling options (COVID-19 aware!):

test1: smooth(make_polygon(customLineStyle( exterior_ring($geometry), '20 80,60 -40,40 40,40 -8,40 30,40 -40,20 -40')),5)
test2,test3: smooth(make_polygon(customLineStyle( exterior_ring($geometry), '200 0,0.1 150,-50 150,0.1 200,150 200,0.1 150,-50 150,0.1 0,100 0')),5)

enter image description here

import itertools, math
from qgis.core import qgsfunction,QgsPoint,QgsGeometry
@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def customLineStyle(geom,param,feature,parent):
    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)

And YES, there's still much room for improvements! ... i.e. storing the calculated geometries in a global dictionary for faster retrievement, support for interior rings, etc...

Correct answer by christoph on October 5, 2021

I think I figured it out. In the geometry generator for your polygon layer set the type to Polygon/Multiploygon and use this expression.

make_polygon(smooth(make_zigzagline_rand( boundary( $geometry),1.5,.1,3),6))

In order for this function to work you need to define a custom function called make_zigzagline_rand using this code that I modified from @christoph answer for wavy lines https://gis.stackexchange.com/a/354873/94350 to make the wave offset varied instead of static.

import random
from qgis.core import qgsfunction,QgsExpressionContextUtils,QgsExpression,QgsProject,QgsPoint,QgsGeometry

@qgsfunction(args='auto', group='Custom', usesGeometry=False, referencedColumns=[])
def make_zigzagline_rand(geom,dist,min_offset,max_offset,feature,parent):
"""
<style>
span { color: red }

</style>
<h2>converts a linestring to a zig-zag line</h2>

make_zigzagline(<span>geometry</span>,<span>distance(s)</span>,<span>min_offset</span>,<span>max_offset</span>)<br/>

<table>
    <tr><td><span>geometry</span></td><td>linestring geometry</td></tr>
    <tr><td><span>distance(s)</span></td><td>linear point distances (single number or a string of comma separated numbers)</td></tr>
    <tr><td><span>min_offset</span></td><td>minimun perpendicular offset</td></tr>
    <tr><td><span>max_offset</span></td><td>maximum perpendicular offset</td></tr>
</table>
<br/><br/>
Examples:
<ul>
    <li>make_zigzagline_rand($geometry,'15,30',5,15) -> zig-zag line</li>
    <li>make_zigzagline_rand($geometry,15,5,15) -> zig-zag line</li>
</ul>

Use smooth function to create wavelines:<br/><br/>
Example:
<ul><li>smooth(make_zigzagline_rand($geometry,'15,30',5,15),3)</li></ul>
"""

if not type(dist) is str:
    dist = str(dist)

dist = [float(n) for n in dist.split(',')]
l = geom.length()
dist_sum = 0
distances = []
while dist_sum + round(sum(dist),2) < l:
    for d in dist:
        dist_sum += d
        distances.append(dist_sum)

# interpolate points on linestring
points2d = [(lambda g: (g.x(), g.y()))(geom.interpolate(d).asPoint()) for d in distances]
vertices = geom.asPolyline()
start = (vertices[0].x(),vertices[0].y())
end = (vertices[-1].x(),vertices[-1].y())

points2d.insert(0,start) # prepend start point
points = [QgsPoint(start[0],start[1])]
i = 0
n = 0
b = -90
for point in points2d[1:]:
    pt1 = QgsPoint(points2d[i][0],points2d[i][1])
    pt2 = QgsPoint(point[0],point[1])
    a = pt1.azimuth(pt2) + b
    pt = pt2.project(random.uniform(min_offset,max_offset), a)
    points.append(pt)
    i += 1
    n += 1
    if n == len(dist):
        n = 0
        b = -b

points.append(QgsPoint(end[0],end[1])) # append end point
return QgsGeometry.fromPolyline(points)

This will give you this output. QGIS random wavy line edge function One issue I have found is that if your geometry has interior rings (holes) the expression will not work. Using exterior_ring instead of boundary in the expression will fill in the interior ring instead of making the entire feature disappear.

Answered by Baswein on October 5, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP