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."
)