# Drawing wavy/wiggly lines in QGIS

Is there a QGIS function or plugin to draw wiggly line?

I have used "Spline Tool" to manually draw some waves, but it is time consuming.

If possible, I would like to draw something like:

Inkscape `Function Plotter` (`sin(x)` curve).

• Really interesting question! Are you thinking about a tool for instantly drawing the lines (i.e. like when you draw a simple line feature using the mouse), or maybe a way to get this result starting from coordinates as input (eventually from points, lines or polygons)?
– mgri
May 26 '17 at 12:39
• @mgri Thanks for your comment. I expect to use this line to mark a boundary with some uncertainties (like a fluctuating shoreline), so primary idea was to convert a polyline (through pre-defined coordinates) into wiggles. But the idea of instantly drawing this type of line does sound attractive, too. May 26 '17 at 13:04
• Please, see if my solution helps. I didn't test it widely, so please let me know if something goes wrong (I can't do it right now, but I will probably edit my answer with more information).
– mgri
May 26 '17 at 18:35

I propose a solution using PyQGIS. It should work both for Linestring and MultiLineString layers.

This solution is based on the creation of semicircular rings, so you need to set a value for the diameter (i.e. the `step` variable in the code below). The step you choose won't be the real step used because it is adjusted on the basis of the line length (but it would be really similar to the value initially set). You need to do some attempts before finding the best value for the `step` variable.

The code also requires a second (optional) parameter (called `crv_angle`), which helps for decreasing or increasing the curvature for the rings (I performed a few tests for it, so I suggest leaving 45 degrees as default angle since it would lead to real circular rings).

You only need to run this code from the Python Console:

``````from math import sin, cos, radians

step = 3 # choose the proper value (e.g. meters or degrees) with reference to the CRS used
crv_angle = 45 # degrees

def segment(polyline):
for x in range(0, len(polyline) - 1):
first_point = polyline[x]
second_point = polyline[x +1]
seg = QgsGeometry.fromPolyline([first_point, second_point])
tmp_azim = first_point.azimuth(second_point)
len_feat = seg.length()
parts = int(len_feat/step)
real_step = len_feat/parts # this is the real step applied

points = []
current = 0
up = True

while current < len_feat:
if up:
round_angle = radians(90 - (tmp_azim - crv_angle))
up = False
else:
round_angle = radians(90 - (tmp_azim + crv_angle))
up = True
first = seg.interpolate(current)
coord_x, coord_y = (first.asPoint().x(), first.asPoint().y())
p1=QgsPointV2(coord_x, coord_y)
p2 = QgsPointV2(coord_x + dist_x, coord_y + dist_y)
points.extend([p1, p2])
current += real_step

second = seg.interpolate(current + real_step)
p3=QgsPointV2(second.asPoint().x(), second.asPoint().y())
points.append(p3)

circularRing = QgsCircularStringV2()
circularRing.setPoints(points) # set points for circular rings
fet = QgsFeature()
fet.setGeometry(QgsGeometry(circularRing))

layer = iface.activeLayer() # load the input layer as you want
crs = layer.crs().toWkt()

# Create the output layer
outLayer = QgsVectorLayer('Linestring?crs='+ crs, 'wiggly_line' , 'memory')
prov = outLayer.dataProvider()
fields = layer.pendingFields()
outLayer.updateFields()

for feat in layer.getFeatures():
geom = feat.geometry()
polyline = geom.asPolyline()
segment(polyline)

# Add the layer to the Layers panel
``````

and it will create a new line memory layer with the expected result:

• Wow! I cannot stop playing with this, so gorgeous. On top of it the output itself can be used for further operations like buffer. Thank you @mgri. One last thing, I sometimes see small circles appear at or near vertices. Is this avoidable? I can remove them by erasing the center node of such circle by Node Tool (so it is not a big deal). May 27 '17 at 5:20
• @Kazuhito please, see my edited code. It seems there was something wrong with the discretization and I hope it is fixed by now. I performed several tests and it seems working well (the code is also more readable).
– mgri
May 27 '17 at 14:16
• Thanks so much @mgri. There are very minor circles, for which I am sorry I cannot clearly explain how these appear. Most vertices are now "circle-free". Only distinction I noticed was such node (with circle) had shown a smaller "r-value" on Vertex Editor Table of Node Tool. This is easily manageable and far better than I had anticipated. Thank you again. Let me accept your answer as solution. May 27 '17 at 17:53
• Thanks, @Kazuhito. I'm sorry for having not created a perfect solution. However, I hope you will enjoy it (otherwise, you may also send me a sample shapefile and I will try to fix the issue). If I find a more efficient way, I will post it!
– mgri
May 27 '17 at 22:36
• Many thanks @mgri. If I find any distinctive pattern in appearance of circles I will update you with reproducible example. This is so much enjoyable (and its output is beautiful)! May 28 '17 at 1:59

