Am primit de curând o provocare interesantă: Adi Șuhanea are un Google Spreadsheet (actualizat automat / manual de cineva) cu localitățile și numărul de școli înscrise la cercetași pentru Școala Altfel, și avea nevoie să afișeze într-o pagină a site-ului (bazat pe Joomla) o hartă cu pe care să fie distribuite școlile, în funcție de localitate.
Soluția inițială era un scriptuleț care rezolva numele localităților în coordonate, și apoi afișa pe un Google Map – problema cu soluția asta este că, de când Google Maps API a introdus obligativitatea unui API key, daca ai cateva zeci de hituri pe pagina respectivă pe zi și câteva zeci de localități de afișat (și sunt), ajungi foarte foarte repede la limita de request-uri pentru Geocoder-ul google.
Pe scurt, cea mai bună soluție în cazul ăsta este să salvezi undeva rezultatul geocodării (long și lat), ca să nu mai faci codarea și data viitoare când îți este afișată pagina – dar având în vedere că localitățile se salvează în Google Spreadsheet, cea mai bună soluție este să păstrezi rezultatele tot acolo. Dar cum?
Enter Google Data API (for python, în cazul nostru). API-ul este destul de complicat pentru utilizarea elementară de care avem noi nevoie, dar am găsit modulul text_db din același API, care pune la dispoziție o interfață mai simplă pentru accesarea Spreadsheet-urilor.
Sigur, pe lângă scrierea în document, e nevoie și de traducerea efectivă a adresei în long și lat, lucru pentru care este nevoie de un Geocoder (și am folosit o interfață python pentru Google Geocoder – geopy)
So here we go:
from geopy import geocoders
from gdata.spreadsheet import text_db
username = "test@example.com"
password = "secret"
document_name = "Numele documentului"
# se obtine interfata catre gdata-api
client = DatabaseClient(username, password)
# se deschide documentul (care va fi tratat database-like)
database = client.GetDatabases(name = document_name)
# si obtin toate sheet-urile (in contextul asta, tabele)
tables = database[0].GetTables()
# LookupFields initializeaza numele campurilor din coloane cu
# textul din prima linie a fiecarei coloane
tables[0].LookupFields()
# obtinem o lista cu toate liniile ceva in coloane cu header-ul
# localitate (povestea cu \" \" este necesara, am incercat si cu
# single quote ('), si nu "vrea"
lines = tables[0].FindRecords("localitate != \"\"")
# initializam geocoder-ul
# din documentatie, se poate initializa cu domeniul tarii, unde
# este disponibil, pentru a "tuna" rezultatele cautarii
g = geocoders.Google(GMAP_API, domain = "maps.google.ro")
for record in lines:
# datele sunt initializate in dictionarul contents
# numele coloanelor, indiferent de cum sunt trecute in document
# sunt normalizate - lowercased, fara spatii, si daca sunt doua cu acelasi nume
# primesc nume gen nume1, nume2 ... (este o functie in text_db care detaliaza)
if record.content.get("lat") == None:
try:
# aici pot fi doua probleme - una, ca nu este niciun rezultat, si atunci va fi ridicata
# o exceptie (prinsa mai jos), sau vor fi mai multe - de aceea o selectam doar pe prima
place, (lat, lng) = g.geocode(record.content.get("localitate"), exactly_one = False)[0]
# documentul meu vrea ca despartitor decimal , nu ., desi reprezentarea default in python
# este cu . - este deci nevoie sa operam usor stringul.
lat = "%f" % record.content['lat']
lng = "%f" % record.content['long']
record.content['lat'] = "%f" % lat.replace(".", ",")
record.content['long'] = "%f" % lng.replace(".", ",")
record.content['place'] = place
# push trimite documentului datele
record.Push()
except Exception:
continue
Ok, that’s it – ori de câte ori se adaugă în document o localitate care NU are nimic trecut în câmpurile pentru long și lat, se va face o cerere către geocoder și se vor salva rezultatele primite în același spreadsheet.
Mai este un side effect fericit aici: dacă primul rezultat nu este cel mai bun, operatorul poate corecta manual, adăugând mai multe informații lângă localitate (spre exemplu, județul), și ștergând longitutidea și latitudinea.
Pe backend, script-ul face treaba descrisă mai sus o dată la câteva ore. Pentru afișare, am legat repede un mini-app Django cu un view, un model și un template, care afișează harta.
Ah, și în procesul ăsta am inventat și un marker de hartă pentru Google Maps, pe care îl puteți lua de mai jos, în formate png (direct) sau svg (Inkscape), bazat pe .eps-ul de aici.
Varianta PNG (58 downloads )
Varianta SVG (Inkscape) (57 downloads )
Și rezultatul final, mai jos:
Cool stuff! Mersi!!! :D