Praca z API Google Maps

Załóżmy, że posiadamy kilka tysięcy adresów, które chcemy umieścić za pomocą znaczników na mapie Google Maps. Znam osobę, uważaną przez niektórych za zdolnego programistę, która zrobiła to w następujący sposób:

  1. otworzenie strony Google Maps
  2. wpisanie poszukiwanego adresu, np. Plac im. Jana Nowaka-Jeziorańskiego 3, Kraków
  3. znalezienie na stronie współrzędnych geograficznych poszukiwanego adresu, w tym wypadku 50.06561980,19.946850
  4. zapis odczytanych współrzędnych do bazy danych

Można i tak, ale o wadach takiego postępowania z pewnością nie trzeba wspominać: rzeczonej osobie zajęło to ponad dwa dni robocze, więc poza stratą czasu dodajmy m.in. zmęczenie czy łatwość pomyłki.
Każdy, nawet średnio rozgarnięty programista, próbowałby sobie ten proces jakoś zautomatyzować czy ułatwić.

Dokumentacja Google Maps

Jak się okazuje sprawa jest banalna. Zacznijmy zatem od przeglądnięcia dokumentacji map i tamtejszego FAQ.

Z łatwością znajdziemy tam pytanie zdające się odpowiadać na nasze zagadnienie:

I need to convert addresses to latitude/longitude pairs. Can I do that with the Google Maps APIs?

Odpowiedź na powyższe pytanie przenosi nas do strony dotyczącej tzw. Geocodingu, czyli konwertowania adresu na współrzędne lub odwrotnie — to jest to, czego szukamy :)
Informacje z tej strony pozwolą nam „ubrudzić sobie trochę ręce”.

Wiemy, że zapytanie możemy wysłać za pomocą następującego URL:
http://maps.googleapis.com/maps/api/geocode/output?parameters
Gdzie wymagane parametry to:

  • address — adres, dla którego chcemy znaleźć współrzędne,
    lub
    latlng — współrzędne, dla których chcemy znaleźć najbliższy odpowiadający adres,
    lub
    components — rodzaj filtru, wg którego możemy ograniczyć poszukiwane miejsca (kraj, droga, terytorium itp.)
  • sensor — określa czy żądanie pochodzi z urządzenia z czujnikiem lokalizacyjnym (GPS)

Dodatkowo, możemy użyć opcjonalnych parametrów:

  • bounds — za pomocą tego parametru można ustalić wielkość sąsiedztwa poszukiwanego punktu jaka ma być pokazana,
  • language — określa język, w jakim zwrócone są rezultaty,
  • region — dwu-znakowy kod regionu, mogący mieć wpływ na wielkość wyświetlanego obszaru,
  • components — filtrowanie wyświeltnaego obszaru do kraju, drogi czy terytorium.

Znamy już potrzebny URL i wiemy jakie parametry przyjmuje. Można zatem przeanalizować do przykładu:
http://maps.googleapis.com/maps/api/geocode/json?address=Plac+im.+Jana+Nowaka-Jeziorańskiego+3,Kraków&sensor=false
Po wklejeniu powyższego adresu do przeglądarki, otrzymujemy odpowiedź w formacie JSON. Znajduje się tam między innymi powiat, województwo, kraj, a także, co najważniejsze – gdzieś tam w środku znajdziemy współrzędne geograficzne poszukiwanego punktu:

            "location" : {
               "lat" : 50.06561980,
               "lng" : 19.946850
            }

W odpowiedzi znajduje się także zmienna status. Warto ją sprawdzić, żeby upewnić się, że zapytanie zwróciło jakiś wynik.

Zaczynamy programowanie ;-)

Okay, wiemy już jak wyglądają zapytania i odpowiedzi, spróbujmy zatem zautomatyzować trochę ten proces.

Załóżmy, że mamy prostą tabelę w bazie MySQL o następującej strukturze:

|------
|Column|Type|Null|Default
|------
|//**id**//|int(10) |No|
|name|varchar(80)|No|
|city|varchar(25)|No|
|street|varchar(80)|No|
|number|varchar(20)|No|
|lng|float|No|
|lat|float|No|