Short answer: you can get it using a custom SVG. See bottom of this post for one.

I believe it is better to represent it than to modify the line geometry. Should you want to move an edge or do other actions on the geometry, it would be a nightmare to manage if the wiggles are part of the geometry instead of just a representation of a straight line.

You can play with the style marker line. There is a way to easily get close to what you need, and with a bit more effort it is likely possible to get it exactly.

To get this, you would style the line with two Marker lines. Each Marker line is made of a Simple Marker, the half-circle. The 1st one is rotated by 180. Both are set to transparent.

On the Marker line, you instruct one of them to be offset so the two symbols are not drawn in front of each other, but side by side. If you use offest = 1/2 * interval size, the output will be a sinusoidal curve. I suggest you play with the interval size, offset and symbol sizes.

The main limitation with this approach is the diameter line of the half circles, which sums to the original line. If your background is white (or any plain color), you could add a 3rd simple line using the background color.

** EDIT **

Another option to get rid of the center line is to create a new SVG symbol. I modified the half-curve, only living the rounded part. It works, though a 1/2 ellipse might be more appealing. The screenshot was done using symbol size 10, interval 4, offset 2.

save the code below in a file half_circle_line.svg and make sure the path to the svg is set in `QGIS // Settings / Options / System / SVG Paths`

``````<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="11.2889mm" height="11.2889mm"
viewBox="0 0 32 32"
<title>Qt Svg Document</title>
<desc>Generated with Qt</desc>
<defs>
</defs>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >

<g fill="#ffffff" fill-opacity="0" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
font-family="MS Shell Dlg 2" font-size="8.25" font-weight="400" font-style="normal"
>
<path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M19.1181,16 C19.1181,16 19.1181,14.2779 17.7221,12.8819 16,12.8819 C14.2779,12.8819 12.8819,14.2779 12.8819,16"/>
</g>
</g>
</svg>
``````
• Nice idea. Currently struggling with persistent "center line"... :\ May 26 '17 at 14:57
• +1 from me too. In the meanwhile, I'm trying to thinking to a PyQGIS solution. @Kazuhito, please let me know if this would be enough for you or if you prefer a physical solution.
– mgri
May 26 '17 at 14:58
• @mgri With this answer I have a "chain" not "wave" now (trying to modify it). Would really appreciate to have a physical solution. May 26 '17 at 15:05
• JGH Do you potentially have an idea to remove the "center line", other than masking it by a white line (i.e.your bottom figure)? It looks as if it is fragmented. May 26 '17 at 15:22
• @Kazuhito - You can change the `Pen style` to No Pen :) May 26 '17 at 15:27

Attention!

In QGIS 3.10 it's possible to dynamically create zig-zag lines and wavelines with the help of the indispensable "geometry generator" and a custom Python expression function.

``````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)
``````

Load the code in the Python console and style your Linestring layer with a Geometry generator:

• Fantastic! Thanks @christoph Mar 24 '20 at 8:37
• @christoph Thanks for this great solution. I am trying to get it to work with polygons as well and created a new question here gis.stackexchange.com/q/367422/94350 Jul 10 '20 at 22:11
• @christoph, thanks for this very nice solution. However, QGIS 3.14 started to crash every time I tried to open the field calculator. After removing the custom function, the crash stopped. Do you think it is related to QGIS version? Jul 28 '20 at 10:50
• @Nico, did you try my latest version? gis.stackexchange.com/questions/376411/… Nov 30 '20 at 12:26
• @Nico, to create a new layer, you can use the Processing Tool Geometry by expression Nov 30 '20 at 12:44