What you want to achieve is basically a kind of "clustering" of lines: grouping lines that are close together. You need one manual decision, a maximum distance up until which lines should be considered part of the same group: see end of step 2 for details.
This solution has five steps, one step for preparing the data, step 2 as the main step to group together lines close enough and 3 steps "post processing" to finally get a single centerline for each "line-cluster".
- Be aware that to prepare the lines for what follows, all should have the same length - some lines at the bottom right are shorter. Thus first make sure all lines have the same length. To do this, create a new attribute
extend_len
using this expression in the field calculator (in an earlier version, I used extend_length
as fieldname. But as you use shapefiles, the length of you fieldnames is limited to 10, thus the shorter name):
if (
length($geometry) < maximum( length($geometry)),
maximum( length($geometry)) - length($geometry),
0
)
Than run Menu Processing / Toolbox / Geometry by expression
, creating a new line with this expression: extend ($geometry,0, "extend_len" )
:
Now all lines have the same length. Remark: theoretically, instead of using two steps, you should be able to introduce the first expression instead of "extend_len"
in the second expression. However, in this context, QGIS is not able to caclulate the aggregate maximum (length($geometry))
. For this reason, two separate steps are necessary.
- Now duplicate the layer created in step 1 (right click on the layer / duplicate layer). Let's say the original layer is named
original
(rename it), the duplicated layer copy
. Now again run geometry by expression with copy
as input layer
and Output geometry type
as polygon
. Than paste the following expression:
make_rectangle_3points (
start_point ($geometry),
end_point ($geometry),
end_point (
geometry (
get_feature_by_id (
'original',
array_get (
overlay_nearest(
'original',
fid,
limit:=array_length (
overlay_nearest(
'original',
fid,
limit:=200,
max_distance:=0.7
)
)
),array_length (
overlay_nearest(
'original',
fid,
limit:=200,
max_distance:=0.7
)
)-1
)
)
)
)
)
Basically, this creates for each line an array of lines, ordered after the distance to the current feature (first the nearest, than second nearest etc.). It only takes into consideration lines at a maximum distance of 0.7 (max_distance:=0.7
, twice in the expression). Change this value if it doesn't fit, however to me, it seemed to fit best to your data. If you set 0.6, a few lines will not be covered by any polygon, if you increase the value, the polygons will get wider and embrace more line per polygon: you will have lesser, but wider polygons. From the array created in this way, the expreeion than takes the last entry: thus the line farthest away form the acutal feature, but still in the limit we define of max. 0.7 [meters, because your CRS is in m]. Than for each pair of lines, the expression takes start- and endpoints and creates rectangles from these points.
The value of 0.7 is ideal because that is the approximate maximal distance between neighboring lines. Most lines are very close together, a few have a larger distance in between, are somehow "isolated". If you calculate the distance of every line to it's nearest neighboring line (creating a new field with field calculator), the highest value you get ist 0.6969533721192867
(the red line in the screenshot above) - so if you set the value to 0.7, you can be sure that all lines are included in one of the "clusters".
If you decide to leave a few lines
With this expression you get several overlapping polygons. Thus you need to dissolve them: Menu Vector / Geoprocessing Tools / Dissolve
.
Now, all polygons are part of one multipart feature. Thus apply Menu Vector / Geometry Tools / Multipart to singleparts
. And here you are with your polygons, grouping together lines that are close to one another.
You're almost done. One last step, however:
- Now it's ease to create the centerline of these rectangles. You can use this expression:
extend (
make_line(
centroid ($geometry),
project (
centroid ($geometry) ,
32,
radians (main_angle($geometry))
)
),
32,
0
)
See screenshot: the red lines are your original input lines. The black line is the center line created by this solution:
Update
As is seems that you have a problem with step 2 (your screenshot look correct, can't identify a problem there), I tried the whole process again with your shapefile again. I'm not sure if the Shapefile format is a problem as it has several limatations. I used Geopackage for processing. I startet with Shapefiles and soon realized that already in step 1, there is a problem with field length (see above, step 1, updated information). So I guess it is better to do everything in Geopackage.
You can try it yourself as I saved my project with your input (converted to Geopackage) with all outputs (intermediate steps) of step 1 to 5 in one project. As you can see in the screenshot, layers start from the bottom (input) and subsequent layers are added on the top. The last layer (5 Center_line
) is the output you look for.
For a strange reason, one line is perpendicular. It has to do with the fact the QGIS expression main_angle($geometry)
returns a value of 119.97400000065494
for the respective polygon feature, whereas for all the others the value returned is 29.97400000013954
(even though they are all parallel). You can solve this if in step 5 (above) you replace main_angle($geometry)
with 29.97400000013954
.
Like this, it works perfect as described. You can try it, the QGIS project with all data can be downloaded from here (don't forget do unzip the downloaded zip folder before opening in QGIS): https://drive.switch.ch/index.php/s/NUbFBSoqQMigbQt