3

The script doesn't work as if I wanted.

from qgis.core import *
from .resources import *
import json

mylayer = QgsProject.instance().mapLayersByName("layer")[0] features = mylayer.getFeatures() for f in features:
dane1 = f.attribute(2)

url = 'https://nominatim.openstreetmap.org/?addressdetails=1&&format=json&limit=1&q='+dane1

response = requests.get(url)

aaa = json.dumps(response.json()[0]["lat"], ensure_ascii=False, indent=4) bbb = json.dumps(response.json()[0]["lon"], ensure_ascii=False, indent=4)

material = aaa.replace('"','') + ', ' + bbb.replace('"','')

mylayer.startEditing()

forum = [material] i = 0

for seg in mylayer.getFeatures(): seg['WSP_X'] = forum[i] i += 1 mylayer.updateFeature(seg) mylayer.commitChanges()

Effect:

enter image description here

Desired effect:

enter image description here

Script doesn't work correctly.

Taras
  • 32,823
  • 4
  • 66
  • 137

2 Answers2

5

The main issue is that you are making the Nominatim request only once. It does not get updated and re-sent for each feature. There are a couple of other issues too, see my comments in the code for explanation.

from qgis.core import *
from .resources import *
import requests
import json

mylayer = QgsProject.instance().mapLayersByName("layer")[0] features = mylayer.getFeatures()

open the edit session outside the loop

mylayer.startEditing()

for f in features:
dane1 = f.attribute(2)

# move the URL formulation and Nominatim request inside the loop to ensure the URL is upated with each new address
url = 'https://nominatim.openstreetmap.org/?addressdetails=1&&format=json&limit=1&q='+dane1

# send request once for every feature
response = requests.get(url)

aaa = json.dumps(response.json()[0]["lat"], ensure_ascii=False, indent=4)
bbb = json.dumps(response.json()[0]["lon"], ensure_ascii=False, indent=4)

material = aaa.replace('"','') + ', ' + bbb.replace('"','')

# remove the `forum` variable and work directly with the `material` variable
#forum = [material]

# no need to get the features again because you are already iterating through them

# corrected the field name and use the `f` variable from the original feature iterator, rather than `seg`
f['WSP_X_Y'] = material
mylayer.updateFeature(f)

mylayer.commitChanges()

enter image description here

Matt
  • 16,843
  • 3
  • 21
  • 52
4

Once I was inspired by @ThomasG77's idea, so now I am suggesting another approach using the QGIS's domestic class QgsNominatimGeocoder with Nominatim geocoder under the hood. It is an open-source solution, otherwise one can use the QgsGoogleMapsGeocoder class, where an apiKey must be specified.

I see several advantages over a nice solution from @Matt: (1) no need to install/use not built-in Python packages e.g. requests (however, it is included in QGIS) (2) no need to deal with proxies and certificates, in requests package it is usually needed by setting verify and proxies parameters (3) no need "to play" with the url. Perhaps there is one disadvantage: this solution is only suitable for QGIS 3.18 and later versions.

Let's assume there is a point layer called 'random_points_test' with its attribute table, see image below.

input

Proceed with Plugins > Python Console > Show Editor (see documentation) and paste the script below

# imports
from statistics import mean
from qgis.core import (
    QgsNominatimGeocoder,
    QgsGeocoderContext,
    QgsCoordinateTransformContext,
    )

def geoms_to_coords(geoms: list) -> str: """ Convert a list with geometries into a string with a single coordinates pair. Parameters: ========== :param geoms: a list with geometries of 'QgsGeometry: Point (X Y)' type Returns: ========== :return: a string with YX-coordinates """ # convert a list with geometries into a list with sets of x,y coordinates geoms_coordinates = [(geom.asPoint().x(), geom.asPoint().y()) for geom in geoms]

# get a mean value of all x and y inside a set
x_avg, y_avg = [mean(coords) for coords in zip(*geoms_coordinates)]

# represent mean x and y as a string with 6 digits after comma
coords = f"{format(y_avg, '.6f')}, {format(x_avg, '.6f')}"

return coords


def geocoding_the_address(address: str, country: str) -> str: """ Geocode an address into coordinates with Nominatim Geocoder. Parameters: ========== :param address: an address to geocode :param country: a code of a country Returns: ========== :return: a string with coordinates or 'No result' """ # call the Nominatim Geocoder with a country code parameter geocoder = QgsNominatimGeocoder(countryCodes=country) # call the context to encapsulate the context of a geocoding operation context = QgsGeocoderContext(QgsCoordinateTransformContext()) # set a method for geocoding a string with an address output = geocoder.geocodeString(address, context)

# check if the output of geocoding is not empty,
# otherwise output 'No result'
if len(output):
    # a list with geometries of each valid output
    output_geoms = [out.geometry() for out in output if out.isValid()]
    # achieve a single coordinate pair
    output_coords = geoms_to_coords(output_geoms)

    return output_coords

else:
    return "No result"


def address_to_coords( layer_name: str, input_field: str, output_field: str, country: str = None ): """ Geocode a field with an address into a field string with coordinates of the address. Parameters: ========== :param layer_name: the name of a layer :param input_field: the input field with an address :param output_field: the output field for coordinates :param (optional) country: to restrict results to one or more countries. It must be in ISO 3166-1alpha2 code and comma-separated. """

# get layer by name
layer = QgsProject.instance().mapLayersByName(layer_name)[0]

# get all fields of the layer
fields = layer.fields()
# get names of all fields of the layer
fields_names = layer.fields().names()

# check if the input field is in all fields of the layer
if input_field in fields_names:

    # get index of the input field
    idx_in = fields.indexFromName(input_field)

    layer.startEditing()

    # check if the output field already exists,
    # otherwise create it (of a string type)
    if not output_field in fields_names:
        new_field = QgsField(output_field, QVariant.String)
        layer.dataProvider().addAttributes([new_field])
        layer.updateFields()

    for feature in layer.getFeatures():
        # get address value of the input field for each feature
        address = feature.attribute(idx_in)

        # check if the address value exists
        if address:
            # appropriate the output field with a new value
            feature[output_field] = geocoding_the_address(address, country)

        else:
            # appropriate the output field with 'No result'
            feature[output_field] = "No result"

        layer.updateFeature(feature)

layer.commitChanges()
print('Done!')


executing the function

address_to_coords('random_points_test', "address", "WSP_X_Y", 'pl')

In the code above, please do not forget to apply several changes in the last line: address_to_coords('random_points_test', "address", "WSP_X_Y", 'pl') before running it.

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

result


References:

Taras
  • 32,823
  • 4
  • 66
  • 137