3

I have a set of points and I want to create lines from them. Points belonging to one line have unique "UniqueID" to be grouped by. I try to use QGIS's "Points to path algorithm". What I don't have is an order field, so the problem is that it connects the points in random order.

enter image description here

The picture contains an example of a line with 6 points. The QGIS Points to path algorithm connects the points in the following order: 1,3,2,6,4,5 and creates visually a good looking line, but with the length longer than the line between point 1 and 6. So I would like to know how to establish the order for the points to be added when creating a line. The points need to be connected from beginning to end in order i.e. 1,2,3,4,5,6.

enter image description here

The attribute table is shown on the picture. Each point has unique "fid" and a field called "UniqueID" that groups the points to one line i.e. points that belong to the same line have the same "UniqueID". The name of the attribute comes from the data I have received so can't really make it better.

How can I introduce an order so that I can have points connected by shortest path? The end goal is to calculate the length of lines, so if that could be done easily on the lines created from the unordered points, it is also a good solution to the issue.

7
  • "Manually" is the answer, unless there's some order to the points you didn't tell us about.
    – Erik
    Feb 2, 2021 at 7:59
  • The order is from one end of the line to the other end in the shortest path.
    – krltos
    Feb 2, 2021 at 8:00
  • Yeah, but are the lines all oriented in the same way? Or is there some other order to your lines?
    – Erik
    Feb 2, 2021 at 8:04
  • Can you show us attribute table of points? Maybe it can be done via pyqgis.
    – Gakovî
    Feb 2, 2021 at 8:05
  • 1
    In your screenshot, all points are on the same line: is this the case with all your points? If so, the solution is easy: just do points to path, explode line, delete duplicate geometries (that's why your line is longet than 1 to 6). If not: For each point, search the nearest neighboring point and connect them. There will remain some gaps, but see here if this helps: gis.stackexchange.com/a/383231/88814 or gis.stackexchange.com/a/382246/88814
    – Babel
    Feb 2, 2021 at 8:55

3 Answers 3

3

If you are looking for a simple line that is going in only one direction, a straightforward way to solve your problem is to use the derivated geometry attributes in order to join the points in the correct order.

Using the Points to path algorithm, add the geometry attribute in Order by expression.

For example, $X will join the points form the left to the right, while $Y will join the points from south to north.

You can use the UniqueIDfield as Path group expression in order to create lines between points with the same unique ID.

1
  • 1
    Great idea, as long as the line is going in one direction only - not if it has a zigzag like shape.
    – Babel
    Feb 2, 2021 at 15:50
2

You don't need to sort anything. Issue is due to the features have a wrong id. So, you only have to assign them in the correct order. It can be done in QGIS with a little PyQGIS script where I pair ids and point geometries in a list of lists. These lists are used to change ids to correct order and directly to produce a good looking line. It looks as follows:

layer = iface.activeLayer()

feats = [ feat for feat in layer.getFeatures() ]

points_id = [ [feat.attribute('id'), feat.geometry().asPoint()] for feat in feats ]

for i, item in enumerate(points_id):
    points_id[i][0] = i

points = [ item[1] for item in points_id ]

line = [ points ]

epsg = layer.crs().postgisSrid()

uri = "LineString?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri,
                           'line',
                           'memory')

prov = mem_layer.dataProvider()

new_feats = [ QgsFeature() for i in range(len(line)) ]

for i, feat in enumerate(new_feats):
    feat.setAttributes([i])
    feat.setGeometry(QgsGeometry.fromPolylineXY(line[i]))

prov.addFeatures(new_feats)
QgsProject.instance().addMapLayer(mem_layer)

For testing my script, I added more points with random ids and preserving values present in your layer. It can be observed in following image.

enter image description here

After running the script in Python Console of QGIS result was as expected: a good looking line.

enter image description here

QGIS 'Points to path' processing algorithm can be used for corroborating that script works as expected. Following image shows the result of running referred algorithm with original ids points.

enter image description here

1

maybe this is not best solution but i think it can work. Your problem is that you have not correct order. To get order, you have to calculate all distances between the points. Then you have to calculate shortest distance between two points and save them to a list(for each point). After that you have to create new field in attribute table and set up the order. Here is the pyqgis code. It is not perfect but i think it can solve your problem. Follow these: Click Ctrl+Alt+P to open python console. Then click to "open editor" icon(look at the picture). Then paste the following code and run it(Green triangle icon). After this you will have order field in your attribute list. Then choose 'Points to Path' processing algorithm and select 'order' in 'Order Field'. Click to run. That's it. Sorry for bad english. Don't forget to change layer name from 'pnts' to yours

#pnts is my points layer, write your own.
points = QgsProject().instance().mapLayersByName('pnts')[0]

dc = dict()
max = 0
ds = 1000000000

for f in points.getFeatures():
    dc[f.id()] = list()
    max=max+1
for f1 in points.getFeatures():
    for f2 in points.getFeatures():
        if f1.id()!=f2.id():
            d = f1.geometry().distance(f2.geometry())
            dc[f1.id()].append([f2.id(),d])
fids = list()
val = 0
ls = list()
index = 0
while True:
    index = index+1
    for fid,dist in dc[val]:
        if dist<ds and (fid in fids)==False:
            ds = dist
            fd = fid
    fids.append(val)
    ls.append([val,fd,ds])
    val = fd
    ds = 100000000
    if index==max:
        break

points.dataProvider().addAttributes([QgsField("order", QVariant.Int)])
points.updateFields()
order = list()
for l in ls:
    order.append(l[0])
i = 0
for f in order:
    attrs = {len(points.fields())-1 : i}
    points.dataProvider().changeAttributeValues({ order[i] : attrs })
    i=i+1    

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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