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.