Tuesday, June 13, 2017

Trovare le coordinate e la timezone di un luogo

Prima di entrare nel merito delle tecniche di calcolo della domificazione, dobbiamo risolvere un problema: come trovo, in modo programmatico, le coordinate di un luogo geografico? Posso usare un datebase di località, naturalmente, vi ho già mostrato come ricavare una semplice tabella con milioni di località e trasformarla in un database sqlite, qualche post addietro, abbiamo anche costruito un widget apposito. Il problema è che i database sono, in genere, molto pesanti da portarsi dietro, si arriva facilmente a qualche decina di mega e non è detto che siamo sempre in condizioni di installarli nel memoria di un dispositivo, se abbiamo qualche limitazione di risorse.

Considerato che il web è un enorme deposito di informazioni, comprese quelle di natura geografica, possiamo pensare, volendo, a utilizzare un servizio REST come quello che mette a disposizione Google: Google Maps Geocoding API, che è gratuito per usi limitati e richiede l'apertura di un account, una API key, e il pagamento oltre una certa soglia di utilizzo, oppure quello di geonames.org che è il sito che mette a disposizione quell'enorme database che ho usato per costruire il mio widget e sicuramente molti altri.

Oggi voglio invece provare a usare un metodo più ruvido e gratuito, basato su wikipedia e su alcune librerie python. Ve ne parlo succintamente, un trafiletto per ogni libreria.

Wikipedia

Per ogni luogo nel mondo, per quanto piccolo, Wikipedia fornisce una pagina in cui sono riportate le coordinate geografiche del luogo. Spulciando nel sorgente della pagina, non è difficile individuarle ed estrarle usando le tecniche delle espressioni regolari, presenti nella Python Standard Library. Preferisco, fra tutte, l'edizione inglese, cosa di cui dobbiamo tener conto quando cerchiamo un nome di città. In quel grande contenitore di librerie python che è Pypi ho trovato un'API per wikipedia pronta per l'uso, che dovremo quindi installare preliminarmente :

# Se non l'avete già fatto:
sudo pip install python-pip <Invio>
# quindi usate pip per installare wikipedia
pip install wikipedia <Invio>

Per la necessità o meno di usare i diritti di amministratore o per Windows o Mac vi prego di controllare la documentazione relativa all'uso di pip. La libreria python wikipedia è soggetta a licenza MIT.

Requests: HTTP for Humans

Se avete mai usato urllib e urlib2 apprezzerete questo piccolo gioiello di Kenneth Reitz, che consente di accedere al contenuto di una pagina web, anche in forma di xml o json dove previsto, in un soffio.

Per installare questa libreria si procede come prima:

pip install requests

La licenza d'uso è Apache 2.0

timezonefinder

Se Wikipedia ci aiuta moltissimo a trovare le coordinate geografiche, usa una rappresentazione della timezone del luogo basata sullo scarto orario da UTC, che diventa complicata da gestire. Con questa libreria posso facilmente ottenere, in base alle coordinate geografiche, la timezone del luogo espressa come "Europe/Rome" e simili, lasciando ad altre librerie il compito di gestire l'ora estiva

Installazione:

pip install timezonefinder

La libreria è di J. Michelfeit, ed è pubblicata su Pypi con licenza MIT

MyLittleTownFinder

Veniamo dunque al codice con cui gestire tutte le complesse operazioni di ricerca ed estrazione delle coordinate geografiche.

Wikipedia è abbastanza regolare nel catalogare i nomi di città, per cui di fronte a toponimi unici, di solito basta comporre l'url così: https://en.wikipedia.org/wiki/nomedelposto e siamo nella pagina giusta. Non sempre è così semplice, per esempio se un nome di città è riportato più volte, per esempio Paris è la capitale della Francia, ma anche una ridente località del Texas (che ha dato il titolo ad un bellissimo film di Wim Wenders).

In questo e simili casi, dobbiamo escogitare un trucco per individuare la pagina di interesse tra le molte possibili. La prima funzione che vi presento, debitamente commentata, fa proprio questo.

import sys
import re
import requests
import wikipedia as wk
from timezonefinder import TimezoneFinder as tfz


def find_a_place_wikipage():

    # first input the name of a place, in English, if possible 

    town = raw_input("city, town, or village: ")

    # the wikipedia search function gets a list of all the available pages

    urls = wk.search(town)

    # the list is printed to the screen, one element at a time,
    # each element preceded by its ordinal numbers. The user can choose an
    # element or type 99 to exit 

    for x,y in enumerate(urls):
        print x,y
    print "99 to quit"
    n = 9999
    while n not in range(len(urls)):
        n = int (raw_input("select a link : "))
        if (n==99):
            sys.exit()
    # the choosen page is returned to the caller
    return urls[n]

Questa funzione cerca di inviduare la pagina corrispondente alla città che cerchiamo. Nel caso non trovi una voce che ci sembra quella corretta, perchè per esempio cerchiamo Venice ma non quella italiana, magari quella in Florida o in Illinois, possiamo aggiungere queste specificazioni geografiche alla stringa di ricerca, il motore di ricerca punterà con maggiore facilità alla voce giusta.

