Recherche "à proximité"
CrazyCat » 10/ 10/2009 15:53
PHP
|
Envoyer à un ami |
Fil des commentaires de ce billet
URL de rétrolien : http://www.g33k-zone.org/trackback/40
Ceci est la suite logique du billet précédent et permet de faire une recherche de points géolocalisés dans un rayon autour d'un point donné.
L'explication fera intervenir quelques légères notions mathématiques et géographiques, mais uniquement pour la compréhension du fonctionnement.
Théorie
Le point central (center)et les données à rechercher doivent contenir leur latitude et leur longitude, exprimées en degré décimal. La distance de recherche (radius) est exprimée en kilomètres.
La recherche s'effectue en 3 phases:
Calcul des points extrêmes
Les points extrêmes permettent de définir un carré dont le centre est le point initial et dont la largeur est le double de la distance de recherche. Pour cela, il faut convertir cette distance en degré décimal.
Sachant qu'un mile nautique représente une minute d'angle terrestre et correspond (approximativement) à 1852 mètres, on peut donc définir qu'un kilomètre est égal à 1/1,852 minute d'angle, soit 1(1,852*60) degré décimal.
La conversion du rayon donne donc: r = rayon / (1.852*60)
Le point central est représenté par lat et lng.
Les points extrêmes sont donc:
- latMin = lat - r
- latMax = lat + r
- lngMin = lng - r
- lngMax = lng + r
Recherche maximale des points
La table "lieux" est en fait la table des villes définie dans le billet Géolocalisation par IP.
La requête sera:
SELECT * FROM lieux WHERE ( lat<=latMax AND lat>=latMin AND lng<=lngMax AND lng>=lngMin )
Ceci va donc nous ramener tous les points contenus dans notre carré.
Affinement au rayon
Pour trouver les points contenus dans le rayon, il faut éliminer calculer la distance exacte entre le point central et chacun des points retournés, la formule est: ((lat - latx)² + (lng -lngx)²)^0.5
Cette valeur étant en degré décimal, il nous suffit ensuite de ne pas garder les points dont la distance est supérieure au rayon recherché.
En pratique
$center est un tableau à deux éléments, lat et lng.
$radius est la distance en kilomètres.
<?php /** * Retourne les points à proximité * Le point central est un tableau associatif de deux données: * lat => latitude * lng => longitude *@param array $center Point central *@param radius Rayon de recherche en kilomètres *@return array Tableau des points compris dans le rayon */ function searchProxy($center, $radius=10) { // Retour vide si $center n'est pas un tableau conforme if (!is_array($center) || empty($center['lat']) || empty($center['lng'])) return; // Retour vide si le rayon n'est pas valide if (intval($radius) == 0) return; // Conversion du rayon en degré décimal $radius = $radius / (60 * 1.852); // Recherche du carré maximal $query = "SELECT * FROM lieux WHERE ( lat<=".($center['lat'] + $radius)." AND lat>=".($center['lat'] - $radius)." AND lng<=".($center['lng'] + $radius)." AND lng >=".($center['lng'] - $radius)." )"; $res = mysql_query( $query ); if (mysql_num_rows($res)==0) return; $points = array(); // Elimination des points hors du cercle while ($row = mysql_fetch_assoc($res)) { $dist = sqrt(pow(($center['lat'] - $row['lat']), 2) + pow(($center['lng'] - $row['lng']),2)); if ($dist>$radius) continue; $points[] = $row; } return $points; } ?>
Nota
Il aurait bien entendu été possible de ne tout faire qu'avec une seule requête, mais les calculs mathématiques en SQL (et particulièrement sur MySQL) prennent énormément de ressources et de temps. Voici tout de même la requête "globale":
SELECT * FROM lieux WHERE ( SQRT(POW((lat - ".$center['lat']."), 2) + POW((lng - ".$center['lng']."),2)) < ".$radius." )