21

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:

enter image description here

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
  • 1
    @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. – Kazuhito 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
18

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)
            dist_x, dist_y = ((real_step*sin(rad_crv_angle))* cos(round_angle), (real_step*sin(rad_crv_angle)) * sin(round_angle))
            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))
        prov.addFeatures([fet])

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

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

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

# Add the layer to the Layers panel
QgsMapLayerRegistry.instance().addMapLayer(outLayer)

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

enter image description here

  • 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). – Kazuhito May 27 '17 at 5:20
  • 1
    @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
  • 1
    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. – Kazuhito May 27 '17 at 17:53
  • 1
    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
  • 1
    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)! – Kazuhito May 28 '17 at 1:59
20

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

Long answer:

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. enter image description here

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.

enter image description here

enter image description here

enter image description here

enter image description here

** 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.

enter image description here

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"
 xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
<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"... :\ – Kazuhito 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. – Kazuhito 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. – Kazuhito May 26 '17 at 15:22
  • @Kazuhito - You can change the Pen style to No Pen :) – Joseph May 26 '17 at 15:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.