PyQGIS – Adding X and Y Coordinate Fields to Multipoint Layer in PyQGIS

fields-attributesmultipointpyqgisxy

I have a MultiPoint layer in QGIS 3.18 that I wanted to add x and y coordinates to in its attribute table. Normally, I can just use field calculator and calculate the new field to be $x or $y.

How do I achieve the same thing in PyQGIS?

My current workflow:

pr = multiPointLayer.dataProvider()
pr.addAttributes([QgsField("x_coord", QVariant.Double), QgsField("y_coord", QVariant.Double)])
multiPointLayer.updateFields()

with edit(multiPointLayer):
    for feature in multiPointLayer.getFeatures():
        feature['x_coord'] = ???
        feature['y_coord'] = ???
        multiPointLayer.updateFeature(feature)

QgsProject.instance().addMapLayer(multiPointLayer)

PyQGIS is unwilling to handle MultiPoint layers, seems like — and when I use the Add X/Y coordinates algorithm provided by QGIS it wanted me to use single points, but I'd much rather keep it a MultiPoint layer since they can be relatively large.


Turns out, all I had to do is to convert my multipoint layer to singlepoint. When Clipping a point layer, the output point layer gets automatically converted to a multipoint one, which caused the issue. The commenting about a multipoint layer made me aware of the properties of the multipoint type.

Best Answer

Here are three solutions. You can either

  • Get a list, array or concatenated string of all x and y coordinates of each MultiPoint
  • Get the x and y coordinate of the centroid of each MultiPoint
  • Get the x and y coordinates of the first point of each MultiPoint

If not using the centroid, you need to loop through the vertices of each MultiPoint-Feature just as you would do it with a MultiLineString or a MultiPolygon.

#multiPointLayer = QgsProject.instance().mapLayersByName('Dissolved')[0] # get a layer
pr = multiPointLayer.dataProvider()
# Note the change of fieldtypes:
pr.addAttributes([
    # Will store the concatenated string of all x and y coordinates of each MultiPoint:
    QgsField("x_coords", QVariant.String),
    QgsField("y_coords", QVariant.String),
    # Will store the x and y coordinate of the centroid of each MultiPoint:
    QgsField("x_coord_centroid", QVariant.Double), 
    QgsField("y_coord_centroid", QVariant.Double),
    # Will store the x and y coordinates of the first point of each MultiPoint:
    QgsField("x_coord_firstpoint", QVariant.Double),
    QgsField("y_coord_firstpoint", QVariant.Double)
])
multiPointLayer.updateFields()

with edit(multiPointLayer):
    for feature in multiPointLayer.getFeatures():
        geom = feature.geometry() # get the geometry of the current feature
        x_arr = [] # create an empty list
        y_arr = [] # create an empty list
        for part in geom.parts(): # loop through the parts of each multipoint feature
            for p in part.vertices(): # now "loop through" each vertex of each part (actually a loop isnt really needed but easier to implement, since each part always has exact one vertex)
                x_arr.append(p.x()) # get the x coordinate of that vertex (p.x()) and append it to the list
                y_arr.append(p.y()) # get the y coordinate of that vertex (p.y()) and append it to the list
        
        # Same as array_to_string(array_foreach(generate_series(0,num_points($geometry)-1),x_at(@element)),','):
        feature['x_coords'] = ','.join(str(x) for x in x_arr) # turn the list of x coordinates into a comma spearated string. Therefore we need to iterate over the list and convert each double value to a string
        feature['y_coords'] = ','.join(str(y) for y in y_arr) # turn the list of y coordinates into a comma spearated string. Therefore we need to iterate over the list and convert each double value to a string
        # Same as x($geometry) and y($geometry):
        feature['x_coord_centroid'] = geom.centroid().asPoint().x() # get the x coordinate of the multipoint features centroid
        feature['y_coord_centroid'] = geom.centroid().asPoint().y() # get the y coordinate of the multipoint features centroid
        # Same as $x and $y:
        feature['x_coord_firstpoint'] = x_arr[0] # get the x coordinate of the first point of each MultiPoint
        feature['y_coord_firstpoint'] = y_arr[0] # get the y coordinate of the first point of each MultiPoint
        
        multiPointLayer.updateFeature(feature)

QgsProject.instance().addMapLayer(multiPointLayer)

A side note on your expressions, not PyQGIS related:

If you type $x or $y in fieldcaluclator on a MultiPoint-layer, you will always get the coordinate of the "first" point. This is mentioned in the function description.

If you use x($geometry) or y($geometry), you will get the centroid for each MultiPoint.

If you want another coordinate than the one from the first point, you can use x_at() or y_at(), e.g. x_at(2) to return the x coordinate of the 3rd point of a MultiPoint.

You can also use x_at() and y_at() to return an array of all x and y coordinates of a MultiPoint by combining it with num_points(), generate_series() and array_foreach(), e.g. by using array_foreach(generate_series(0,num_points($geometry)-1),x_at(@element)) to get an array of all x coordinates of each MultiPoint or array_foreach(generate_series(0,num_points($geometry)-1),y_at(@element)) to get an array of all y coordinates of each MultiPoint.