New file |
0,0 → 1,554 |
<?php |
// declare(encoding='UTF-8'); |
/** |
* Classe recherchant des infos sur une zone géo ou bien des coordonnées. |
* |
* @internal Mininum PHP version : 5.2 |
* @category CEL |
* @package Services |
* @subpackage Bibliothèques |
* @version 0.1 |
* @author Mathias CHOUET <mathias@tela-botanica.org> |
* @author Jean-Pascal MILCENT <jpm@tela-botanica.org> |
* @author Aurelien PERONNET <aurelien@tela-botanica.org> |
* @license GPL v3 <http://www.gnu.org/licenses/gpl.txt> |
* @license CECILL v2 <http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt> |
* @copyright 1999-2014 Tela Botanica <accueil@tela-botanica.org> |
*/ |
class RechercheInfosZoneGeo extends Cel { |
|
public $maxTimeoutRequeteMondiale = 0; |
|
/** |
* Cas de la demande par lat et lon |
* array( |
* 'code_pays' => 'FR', |
* 'code_zone' => 'INSEE-D:35800', |
* 'type' => 'france', |
* 'nom' => 'Taulignan-les-oies' |
* ); |
* |
* array( |
* 'code_pays' => 'RU', |
* 'code_zone' => 'OSM-ID:4511574', |
* 'type' => 'monde', |
* 'nom' => 'Moscou' |
* ); |
* |
* Cas de la demande par nom |
* array( |
* 'lat': 40.372934, |
* 'lng': -3.690601, |
* 'nom': 'Madrid', |
* 'code_zone': 'OSM-ID:558474', |
* 'code_pays': 'ES', |
* 'type': 'monde' |
* ); |
* |
* array( |
* 'lat': 48.860799, |
* 'lng': 2.3457986, |
* 'nom': 'Paris', |
* 'code_zone': '75056', |
* 'code_pays': 'FR', |
* 'type': 'france' |
* ); |
* |
*/ |
|
//TODO: migrer tout ce qui concerne la zone géo dans cette classe |
public function obtenirInfosPourCoordonnees($coordonnees) { |
$infos_coord = null; |
// Test facile qui permet d'abord de tenter une localisation bien plus rapide si les coordonnées |
// sont dans la bounding box approximative de la France |
if($this->testerCoordonneesWgsFrance($coordonnees['latitude'], $coordonnees['longitude'])) { |
$infos_coord = $this->chercherInfosCommune('osm', $coordonnees['latitude'], $coordonnees['longitude']); |
if ($infos_coord == null) { |
// Sinon recherche par pays |
$infos_coord = $this->chercherInfosPays($coordonnees['latitude'], $coordonnees['longitude']); |
} |
} else { |
// Recherche par pays immédiate si en dehors de la bouding box française |
$infos_coord = $this->chercherInfosPays($coordonnees['latitude'], $coordonnees['longitude']); |
} |
return $infos_coord; |
} |
|
// gère aussi les id_zone_geo @TODO renommer la méthode et internationaliser |
// la gestion des codes |
public function obtenirInfosPourNom($nom, $pays, $code_departement=null, $id_zone=null) { |
$nom = trim($nom); |
$pays = trim($pays); |
$code_departement = trim($code_departement); |
|
$retour = false; |
// Dans le cas de la france on possède directement une table |
// avec les centroïdes des communes |
if($pays == 'FR' || $code_departement != "") { |
$retour = $this->chercherCentroideCommuneBdd($nom, $code_departement, $id_zone); |
} else { |
// Sinon méthode habituelle, un test simple suivi d'un test plus complexe |
// On cherche donc d'abord en France |
// @TODO c'est quoi ce if() mal ordonné ? |
if($pays == "") { |
$retour = $this->chercherCentroideCommuneBdd($nom, $code_departement, $id_zone); |
} |
// Si on ne trouve rien on teste dans le monde entier |
if($retour == false) { |
$retour = $this->effectuerRequeteGeocodingMondiale($nom, '2,3,4,5,6,7,8', 1, $pays, $id_zone); |
} |
} |
|
return $retour; |
} |
|
public function obtenirInfosPourCodeInseeCommune($code_insee) { |
$code_insee = trim($code_insee); |
|
$retour = $this->chercherCentroideCommuneInseeBdd($code_insee); |
|
return $retour; |
} |
|
/** |
* Renvoie tous les codes de zones pour le groupe $groupe |
*/ |
protected function obtenirZonesDuGroupe($groupe) { |
$requete = 'SELECT valeur as id_zone FROM cel_groupes_zones ' |
. 'WHERE id_groupe = ' . Cel::db()->proteger($groupe) |
. ' -- '.__FILE__.':'.__LINE__; |
|
$liste_zones = Cel::db()->requeter($requete); |
return $liste_zones; |
} |
|
/** |
* Renvoie des infos pour un groupes de zones, déclaré dans la table |
* cel_groupe_zones : toutes les infos des zones concernées, plus le carré |
* englobant (bounding box) et le centroïde de l'union des zones |
*/ |
public function obtenirInfosPourGroupeZonesFrance($groupe) { |
$groupe = trim($groupe); |
$retour = false; |
|
$zones = $this->obtenirZonesDuGroupe($groupe); |
$retour = array( |
'groupe' => array(), |
'zones' => array() |
); |
foreach ($zones as $zone) { |
$idZone = $zone['id_zone']; |
$retour['zones'][] = $this->obtenirInfosPourNom(null, null, null, $idZone); |
} |
|
// recherche bbox (de Bouygues Telecom) @TODO faire ça au dessus, en une passe ? |
$lngmin = 99999; |
$latmin = 99999; |
$lngmax = -99999; |
$latmax = -99999; |
foreach ($retour['zones'] as $zone) { |
$lat = $zone['lat']; |
$lng = $zone['lng']; |
if ($lat < $latmin) { $latmin = $lat; } |
if ($lat > $latmax) { $latmax = $lat; } |
if ($lng < $lngmin) { $lngmin = $lng; } |
if ($lng > $lngmax) { $lngmax = $lng; } |
} |
$retour['groupe']['bbox'] = array( |
'lat_min' => $latmin, |
'lat_max' => $latmax, |
'lng_min' => $lngmin, |
'lng_max' => $lngmax |
); |
// @TODO suggérer un niveau de zoom en fonction de la plus grande dimension |
// de la bounding-box (ne marchera pas dans tous les cas mais ce serait |
// déjà pas mal) |
$retour['groupe']['centroide'] = array( |
'lat' => ($latmin + $latmax) / 2, |
'lng' => ($lngmin + $lngmax) / 2 |
); |
|
return $retour; |
} |
|
public function chercherInfosCommune($projet, $latitude, $longitude) { |
|
$valeurs_params = '?lat='.$latitude.'&lon='.$longitude; |
$url_service = $this->config['cel']['url_service_geo_local'].$valeurs_params; |
$url_service = str_replace(',', '.', $url_service); |
|
$ch = curl_init($url_service); |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
$reponse = curl_exec($ch); |
|
$infos_coord = $this->traiterReponseServiceCommune($reponse); |
curl_close($ch); |
|
return $infos_coord; |
} |
|
public function chercherInfosPays($latitude, $longitude) { |
|
$valeurs_params = '?lat='.$latitude.'&lon='.$longitude; |
$url_service = $this->config['cel']['url_service_geo_mondial'].$valeurs_params; |
$url_service = str_replace(',', '.', $url_service); |
|
$ch = curl_init($url_service); |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
$reponse = curl_exec($ch); |
|
$res = json_decode($reponse, true); |
$retour = null; |
|
$retour = array( |
'code_zone' => '', |
'code_pays' => '', |
'nom' => '', |
'type' => '' |
); |
|
if(!empty($res)) { |
// Cas où l'on a recherché des coordonnées en France |
// On en profite pour remplir plus de données |
// que le simple pays si elles sont présentes |
if(isset($res[8]) && isset($res[8]['codeInsee'])) { |
$retour = array( |
'code_zone' => $res[8]['codeInsee'], |
'code_pays' => 'FR', |
'nom' => $res[8]['nom'], |
'type' => 'france' |
); |
} else { |
// Cas de la recherche en dehors de France |
// La zone de plus haut niveau est toujours un pays |
// (car le niveau de zone est limité à 2) |
$infos_pays = $res[min(array_keys($res))]; |
// La zone de niveau le plus bas est la "localité" |
// la plus précise qu'on a pu trouver |
$infos_localite = $res[max(array_keys($res))]; |
|
// Cas où l'on a trouvé un code pays |
if(!empty($infos_pays['codeIso31661'])) { |
$retour = array( |
'code_pays' => $infos_pays['codeIso31661'], |
'nom' => '' |
); |
} elseif(!empty($infos_pays['codeIso31662'])) { |
// Quelquefois la zone de plus haut niveau est une région ou une province |
// heureusement son code est de forme XX-YY ou XX est le code iso du pays ! |
$retour = array( |
'code_pays' => substr($infos_pays['codeIso31662'], 0, 2), |
'nom' => '' |
); |
} |
|
// Pas de sens de prendre un nom de localité si on a pas de pays |
if(!empty($retour['code_pays'])) { |
// Type sert à savoir quelle fonction de mise à jour on appellera |
$retour['type'] = 'monde'; |
$retour['nom'] = $infos_localite['intitule']; |
$retour['code_zone'] = $infos_localite['codeZoneGeo']; |
} |
} |
} |
|
return $retour; |
} |
|
public function traiterReponseServiceCommune($reponse) { |
$retour = null; |
$reponse = json_decode($reponse); |
// cas du service lion 1906 qui peut renvoyer plusieurs communes (on prend la première) |
if (is_array($reponse)) { |
$reponse = $reponse[0]; |
} |
if (isset($reponse->codeINSEE)) { |
// Type sert à savoir quelle fonction de mise à jour on appellera |
$retour = array( |
'code_zone' => $reponse->codeINSEE, |
'code_pays' => 'FR', |
'nom' => $reponse->nom, |
'type' => 'france' |
); |
} |
return $retour; |
} |
|
// @TODO gérer $id_zone (inactif pour l'instant) |
protected function effectuerRequeteGeocodingMondiale($nom, $niveau = '2,3,4,5,6,7,8', $limite = 1, $pays = null, $id_zone=null) { |
$url_sans_pays = $this->config['cel']['url_service_geo_mondial'].'?masque='.urlencode($nom).'&niveau='.$niveau.'&limite='.$limite; |
$url = $url_sans_pays.(!empty($pays) ? '&pays='.urlencode($pays) : ''); |
|
$ch = curl_init($url); |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
curl_setopt($ch, CURLOPT_TIMEOUT, $this->maxTimeoutRequeteMondiale); |
$reponse = curl_exec($ch); |
|
$res = @json_decode($reponse, true); |
|
$retour = array(); |
|
if(!empty($res)) { |
// cas de la localisation directe d'une commune |
if($limite == 1) { |
$retour = $this->traiterLigneResultatRequeteGeocodingMondiale($res); |
} else { |
// cas de l'autocompletion |
$retour = array(); |
foreach($res as $ligne) { |
$ligne_decodee = $this->traiterLigneResultatRequeteGeocodingMondiale($ligne); |
$param = array('nom' => $ligne_decodee['nom'], 'code' => $ligne_decodee['code_pays']); |
$retour[] = $this->formaterLigneResultat($param); |
} |
} |
} else if($limite == 1) { |
// Dans le cas où un pays a été demandé mais pas de résultat, on rentente la recherche sans |
// préciser de pays |
$res = @json_decode(file_get_contents($url_sans_pays), true); |
|
if(!empty($res)) { |
$retour = $this->traiterLigneResultatRequeteGeocodingMondiale($res); |
} |
} |
|
return $retour; |
} |
|
/** |
* Renvoie les infos d'une commune, incluant les coordonnées du centroïde, |
* en fonction du nom et/ou du département, et/ou de l'id_zone (plus fiable) |
*/ |
protected function chercherCentroideCommuneBdd($commune, $departement=null, $id_zone=null) { |
$commune_formatee = str_replace(array(' ', '-'), '_', trim($commune)); |
// clauses |
$clauses = array(); |
if ($commune != null) { |
$clauses[] = 'nom LIKE '.Cel::db()->proteger($commune_formatee); |
} |
if ($departement != null) { |
$departement = trim($departement, "*"); |
if (strlen($departement) > 2) { |
// @TODO piège : si deux communes ont le même nom dans deux |
// DROM-COM, la troncature à 2 chiffres sera insuffisante |
$departement = substr($departement, 0, 2); |
} |
$clauses[] = 'code LIKE '.Cel::db()->proteger($departement.'%'); |
} |
if ($id_zone != null) { |
$clauses[] = 'id_zone_geo = '.Cel::db()->proteger($id_zone); |
} |
$clauses = implode(' AND ', $clauses); |
|
$requete = 'SELECT utm_x, utm_y, utm_secteur, code FROM cel_zones_geo ' |
. 'WHERE ' . $clauses |
. ' -- '.__FILE__.':'.__LINE__; |
|
$commune_coordonnees = Cel::db()->requeter($requete); |
|
$retour = false; |
if ($commune_coordonnees && is_array($commune_coordonnees) && count($commune_coordonnees) > 0) { |
$lat_lon = $this->convertirUtmVersLatLong($commune_coordonnees[0]['utm_x'],$commune_coordonnees[0]['utm_y'],$commune_coordonnees[0]['utm_secteur']); |
|
$retour = array( |
'lat' => (float) $lat_lon['lat'], |
'lng' => (float) $lat_lon['lng'], |
'nom' => $commune, |
'code_zone' => $commune_coordonnees[0]['code'], |
'code_pays' => 'FR', |
'type' => 'france' |
); |
} |
return $retour; |
} |
|
/** |
* Renvoie les infos d'une commune, incluant les coordonnées du centroïde, |
* en fonction du code insee (plus fiable) |
*/ |
protected function chercherCentroideCommuneInseeBdd($code_insee) { |
$code_insee = trim($code_insee); |
$commune_coordonnees = array(); |
if ($code_insee != null) { |
$requete = 'SELECT `wgs84_latitude`, `wgs84_longitude`, `nom` FROM `cel_zones_geo` ' |
. 'WHERE `code`=' .Cel::db()->proteger($code_insee) |
. ' -- '.__FILE__.':'.__LINE__; |
|
$commune_coordonnees = Cel::db()->requeter($requete); |
} |
|
$retour = false; |
if ($commune_coordonnees && is_array($commune_coordonnees) && count($commune_coordonnees) > 0) { |
$retour = array( |
'lat' => (float) $commune_coordonnees[0]['wgs84_latitude'], |
'lng' => (float) $commune_coordonnees[0]['wgs84_longitude'], |
'nom' => $commune_coordonnees[0]['nom'], |
'code_zone' => $code_insee, |
'code_pays' => 'FR', |
'type' => 'france' |
); |
} |
|
return $retour; |
} |
public function obtenirListeInfosPourNom($lieu) { |
$lieux_retour = $this->obtenirListeCommunes($lieu); |
|
// S'il y a moins de 50 résultats on tente des les fusionner avec une recherche mondiale |
// avec 50 - n éléments (où n est la taille du tableau de communes) |
if(count($lieux_retour) < 50) { |
$lieux_mondiaux = $this->effectuerRequeteGeocodingMondiale($lieu, '2,3,4,5,6,7,8', 50 - count($lieux_retour)); |
usort($lieux_mondiaux, array($this, 'trierListeLieux')); |
$lieux_retour += $lieux_mondiaux; |
} |
|
// Suppression de possibles doublons @WARNING marche pas !!! |
//$lieux_retour = $this->reduireListeLieux($lieux_retour); |
|
return $lieux_retour; |
} |
|
public function obtenirListeCommunes($lieu) { |
$lieu_formate = $this->formaterChaineLieuPourRequete($lieu); |
$retour = array(); |
|
if ($this->estUneChaineRequeteValide($lieu_formate)) { |
$requete = 'SELECT DISTINCT nom, code '. |
'FROM cel_zones_geo '. |
'WHERE nom LIKE '.Cel::db()->proteger($lieu_formate.'%').' '. |
'ORDER BY nom '. |
'LIMIT 50 '. |
' -- '.__FILE__.':'.__LINE__; |
|
$liste_lieux = Cel::db()->requeter($requete); |
if ($liste_lieux) { |
foreach ($liste_lieux as $lieu_trouve) { |
$retour[] = $this->formaterLigneResultat($lieu_trouve); |
} |
} |
} |
return $retour; |
} |
|
/** |
* Fonctions utilitaires |
*/ |
|
// qui a écrit cette m*rde ? |
protected function reduireListeLieux($tableau) { |
$index = array(); |
foreach($tableau as $ligne) { |
// merci de mettre des commentaires !!! |
// ça enlève les 2 premières lettres ou chiffres du nom de lieu => wtf ? |
$index_loc = trim(preg_replace("/\([a-zA-Z0-9]{2}\)$/", "", $ligne[0])); |
if(!isset($index[$index_loc])) { |
$index[$index_loc] = $ligne; |
} else { |
if(mb_strlen($ligne[0]) > mb_strlen($index[$index_loc][0])) { |
$index[$index_loc] = $ligne; |
} else if(mb_strlen($ligne[0]) == mb_strlen($index[$index_loc][0])) { |
if(preg_match("/(.)* ([a-zA-Z0-9]{2})/", $ligne[0])) { |
$index[$index_loc] = $ligne; |
} |
} |
} |
} |
|
return array_values($index); |
} |
|
protected function trierListeLieux($a, $b) { |
return strcmp($a[0], $b[0]); |
} |
|
protected function testerCoordonneesWgsFrance($latitude, $longitude) { |
$coord_france = false; |
if ($latitude != '' && $longitude != '') { |
if ($latitude < 51.071667 && $latitude > 41.316667) { |
if ($longitude < 9.513333 && $longitude > -5.140278) { |
$coord_france = true; |
} |
} |
} |
return $coord_france; |
} |
|
protected function traiterLigneResultatRequeteGeocodingMondiale($res) { |
|
$lat = ""; |
$lon = ""; |
$nom = ""; |
$code =""; |
$code_pays=""; |
$type=""; |
|
if(!empty($res)) { |
$code_pays = $this->extraireCodePays($res); |
|
$nom = $res['intitule']; |
$lat = $res['centre_lat']; |
$lon = $res['centre_lng']; |
$type = $code_pays == 'FR' || $code_pays == 'FX' ? 'france' : 'monde'; |
|
$retour = array( |
'lat' => (float)$lat, |
'lng' => (float)$lon, |
'nom' => $nom, |
'code_zone' => $code, |
'code_pays' => $code_pays, |
'type' => $type |
); |
} |
|
return $retour; |
} |
|
protected function convertirUtmVersLatLong($x, $y, $sector) { |
$convertisseur = new gPoint(); |
$convertisseur->setUTM($x, $y, $sector); |
$convertisseur->convertTMtoLL(); |
|
$lat_long = array(); |
$lat_long['lat'] = str_replace(',','.',$convertisseur->Lat()); |
$lat_long['lng'] = str_replace(',','.',$convertisseur->Long()); |
return $lat_long; |
} |
|
protected function extraireCodePays($infos_pays) { |
$code = ""; |
if(!empty($infos_pays['codeIso31661'])) { |
$code = $infos_pays['codeIso31661']; |
} elseif(!empty($infos_pays['codeIso31662'])) { |
// Si pas de code pays, le sous code peut exister et content le code pays |
// sous la forme codepays-souscode |
$code = substr($infos_pays['codeIso31662'], 0, 2); |
} |
return $code; |
} |
|
protected function estUneChaineRequeteValide($lieu) { |
return (strlen($lieu) > 0) && ($lieu != '%'); |
} |
|
protected function formaterLigneResultat($ligne) { |
if(empty($ligne['code']) || $ligne['code'] == null) { |
$res = array($ligne['nom'], ''); |
} else { |
$res = array($ligne['nom'].' ('.substr(sprintf('%02s', $ligne['code']),0,2).')', $ligne['code']); |
} |
return $res; |
} |
|
/** |
* Remplace les * par % pour faire des recherches floues |
* Remplace les + par _ (nginx envoie des "+" dans l'URL à la place des espaces) |
* Remplace les espaces et les - par _ car les noms de communes peuvent avoir des espaces ou des tirets |
* @param string $lieu |
* @return string le lieu formaté pour la recherche |
*/ |
protected function formaterChaineLieuPourRequete($lieu) { |
$lieu = ltrim($lieu); |
$lieu = preg_replace('/\*+/', '%', $lieu); |
$lieu = str_replace(['+', ' ', '-'], '_', $lieu); |
return $lieu; |
} |
} |