7

I have installed the "GeoCoding" Plugin in QGIS and it worked well even though I have used non-ASCII characters to geocode (i.e. Hacettepe Üniversitesi). However, when I tried to do the same in PyQGIS with the following code I got the error:

"UnicodeEncodeError: 'ascii' codec can't encode character"

The problem is that the returned address contains non-ASCII characters. Any ideas to solve this?

from geopy.geocoders import Nominatim

#Next, we'll create our geocoder:
geocoder = Nominatim()

#geocode an address:
location = geocoder.geocode("Hacettepe Üniversitesi")

print location

Another relevant question: I rely on Joel Lawhead's book ('QGIS Python Programming Cookbook') and the import statement relevant to this example in the book is:

from GeoCoding.geopy.geocoders import Nominatim

However, this statement gave the following error

ImportError: No module named geopy.geocoders

Is there a source we could follow to make these import statements without a try-and-guess?

1
  • Received a similar error even if I input London Eye! :S – banbar Jul 29 '17 at 17:33
5

IMHO the issue with non-ASCII characters shall not arise anymore, due to improvements that were done in software since 2017 year. Perhaps the problem was about the PyQGIS's version or OS settings.

Nowadays the geoding of 'Hacettepe Üniversitesi' object works well with both GeoPy (as was mentioned by @ivan) and "GeoCoding" Plugin using PyQGIS.

  1. GeoPy in QGIS

    Using this code

    from geopy.geocoders import Nominatim
    
    geocoder = Nominatim(user_agent="MyGeocoder", timeout=10)
    
    location = geocoder.geocode("Hacettepe Üniversitesi", exactly_one=False)
    
    print(location.raw)
    

    I was able to get this result, see the image below

    [Location(Hacettepe Üniversitesi Beytepe Kampüsü, Hacettepe-Beytepe Kampüs Yolu, Üniversiteler Mahallesi, Çankaya, Ankara, İç Anadolu Bölgesi, 06800, Türkiye, (39.867445950000004, 32.73548807525388, 0.0)), Location(Hacettepe Universitesi, Hacettepe-Beytepe Kampüs Yolu, Üniversiteler Mahallesi, Çankaya, Ankara, İç Anadolu Bölgesi, 06800, Türkiye, (39.8688599, 32.7356556, 0.0))]

    geopy

  2. GeoCoding in QGIS

    This time using this code

    from qgis import utils
    geocoding_plugin = utils.plugins['GeoCoding']
    geocoding_plugin.geocode()
    

    I achieved this result, see the image below

    geocoding_plugin

3

Since QGIS 3.18, no need for third party to geocode from Nominatim or Google Maps (not demonstrated below as you need an API key) as there is a class dedicated for this purpose in PyQGIS

Below, a recipe for Nominatim

import json
from qgis.core import (
    QgsNominatimGeocoder,
    QgsGeocoderContext,
    QgsCoordinateTransformContext,
    QgsGeocoderInterface
)

n = QgsNominatimGeocoder()
context = QgsGeocoderContext(QgsCoordinateTransformContext())

address = '28 rue paul bellamy, 44000 Nantes'
out = n.geocodeString(address, context)

getter_methods_geocoder_result = [
    'additionalAttributes',
    'crs',
    'geometry',
    'isValid',
    'description',
    'error',
    'group',
    'identifier'
]

for f in out:
    for method in getter_methods_geocoder_result:
        print(method, getattr(f, method)())

For reverse geocoding, there is an interface method geocodeFeature in parent class QgsGeocoderInterface of both QgsNominatimGeocoder and QgsGoogleMapsGeocoder. You need to implement it and change flags to set that your geocoder supports both geocoding and reverse geocoding

A "naive" reverse geocoding implementation below

import json
from qgis.core import (
    QgsNominatimGeocoder,
    QgsGeocoderContext,
    QgsCoordinateTransformContext,
    QgsGeocoderInterface
)

class QgsNominatimGeocoderOverloaded(QgsNominatimGeocoder):
    def __init__(self, endpointReverse="https://nominatim.openstreetmap.org/reverse"):
        super().__init__()
        self.endpointReverse = endpointReverse
    def flags(self):
        return QgsGeocoderInterface.Flag.GeocodesStrings & QgsGeocoderInterface.Flag.GeocodesFeatures
    def geocodeFeature(self, feature, context, feedback=None):
        pt = feature.geometry().asPoint()
        lon, lat = pt.x(), pt.y()
        url = f"{self.endpointReverse}?lat={lat}&lon={lon}&format=json"
        request = QgsBlockingNetworkRequest()
        request.get(QNetworkRequest(QUrl(url)))
        reply = request.reply()
        content = reply.content()
        jsonContent = json.loads(content.data().decode())
        return self.jsonToResult(jsonContent)

context1 = QgsGeocoderContext(QgsCoordinateTransformContext())

if len(iface.activeLayer().selectedFeatures()) > 0:
    firstSelected = iface.activeLayer().selectedFeatures()[0]

n1 = QgsNominatimGeocoderOverloaded()

out1 = n1.geocodeFeature(firstSelected, context1)

getter_methods_geocoder_result = [
    'additionalAttributes',
    'crs',
    'geometry',
    'isValid',
    'description',
    'error',
    'group',
    'identifier'
]


for method in getter_methods_geocoder_result:
    print(method, getattr(out1, method)())

To account for rate limit, cache (avoid two calls with same URLs in a short interval to avoid being "banned"), a more complete code should be coded using code similar to geocodeString implementation e.g https://qgis.org/api/qgsnominatimgeocoder_8cpp_source.html#l00072

6
  • wow ... I did not know about that! Thank you – Taras Apr 30 at 17:38
  • Is it also applicable for reverse geocoding? I cannot find it in qgis.org/pyqgis/master/core/… – Taras Apr 30 at 17:57
  • Not available, need to overload the class. Discussion about native geocoding with some discussions related to reverse geocoding github.com/qgis/QGIS/pull/38169 – ThomasG77 Apr 30 at 18:59
  • Have no clue yet how to deploy your function together with a class, but it is charming =) – Taras Apr 30 at 19:18
  • I think it should be return n1.jsonToResult(jsonContent) instead of return n.jsonToResult(jsonContent). Otherwise I am getting an error Traceback (most recent call last): File "C:\PROGRA~1\QGIS3~1.18\apps\Python37\lib\code.py", line 90, in runcode exec(code, self.locals) File "<input>", line 1, in <module> File "<string>", line 32, in <module> File "<string>", line 23, in geocodeFeature NameError: name 'n' is not defined – Taras Apr 30 at 19:21
0

Here is one way to do it:

  • I used Ubuntu with Jupyter notebook, but it should work with any Jupyter environment with Python 3

  • pip install geopy

    after install, make that line as comment to avoid reinstall

  • from geopy.geocoders import Nominatim

  • geocoder = Nominatim(user_agent="http")

    The line above sets user agent to avoid the error you were getting as without agent, it violates this https://operations.osmfoundation.org/policies/nominatim/

  • location = geocoder.geocode("Hacettepe Üniversitesi")

  • print(location)

Result is

Hacettepe Üniversitesi Beytepe Kampüsü, Hacettepe-Beytepe Kampüs Yolu, Bilkent, Üniversiteler Mahallesi, Çankaya, Ankara, İç Anadolu Bölgesi, 06800, Türkiye

Added link to colab where you can see it in action 
https://colab.research.google.com/drive/1GX29YuMwsehPGL-WcQkHtyh-0w97rpoN?usp=sharing

enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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