PHP: una classe per trovare le coordinate geografiche di una lista di localita’ con Google Maps

Questo post ha più di 1 anno. Il contenuto potrebbe essere obsoleto, non più completamente accessibile o mancante di alcune informazioni.

planisferoHo ricavato il codice di questa classe PHP modificando leggermente un chiarissimo esempio disponibile sul sito di Google Maps. L’esempio mostra come sia possibile inserire in un database MySQL le coordinate geografiche relative ad un archivio di località o indirizzi. Ho pensato di renderlo più versatile traducendolo in una classe che prende come input un array di indirizzi/località e restituisce in output un array di coordinate geografiche. Le API di Google Maps mettono a disposizione un servizio per la georeferenziazione che può essere utilizzato attraverso due differenti modalità:

  1. Attraverso una richiesta HTTP
  2. Attraverso l’oggetto GClientGeocoder

Le operazioni di georeferenziazione attraverso GClientGeocoder sono piuttosto onerose in termini di tempo e risorse, per questo motivo Google stesso suggerisce di utilizzare le richieste server side via HTTP.
La URI per accedere al servizio è: http://maps.google.com/maps/geo? e i parametri per completare la richiesta:

  • q – è l’indirizzo o località da georeferenziare
  • key – è la chiave per l’accesso a Google Maps (attivabile qui)
  • output – è il formato dell’output generato, i valori possibili sono: xml, kml, csv, json.

Il codice base è praticamente lo stesso dell’esempio fornito da Google al quale ho soltanto eliminato le parti di accesso al database MySQL. La classe compatibile con PHP5 (che è mostrata qui sotto), utilizza come output un file xml, mentre quella compatibile con PHP4 utilizza un output csv.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class geocodeaddr
{
  public $mapsHost = "maps.google.com";
  public $googleKey = "myGoogleMapsKey";
  public $dataArray;
  public $errorMsg = "";
 
  public function __construct($dataArray, $googleKey)
  {
    $this->dataArray = $dataArray;
    $this->googleKey = $googleKey;
  }
  public function getAddress()
  {
    $results = array();
    // Initialize delay in geocode speed
    $delay = 0;
    $base_url = "http://" . $this->mapsHost . "/maps/geo?output=xml" . "&key=" . 
                $this->googleKey . "&oe=utf-8";
    // Iterate through the rows, geocoding each address
    foreach ($this->dataArray as $address) {
      $geocode_pending = true;
        while ($geocode_pending) {
          $request_url = $base_url . "&q=" . urlencode($address);
          $xml = simplexml_load_file($request_url) or die("url not loading");
          $status = $xml->Response->Status->code;
          if (strcmp($status, "200") == 0) {
            // Successful geocode
            $geocode_pending = false;
            $coordinates = $xml->Response->Placemark->Point->coordinates;
            $coordinatesSplit = split(",", $coordinates);
            // Format: Longitude, Latitude
            $lng = $coordinatesSplit[0];
            $lat = $coordinatesSplit[1];
            $results[] = array("address" => $address, "lat" => $lat, "lng" => $lng);
          } else if (strcmp($status, "620") == 0) {
            // sent geocodes too fast
            $delay += 100000;
          } else {
            // failure to geocode
            $geocode_pending = false;
            $results[] = array("address" => $address, "lat" => 0, "lng" => 0);
            $this->errorMsg .= "Address ".$address.
                  " failed to geocoded. Received status ".$status."\n";
          }
        usleep($delay);
      }
    }
    return $results;
  }
}

Per comprendere meglio come utilizzare la struttura xml generata dal servizio di Google riporto un esempio utilizzando “Roma” come indirizzo di ricerca:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.0">
<Response>
  <name>Roma</name>
 
  <Status>
    <code>200</code>
    <request>geocode</request>
  </Status>
  <Placemark id="p1">
 
    <address>Roma, Roma (Lazio), Italy</address>
    <AddressDetails Accuracy="4" xmlns="urn:oasis:names:tc:ciq:xsdschema:xAL:2.0">
      <Country>
        <CountryNameCode>IT</CountryNameCode>
        <AdministrativeArea>
 
          <AdministrativeAreaName>Lazio</AdministrativeAreaName>
          <SubAdministrativeArea>
            <SubAdministrativeAreaName>Roma</SubAdministrativeAreaName>
            <Locality>
              <LocalityName>Roma</LocalityName>
 
            </Locality>
          </SubAdministrativeArea>
        </AdministrativeArea>
      </Country>
    </AddressDetails>
    <Point>
 
      <coordinates>12.482324,41.895466,0</coordinates>
    </Point>
  </Placemark>
</Response>
</kml>

Notate che ho utilizzato un piccolo hack non documentato per evitare “warning” con caratteri particolari, tipo le lettere accentate, che si verifica scegliendo il formato xml. Il trucco consiste nel forzare l’encoding dei caratteri nel formato utf-8 aggiungendo alla querystring questo parametro: &oe=utf-8

Un altro possibile errore potrebbe verificarsi utilizzando la classe versione PHP4. Infatti la funzione utilizzata per “recuperare” il file csv via HTTP è file_get_contents(). Questa funzione è compatibile solo con le versioni di PHP 4.3.x e superiori, e solo se è abilitata la direttiva del php.ini: allow_url_fopen. Per questo motivo nel codice della classe geocoder4.class.php, ho inserito un metodo che utilizza le librerie cURL in alternativa a file_get_contents(). Il metodo deve essere decommentato insieme alla riga di codice che lo richiama nel caso in cui si voglia usare cURL (verificate che almeno cURL sia abilitato!!).

Questo, invece, è uno spezzone di codice che mostra come instanziare la classe in PHP5 e generare una tabella di risultati: (un esempio di utilizzo della classe è visibile qui)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
require("geocoder5.class.php");
$data = array("Roma, RM, Italy", 
              "Milano, MI, Italy", 
              "Bologna, BO, Italy", 
              "Napoli, NA, Italy", 
              "Palermo, PA, Italy");
$gmpKey = "ABQIAAAAzr2EBOXUKnm..."; // Inserire la Google key corretta!
$test = new geocodeaddr($data,$gmpKey);
$geocodeResults = $test->getAddress();
echo("<table class='datatable' cellspacing='0' 
      summary='Risultati georeferenziazione'>");
echo("<tr><th scope='col'>Indirizzo</th>
      <th scope='col'>Latitudine</th><th scope='col'>Longitudine</th></tr>");
foreach ($geocodeResults as $rowResult){
  echo('<tr><td>'.$rowResult['address'].'</td><td>'.$rowResult['lat'].
        '</td><td>'.$rowResult['lng'].'</td></tr>');
}
echo("</table>");
echo $test->errorMsg;
...

Download

Il pacchetto completo delle classi sia per PHP5 che per PHP4 ed il file di esempio è scaricabile qui.

Conclusioni:

Non è stato difficile adattare l’esempio fornito da Google Maps alle classi, ci è voluto un po’ tempo, invece per assicurarsi che la classe per il PHP4 funzionasse senza errori con diversi tipi di configurazione dei server. Per questo motivo, consiglio a tutti di passare al PHP5 se e ove possibile!
Infine, sarebbe interessante utilizzare la tecnologia Ajax per recuperare in modo asincrono le coordinate, ed eliminare in questo modo il fastidioso stato di attesa del browser finché non sono state georeferenziate tutte le località. Tempo permettendo, è un miglioramento che vorrei implementare.

Riferimenti ed approfondimenti:

Questo articolo è stato pubblicato in Informatica e contrassegnato con , . Aggiungi ai segnalibri il permalink.