6

How do I create a vector layer with a network of line segments drawn between every point in another vector layer?

Basically, I am trying to create something similar to a hub/spoke network, except that instead of having a single hub and the other points are destinations, I would like every point to be treated as a hub with a direct connection to every other point.

I have managed to get the hub lines from one point to every other using the MMQGIS plugin "Hub Lines" module, but it seems that I would need to re-run it for each individual point.

Is there a way to just do it all in one go in QGIS?

It may be worth noting that I don't need to know the distances between the points; I'm looking for the azimuths.

1

4 Answers 4

5

I think that it might be easiest to do with ST_ShortestLine with PostGIS http://postgis.net/docs/ST_ShortestLine.html or with a similar ShortestLine function in Spatialite https://www.gaia-gis.it/gaia-sins/xmlBlob/spatialite-sql-latest.html.

You said you have only points but those functions can be used for other geometries as well.

Here is an example with Spatialite.

SELECT
    shortestline(a.geometry,b.geometry) AS geometry, 
    a.rowid AS start,
    b.rowid AS end 
FROM
    source_table a,
    source_table b 
WHERE
    a.rowid < b.rowid;

enter image description here

1
  • Thanks for the suggestion. Unfortunately I've never used PostGIS or Spatialite, and was daunted by the prospect of learning a new system. Fortunately, about an hour ago I stumbled across the FlowMapper plugin for QGIS. It does exactly what I had hoped for, though I have to create a file of dummy magnitude data for each point file (not too onerous).
    – Nick_W
    Commented Apr 2, 2015 at 0:36
4

For QGIS versions QGIS 2.x and lower