La seconda funzione è quella che gestisce il recupero dei dati dalla pagina che abbiamo individuato. E' abbastanza semplice e commentata, per cui ve la lascio esaminare senza ulteriori discussioni.

def get_coordinates(location):

    # location is given by the caller
    base_url = 'https://en.wikipedia.org/wiki/'

    # a complete url is formed
    url = base_url + location

    # a page request is sent, if HTTP request is successful
    # then the procedure goes on, else is stopped
    page = requests.get(url)
    if page.status_code == requests.codes.ok:
        # the html text is retrieved
        b = page.text
        pattern = '(wgCoordinates":{"lat":)([+-]?\d*\.?\d*)(,"lon":)([+-]?\d*\.?\d*)'
        m = re.search(pattern, b) 
        # a regular expression is compared to the text
        # if successful, the coordinates are extracted and converted to float
        if m:
            latitudine = float(m.group(2))
            longitudine = float(m.group(4))
            # timezonefinder is used to precisely locate the related timezone
            tf = tfz()
            timezone = tf.timezone_at(lat=latitudine, lng=longitudine)
            # all three results are returned back to the caller as a tuple
            return (latitudine, longitudine, timezone)
        else:
            # unsuccessful operation  
            print"not found, sorry"    
            return None
    else:
        # error in HTTP request 
        print "requests error"
        return None

Per finire qualche riga di codice per eseguire in serie le due funzioni.

if __name__ == '__main__':
    location = find_a_place_wikipage()
    result = get_coordinates(location)
    if result:
        lat, lon, timezone = result
        print "latitude {0} longitude {1} timezone {2} ".format(lat, lon, timezone) 

Bene, facciamo qualche prova.

Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> 
========= RESTART: /home/ubuntu/Scrivania/proveStroBlog/cityurls.py =========
città: Roma
0 Roma
1 Representation oligonucleotide microarray analysis
2 A.S. Roma
3 Roma (mythology)
4 Ruska Roma
5 Roma, Queensland
6 Servitka Roma
7 Atletico Roma F.C.
8 Colonia Roma
9 Town of Roma
99 to quit
select a link : 0
not found, sorry
>>> 
========= RESTART: /home/ubuntu/Scrivania/proveStroBlog/cityurls.py =========
città: Rome
0 Rome
1 Rome, Georgia
2 Founding of Rome
3 History of Rome
4 Rome (TV series)
5 Rome Prize
6 Third Rome
7 Diocese of Rome
8 Sack of Rome
9 Province of Rome
99 to quit
select a link : 0
latitude 41.9 longitude 12.5 timezone Europe/Rome 
>>> 

Come potete vedere, ho inserito il nome di città nella dicitura italiana, ma sulla wikipedia in lingua inglese non trova niente. Una volta inserito il nome inglese, tutto fila liscio.

Un'altra prova:

Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> 
========= RESTART: /home/ubuntu/Scrivania/proveStroBlog/cityurls.py =========
città: moscow
0 Moscow
1 MoSCoW method
2 Moscow, Idaho
3 Moscow Metro
4 FC Moscow
5 Administrative divisions of Moscow
6 Moscow Conservatory
7 Mayor of Moscow
8 Grand Duchy of Moscow
9 Moscow Time
99 to quit
select a link : 0
latitude 55.75 longitude 37.6166666667 timezone Europe/Moscow 
>>> 

Notate che c'è una Moscow anche nell'Idaho, USA. Se selezionassi quel link avrei questo risultato:

Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> 
========= RESTART: /home/ubuntu/Scrivania/proveStroBlog/cityurls.py =========
città: moscow
0 Moscow
1 MoSCoW method
2 Moscow, Idaho
3 Moscow Metro
4 FC Moscow
5 Administrative divisions of Moscow
6 Moscow Conservatory
7 Mayor of Moscow
8 Moscow Time
9 Grand Duchy of Moscow
99 to quit
select a link : 2
latitude 46.73 longitude -117.0 timezone America/Los_Angeles 
>>> 

Facciamo direttamente la ricerca di Paris, Texas per vedere come se la cava.

Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
[GCC 6.3.0 20170118] on linux2
Type "copyright", "credits" or "license()" for more information.
>>> 
========= RESTART: /home/ubuntu/Scrivania/proveStroBlog/cityurls.py =========
città: paris texas
0 Paris, Texas
1 Paris, Texas (film)
2 Paris, Texas (band)
3 Lamar County, Texas
4 Paris Independent School District
5 Paris, Texas (disambiguation)
6 The Paris News
7 Paris (disambiguation)
8 Eiffel Tower (Paris, Texas)
9 First Church of Christ, Scientist (Paris, Texas)
99 to quit
select a link : 0
latitude 33.662508 longitude -95.547692 timezone America/Chicago 
>>> 

Mi sembra che funzioni benino. A questo punto penso che abbiamo un po' di materiale per andare oltre e iniziare a parlare della domificazione. A fra un po'.

No comments:

Post a Comment

How to create a virtual linux machine with qemu under Debian or Ubuntu with near native graphics performance

It's been a long time since my latest post. I know, I'm lazy. But every now and then I like to publish something that other people c...