Zaczynamy od połączenia się z bazą i pobrania rekordów:

function connect() {
    global $pdo;
    $pdo = new PDO("mysql:host=localhost;dbname=test",
                   "root", "");
}
function get_stations_without_geocode() {
    global $pdo;
    $stmt = $pdo->prepare('
        SELECT city, street, number
        FROM railway_stations
        WHERE lng = 0 OR lat = 0'
    );
    $stmt->execute();
    return $stmt->fetchAll( PDO::FETCH_OBJ );
}

Wywołujemy powyższe funkcje, przy czym wynik drugiej przypisujemy do zmiennej:

connect();
$stations = get_stations_without_geocode();

Zmienna ta jest tablicą zawierającą obiekty o polach: city, street i number. Napiszmy pętlę przez wszystkie elementy tablicy:

foreach ($stations as $s) {
  $street = str_replace(" ", "+", $s->street);
  $city = str_replace(" ", "+", $s->city);
  $url =
   "http://maps.googleapis.com/maps/api/geocode/json?address=".
   $street . "+" .$s->number . "," . $city ."&sensor=false";
  $answer = file_get_contents($url);
  $answer = json_decode($answer);
  if ($answer->status == "OK") {
    $lng = $answer->results[0]->geometry->location->lng;
    $lat = $answer->results[0]->geometry->location->lat;
    update_geocode($s->id, $lng, $lat);
    echo "
" . $s->name .
       ": <span style="color: green;">OK!</span>

";
  } else {
    echo "
" . $s->name .
       ": <span style="color: red;">Wystąpił błąd ;-(</span>

";
  }
}
  • 2. i 3. linia powyższego listingu zamieniają spacje w nazwach ulicy i miejscowości na znaki „+”. Bez tego prawdopodobnie też zadziała, ale na taki właśnie format zamienia Google nasze zapytania, a plusy są dla człowieka bardziej czytelne niż %20.
  • 4. linijka tworzy adres URL z zapytaniem dla konkretnego miejsca odpowiadającego kolejnej iteracji pętli.
  • 7. linia to zapis do zmiennej $answer odpowiedzi. Po takiej operacji jest to dla nas zwykły łańcuch znaków, dlatego w 8. linijce dekodujemy go do postaci obiektowej.
  • W linii 9. sprawdzamy czy odpowiedź na nasze żądanie to OK. Jeśli tak to odczytujemy długość i szerokość geograficzną (linie 10. i 11.), a następnie zapisujemy je do bazy uaktualniając bieżący rekord (linia 12.).
  • Jeśli wystąpił błąd i API nie znalazło współrzędnych dla podanego adresu, wypisujemy informację o błędnym wpisie – linia 17.

Przytoczenia wymaga jeszcze funkcja update_geocode($id, $lng, $lat);:

function update_geocode($id, $lng, $lat) {
    global $pdo;
    $stmt = $pdo->prepare('
        UPDATE railway_stations
        SET lng = ?, lat = ?
        WHERE id = ?'
    );
    $stmt->execute(array($lng, $lat, $id));
}

Jak widać, jest to podstawowa funkcja aktualizująca rekordy w bazie danych.

Mamy zatem pobrane współrzędne geograficzne, możemy umieścić znaczniki na mapie:

var myLatlng = new google.maps.LatLng(50.06561980,19.946850);
var mapOptions = {
  zoom: 4,
  center: myLatlng,
  mapTypeId: google.maps.MapTypeId.ROADMAP,
}
var map = new google.maps.Map(
                   document.getElementById("map_canvas"),
                   mapOptions);

var marker = new google.maps.Marker({
  position: myLatlng,
  title:"Dworzec PKP Kraków Główny"
});
marker.setMap(map);

Podsumowanie

Odszukanie potrzebnych informacji w dokumentacji i napisanie właściwego kodu nie powinno zająć więcej 2-3 kwadranse. W rzeczonej sytuacji można było zaoszczędzić zatem dwa dni robocze, ale tak to bywa, gdy za „programowanie” zabiera się nieodpowiednia osoba…

SKOMENTUJ