My technique using the FlowMapper plugin to connect all the points with each other:

  1. In QGIS, save the point layer to a CSV file, checked 'skip attribute creation', selected tab delimiter- only the coordinates should be exported. (I'll call this file A)
  2. Change the file suffix from .csv to .txt
  3. Open the file in a text editor and delete "X" and "Y" from the top (save and exit)
  4. Create a dummy magnitude file in Excel, LibreOffice calc, or whatever. The dummy file must have as many rows and columns as there are points in the point layer (20x20, in my case)
  5. Save that file as a .csv with either space or tab delimiter. (I'll call this file B)
  6. Convert the .csv to a .txt
  7. In QGIS, in the FlowMapper plugin menu, select 'Generate flow lines and nodes'.
  8. Select file 'A.txt' for the node coordinates
  9. Select file 'B.txt' for the flow magnitudes
  10. Select 'gross' for 'flow type'
  11. Enter a save file name/location for the output line shapefile
  12. Click OK.

The resulting line layer has several attributes that aren't immediately useful to what I'm doing, so I deleted them, but they may be useful to others.

It's kind of a clunky workflow, but without learning PostGIS or Spatialite, it's the best I've managed to do (and it gets the exact result I was hoping for).

2

Solution for two point layers using PyQGIS

Let's assume there are two point layers 'start'(pink) and 'end'(dark green), with its corresponding attribute tables accordingly, see image below.

input

Proceed with Plugins > Python Console > Show Editor and paste the script below

# accessing two layers with points and one layer with polygons
points_from = QgsProject.instance().mapLayersByName("start")[0] # points from
points_to = QgsProject.instance().mapLayersByName("end")[0] # points to

# creating a virtual output layer with line strings
line_layer = QgsVectorLayer("LineString?crs={}&index=yes".format(points_from.crs().authid()), "Connections", "memory")

# adding new fields 
provider = line_layer.dataProvider()
provider.addAttributes([QgsField(field.name() + '_f', field.type()) for field in points_from.fields()]) # adding fields from 'points_from' with '_f'-prefix
provider.addAttributes([QgsField(field.name() + '_t', field.type()) for field in points_to.fields()]) # adding fields from 'points_to' with '_t'-prefix 
line_layer.updateFields()

# looping over each feature from two layers and making all possible connections between them
for point_from in points_from.getFeatures():
    for point_to in points_to.getFeatures():
        connect = [point_from.geometry().asPoint(), point_to.geometry().asPoint()] # connection between two points
        line = QgsGeometry.fromPolylineXY(connect) # creating a line string from connection
        f = QgsFeature() # creating Qgs feature
        f.setGeometry(line) # setting new geometry as a line string
        f.setAttributes([point_from.attribute(i) for i in points_from.attributeList()] + [point_to.attribute(j) for j in points_to.attributeList()]) # setting new attributes as from and to
        provider.addFeature(f) # adding Qgis feature to the output layer

# adding a new feature to the map
QgsProject.instance().addMapLayer(line_layer)

Press Run script run script and get the output that will look like

result


When only "ID"-fields from both layers are required then execute this script

# accessing two layers with points
points_from = QgsProject.instance().mapLayersByName("start")[0] # points from
points_to = QgsProject.instance().mapLayersByName("end")[0] # points to

# getting index of the 'id'-field from two layers
points_from_id_index = points_from.fields().indexFromName('id') # index in points from
points_to_id_index = points_to.fields().indexFromName('id') # index in points to

# creating a virtual output layer with line strings
line_layer = QgsVectorLayer("LineString?crs={}&index=yes".format(points_from.crs().authid()), "Connections", "memory")

# adding new fields 
provider = line_layer.dataProvider()
provider.addAttributes([QgsField(points_from.fields()[points_from_id_index].name() + '_from', points_from.fields()[points_from_id_index].type()),\
                        QgsField(points_to.fields()[points_to_id_index].name() + '_to', points_to.fields()[points_to_id_index].type())
                        ])
line_layer.updateFields()

# looping over each feature from two layers and making all possible connections between them
for point_from in points_from.getFeatures():
    for point_to in points_to.getFeatures():
        connect = [point_from.geometry().asPoint(), point_to.geometry().asPoint()] # connection between two points
        line = QgsGeometry.fromPolylineXY(connect) # creating a line string from connection
        f = QgsFeature() # creating Qgs feature
        f.setGeometry(line) # setting new geometry as a line string
        f.setAttributes([point_from.attribute(points_from_id_index), point_to.attribute(points_to_id_index)]) # setting new attributes as from and to
        provider.addFeature(f) # adding Qgis feature to the output layer

# adding a new feature to the map
QgsProject.instance().addMapLayer(line_layer)

result2


References:

1

Solution for three and more point layers using PyQGIS

Let's assume there are three point layers 'start'(pink), 'end'(dark green) and 'pois'(dark blue), with its corresponding attribute tables accordingly, see image below.

input

Proceed with Plugins > Python Console > Show Editor and paste the script below

# imports
import processing

# a list that defines all point layers for which connections have to be created
point_layers_names = ['start', 'end', 'pois'] # here a user has to specify names of point layers!

# creating a list of layers with points
point_layers = []
for point_layer_name in point_layers_names:
    point_layers.append(QgsProject.instance().mapLayersByName(point_layer_name)[0])

# merging point layers into one layer with 'qgis:mergevectorlayers' geoalgorithm
points_merged = processing.run("qgis:mergevectorlayers",{
                                        'CRS' : point_layers[0].crs().authid(),
                                        'LAYERS' : point_layers,
                                        'OUTPUT' : 'memory:'
                                        })['OUTPUT']

# getting indexes of fields in the merged layer
points_merged_id_index = points_merged.fields().indexFromName('id')
points_merged_layer_index = points_merged.fields().indexFromName('layer')

# creating a virtual output layer with line strings
line_layer = QgsVectorLayer("LineString?crs={}&index=yes".format(point_layers[0].crs().authid()), "Connections", "memory")

# adding new fields
provider = line_layer.dataProvider()
provider.addAttributes([QgsField(points_merged.fields()[points_merged_index].name() + '_from', points_merged.fields()[points_merged_index].type()),\
                        QgsField(points_merged.fields()[points_merged_layer_index].name() + '_from', points_merged.fields()[points_merged_layer_index].type()),\
                        QgsField(points_merged.fields()[points_merged_index].name() + '_to', points_merged.fields()[points_merged_index].type()),\
                        QgsField(points_merged.fields()[points_merged_layer_index].name() + '_to', points_merged.fields()[points_merged_layer_index].type()),\
                        ])
line_layer.updateFields()

# looping over each feature from the merged layer and making all possible connections between them
for f1 in points_merged.getFeatures():
    for f2 in points_merged.getFeatures():
        if f1.geometry().asPoint() != f2.geometry().asPoint(): # avoiding connections between same features
            connect = [f1.geometry().asPoint(), f2.geometry().asPoint()] # connection between two points
            line = QgsGeometry.fromPolylineXY(connect) # creating a line string from connection
            f = QgsFeature() # creating Qgs feature
            f.setGeometry(line) # setting new geometry as a line string
            f.setAttributes([f1.attribute(points_merged_index), f1.attribute(points_merged_layer_index), f2.attribute(points_merged_index), f2.attribute(points_merged_layer_index)]) # setting new attributes as from and to
            provider.addFeature(f) # adding Qgis feature to the output layer
            
# adding a new feature to the map
QgsProject.instance().addMapLayer(line_layer)

Press Run script run script and get the output that will look like

result1


Mind that in the example above duplicate geometries will appear because of the connection in two directions. If this component has to be avoided, please run this script

# imports
import processing

# a list that defines all point layers for which connections have to be created
point_layers_names = ['start', 'end', 'pois'] # here a user has to specify names of point layers!

# creating a list of layers with points
point_layers = []
for point_layer_name in point_layers_names:
    point_layers.append(QgsProject.instance().mapLayersByName(point_layer_name)[0])

# merging point layers into one layer with 'qgis:mergevectorlayers' geoalgorithm
points_merged = processing.run("qgis:mergevectorlayers", {
                                        'CRS' : point_layers[0].crs().authid(),
                                        'LAYERS' : point_layers,
                                        'OUTPUT' : 'memory:'
                                        })['OUTPUT']

# getting indexes of fields in the merged layer
points_merged_id_index = points_merged.fields().indexFromName('id')
points_merged_layer_index = points_merged.fields().indexFromName('layer')

# creating a virtual output layer with line strings
line_layer = QgsVectorLayer("LineString?crs={}&index=yes".format(point_layers[0].crs().authid()), "Connections", "memory")

# adding new fields
provider = line_layer.dataProvider()
provider.addAttributes([QgsField(points_merged.fields()[points_merged_index].name() + '_from', points_merged.fields()[points_merged_index].type()),\
                        QgsField(points_merged.fields()[points_merged_layer_index].name() + '_from', points_merged.fields()[points_merged_layer_index].type()),\
                        QgsField(points_merged.fields()[points_merged_index].name() + '_to', points_merged.fields()[points_merged_index].type()),\
                        QgsField(points_merged.fields()[points_merged_layer_index].name() + '_to', points_merged.fields()[points_merged_layer_index].type()),\
                        ])
line_layer.updateFields()

# looping over each feature from the merged layer and making all possible connections between them
for f1 in points_merged.getFeatures():
    for f2 in points_merged.getFeatures():
        if f1.geometry().asPoint() != f2.geometry().asPoint(): # avoiding connections between same features
            connect = [f1.geometry().asPoint(), f2.geometry().asPoint()] # connection between two points
            line = QgsGeometry.fromPolylineXY(connect) # creating a line string from connection
            f = QgsFeature() # creating Qgs feature
            f.setGeometry(line) # setting new geometry as a line string
            f.setAttributes([f1.attribute(points_merged_index), f1.attribute(points_merged_layer_index), f2.attribute(points_merged_index), f2.attribute(points_merged_layer_index)]) # setting new attributes as from and to
            provider.addFeature(f) # adding Qgis feature to the output layer

# removing duplicated geometries
line_layer_unique = processing.run("qgis:deleteduplicategeometries", {'INPUT': line_layer, 'OUTPUT': 'memory:'})['OUTPUT']

# adding a new feature to the map
QgsProject.instance().addMapLayer(line_layer_unique)

Press Run script run script and get new output

result2


References:

Your Answer

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

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