# Sorting out curved and straight lines in QGIS [closed]

I have a layer in QGIS with thousands of lines and I would need to sort out the curved lines and lines that are straight. Can I do this easily in QGIS?

• Can you add a screenshot? What is your definition of straight and curved?
– BERA
May 27, 2021 at 6:51
• Basically every line, that has two nodes, must be straight, while all the other most likely are not... so, you could simply look for the number of nodes/vertices. Or you could add a screenshot of your data.
– Erik
May 27, 2021 at 7:15
• GIS vector data is not "curved" in a strict sense (like circle), its always vertex-based. So any complex line consists of small, straight segments. "Curved" in this context can only mean a certain definition of how two or more connected segments relate to each other - like angle/length. So you must provide a definition of what you consider to be "curved" for your purpose. May 27, 2021 at 7:32
• @Erik There can be several points on a straight line, so the presence of three vertices is not always a sign of a curved line. May 27, 2021 at 7:37

1. Use the simplify tool to remove unnecessary vertices that lie on a straight line as described in this answer.

2. Using the field calculator, create a new field of type `text` with the following expression:

if( num_points( nodes_to_points(\$geometry, True) )> 2, 'Curved', 'Straight' )

You can use pyqgis and compare each line's length to the distance between start and end points: ``````lyr = QgsProject.instance().mapLayersByName('Single parts')

straight = [] #A list to hold feature ids of straight lines
curved = []

for f in lyr.getFeatures():
line = f.geometry()
linelength = line.length()
vertices = [v for v in line.vertices()]
shortest_line = QgsLineString(vertices, vertices[-1])

if linelength > 1.03 * shortest_line.length(): #If the line length is longer than the shortest line + 3 % then it's curved. (I just chose 3 % subjectively)
curved.append(f.id()) #Append id to list
else:
straight.append(f.id())

lyr.select(straight)
`````` Given a curved line, you can apply the following expression to identify and visualize those sub-sections of the line that are (either absolute or more or less) straight; or: which ones are, to the contrary, the curved ones. Both "straight" or "curved" can be defined freely based on two parameters: `dist` and `tolerance`:

• `dist` sets a distance. The line is than segmented in segments of this length
• `tolerance` is based on the "straigtness"/"curvyness" of each of these segments.

Identify straight segements (red), generated with a length of 100 [meters] and tolerance of `<5`. The line is segmented in 100 m-segments. Only those segments are drawn in red if their accumulated difference of angles for 10 evenly distributed points in this segment is less than 5: Highlight strongly curved segments with settings dist: `70`, tolerance: `>5`: Identify spots where the line turns to a susbtantially new direction with settings dist: `150`, tolerance: `>60`: Using this method to identify curves (red) of a road on an OpenStreetMap basemap with dist: `90`, tolerance: `>100` "Counting" u-turn like curves with dist: `20` and tolerance: `>100`: On the line layer, use this expression with geometry generator and adapt values for `dist` in line 3 (change the value of `150)` and `tolerance` on line 6 (change `>30` and keep the single quotes `'`). If satisfied with the result, you can create actual geometries form it using Geometry by expression (see below for a slightly simplified variant):

``````with_variable(
'dist',
150,
with_variable (
'tolerance',
'>30',
collect_geometries (
array_foreach (
generate_series (0, length(\$geometry)/@dist),
if (
eval (
(abs((line_interpolate_angle( \$geometry, @element*@dist + 0)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.1)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.1)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.2)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.2)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.3)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.3)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.4)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.4)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.5)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.5)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.6)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.6)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.7)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.7)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.8)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.8)-line_interpolate_angle( \$geometry, @element*@dist +@dist*0.9)))+
abs((line_interpolate_angle( \$geometry, @element*@dist +@dist*0.9)-line_interpolate_angle( \$geometry, @element*@dist +@dist))))
|| @tolerance
),
line_substring( \$geometry, @element*@dist, @element*@dist+@dist),
make_line (make_point (0,0),make_point (0,0))
)
)
)
)
)
``````

How it works:

1. Segment the line into segments of a freely selectable length with `array_foreach ()` and `generate_series()`

2. Each segment is sub-divede again in 10 equal parts (lines 12 to 21).

3. At each of these points, the angle (tangent, using `line_interpolate_angle()`) is measured.

4. The angle is compared with the one of the next part: subtract angle from the following part from the angle of the current part. If the line here is completely straight, it will be the same, thus the difference is 0. The larger the difference, the more the line deviates from a straight line.

5. Use `abs()` to avoid negative values.

6. Sum up all differences for the 10 parts of each segment.

7. Compare the resulting value with a condition. To be able to use the condition on top of the expression as variable, I had to introduce it afterwards using an `eval()` statement: concatenate the resulting number with the string of the condition.

8. If this condition is true, a geometry will be drawn for the current segment, based on the `line_substring()` function. Otherwise, an empty line is created.

9. With `collect_geometries()`, the array of geometries is transfered to a geometry (line).

Variant

In the expression above, there is the repeating part (lines 12-21) starting with `abs((line_interpolate_angle(...`. To avoid repeating the same string again and again, you can create it as a variable and call it inside the `eval()` function. Create the repeating parts of the these repeating part as a string and concatenate it with the varying @element from an `array_foreach()` function that creates an individual line for each of the 10 sub-segments. The expression looks like this with the additional variable `expression` (lines 7-13) that is called in line 19:

``````with_variable(
'dist',
100,
with_variable (
'tolerance',
'>30',
with_variable(
'expression',
replace (
array_to_string (
array_foreach (
generate_series (0,9),
'abs((line_interpolate_angle( \$geometry, @element *@dist + @dist*'  || (@element/10)  || ' )-line_interpolate_angle( \$geometry,  @element  *@dist +@dist*'  ||  (@element/10+0.1)  || ')))'  || if (@element <9, '+', ''))), '+,','+'),
collect_geometries (
array_foreach (
generate_series (0, length(\$geometry)/@dist),
if (
eval (
(@expression) || @tolerance
),
line_substring( \$geometry, @element*@dist, @element*@dist+@dist),
make_line (make_point (0,0),make_point (0,0))
)
)
)
)
)
)
``````

Remark about the very conpets of "curved" and "straight":

What means "curved" and "straight" depends very much on the definition. Sould a zig-zag line witch angles of 90 degrees (like a stair) be considered curved or straight? Should the **whole line" be considered? Than only a mathematical line would fulfill this condition. Or should we go to identify straight segments that are part of the line? How long should these be?

As this solution and the real-world example (street extracted from OpenStreetMap: Nuova strada del Passo del San Gottardo) show, changing the input values (and thus the definiton), the result changes dramatically.

As the question did not provide any idea about how "curved" or "straight" should be defined in the given context, there are several great solutions here, each with a different approach. So in fact each solution is somehow an answer to a different definition.

This solution here tries to make definition flexible as you can define two parameters to adapt the definition to different needs and visualize the result.

If the curvature is only a true/false statement per feature, it could be resolved into a field value using an expression like:

``````if(round(area(oriented_bbox(\$geometry))) <> 0,'curved','straight')
``````

The oriented bounding box for a curved polyline will have a nonzero area, contrary to straight lines with two or more aligned vertices, as illustrated below (oriented_bbox as symbology and the area of that polygon as label). Create a segment from the first to last vertex and then for each interior point, determine its distance from this segment. Set a threshold. Below the threshold, the line is considered straight. Above the threshold it's curved.

There are probably simpler collinearity measures.