Google-map ( 1 article - Voir la liste )

Astuce Géocoder une adresse et récupérer les limites d'une ville

Voici une classe utilitaire pour géocoder une adresse et/ou récupérer les limites (= bounds) d'une ville. Elle utilise l'API Google Map.

/**
 * Classe utilitaire de géocodage.
 *
 * Permet de :
 *  - géocoder simplement une adresse auprès de l'API Google Map.
 *  - récupérer les limites d'une ville.
 *
 * L'appel à l'API Google Map utilise curl.
 *
 * @class Geocoder
 */
class Geocoder {

  /**
   * L'URL d'appel au service de geocodage de Google.
   *
   * @const string
   */
  const BASE_URL = 'https://maps.google.com/maps/api/geocode/json';

  /**
   * Clé d'API Google Map.
   *
   * @var string
   */
  private $apiKey;

  /**
   * ID client à utiliser avec la clé privée ci-dessous.
   *
   * @var string
   */
  private $clientID;

  /**
   * Clé privée permettant de dépasser la limitation de 2000 requêtes/jour.
   *
   * @var string
   */
  private $privateKey;

  /**
   * Copnstructeur.
   *
   * @param string $api_key
   *   Clé d'API Google Map.
   * @param string $client_id
   *   ID client pour l'API. Laisser vide pour une utilisation classique.
   * @param string $private_key
   *   Clé secrète associéeà 'ID client. Laisser vide pour
   *   une utilisation classique.
   */
  public function __construct($api_key, $client_id = '', $private_key = '') {
    $this->apiKey = $api_key;
    $this->clientID = $client_id;
    $this->privateKey = $private_key;
  }

  /**
   * Retourne le nom d'une ville à partir du tableau de résultat de géocodage.
   *
   * @param array $geocode_result
   *   Tableau de résultat d'un géocodage.
   *
   * @return string
   *   Un tableau dont les clés sont 'top', 'right, 'bottom' et 'left'
   */
  public function getName(array $geocode_result) {

    $city_name = '';
    $address_components = $geocode_result['address_components'];

    $i = 0;
    $length = count($address_components);

    while (empty($city_name) && $i < $length) {

      if (!empty($address_components[$i]['types'][0]) && $address_components[$i]['types'][0] == 'locality') {
        $city_name = $geocode_result['address_components'][$i]['long_name'];
      }

      $i++;
    }

    return $city_name;
  }

  /**
   * Retourne les limites d'une ville.
   *
   * @param array $geocode_result
   *   Tableau de résultat d'un géocodage.
   *
   * @return array
   *   Un tableau dont les clés sont 'top', 'right, 'bottom' et 'left'
   */
  public function getBounds(array $geocode_result) {

    $bounds = [];

    if (!empty($geocode_result['geometry']['bounds'])) {

      $geo_bounds = $geocode_result['geometry']['bounds'];

      $bounds = [
        'top' => $geo_bounds['northeast']['lat'],
        'right' => $geo_bounds['northeast']['lng'],
        'bottom' => $geo_bounds['southwest']['lat'],
        'left' => $geo_bounds['southwest']['lng'],
      ];
    }
    return $bounds;
  }

  /**
   * Retourne le nom de la ville et ses limites.
   *
   * @param string $city_name
   *   Nom de la ville, avec son code postal.
   *
   * @return array
   *   Un tableau dont les clés sont 'name' et 'bounds'
   */
  public function getCity($city_name) {

    $city = NULL;

    $geocode = $this->geocode($city_name);

    if (!empty($geocode)) {

      $city['name'] = $this->getName($geocode);
      $city['bounds'] = $this->getBounds($geocode);
    }

    return $city;
  }

  /**
   * Retourne le résultat d'un géocodage sur l'adresse en argument.
   *
   * @param string $address
   *   Adresse à géocoder.
   *
   * @return array
   *   Le flux json de Google décodé
   */
  public function geocode($address) {

    $result = NULL;

    $query_parameters = [
      'address' => $address,
      'key' => $this->apiKey,
      'result_type' => 'locality',
      'components' => 'country:FR',
      'sensor' => 'false',
    ];

    $url = self::BASE_URL;
    $query_string = '';

    foreach ($query_parameters as $key => $value) {
      $query_string .= '&' . $key . '=' . urlencode($value);
    }
    $url .= '?' . substr($query_string, 1);

    if (!empty($this->clientID) && !empty($this->private_key)) {
      $url = $this->signUrl($url);
    }

    $json_response = $this->curlFileGetContent($url);
    $response = json_decode($json_response, TRUE);

    if ($response['status'] == 'OK') {
      $result = $response['results'][0];
    }

    return $result;
  }

  /**
   * Retourne l'URL en argument en y ajoutant le paramètre de signature.
   *
   * Ce paramètre de signature est construit à partir de la clé privée et de
   * l'ID client. Elle permet notamment de dépasser les 2000 requêtes/jour.
   *
   * @param string $unsigned_url
   *   URL à signer.
   *
   * @return string
   *   L'URL signée
   */
  private function signUrl($unsigned_url) {

    $url = parse_url($unsigned_url);

    $url_part_to_sign = $url['path'] . "?" . $url['query'];

    if (strpos($url_part_to_sign, 'client') === FALSE) {
      $url_part_to_sign .= '&client=' . $this->clientID;
      $unsigned_url .= '&client=' . $this->clientID;
    }

    // Décode la clé privée dans son format binaire
    $decoded_key = $this->decodeBase64UrlSafe($this->privateKey);

    // Crée une signature binaire via HMAC SHA1
    $signature = hash_hmac('sha1', $url_part_to_sign, $decoded_key, TRUE);
    $encoded_signature = $this->encodeBase64UrlSafe($signature);

    return $unsigned_url . '&signature=' . $encoded_signature;
  }

  /**
   * Appelle l'URL en argument via curl, et retourne le résultat de l'exécution.
   *
   * @param string $url
   *   URL à appeler.
   *
   * @return mixed
   *   Le retour de l'API
   */
  private function curlFileGetContent($url) {

    $c = curl_init();

    curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($c, CURLOPT_URL, $url);

    $content = curl_exec($c);
    curl_close($c);

    return $content;
  }

  /**
   * Encode une chaîne en base 64.
   *
   * Échappe les caractères gênants dans une URL (+ => - et / => _).
   *
   * @param string $value
   *   Chaîne à encoder.
   *
   * @return string
   *   La chaîne encodée
   */
  private function encodeBase64UrlSafe($value) {

    $base64_value = base64_encode($value);
    return str_replace(['+', '/'], ['-', '_'], $base64_value);
  }

  /**
   * Décode une chaîne en base 64 dont les caractères gênants sont échappés.
   *
   * @param string $value
   *   Chaîne à décoder.
   *
   * @return string
   *   La chaîne décodée
   */
  private function decodeBase64UrlSafe($value) {

    $value = str_replace(['-', '_'], ['+', '/'], $value);
    return base64_decode($value);
  }

}

Pour récupérer les limites de la ville de Paris, on aura par exemple :

$api_key = '<MON API KEY>';
$city = 'Paris';
$postal_code = 75000;
$geocoder = new Geocoder($api_key);
$geocoder->getCity($city . ' ' . $postal_code);

Remarques :

  • Cette classe a été créée initialement pour récupérer les limites de toutes les villes de France. Elle permet toutefois de construire l'appel Google pour géocoder n'importe quelle adresse.
  • La classe permet de gérer un compte client Google Map, permettant de dépasser les 2000 requête/jours. La classe a été réécrite pour l'API Google Map v3. Cette fonctionnalité n'a pas été retestée dans cette version.