Calcul de la Distance entre deux coordonnées GPS avec MySQL
Du coté PHP
|
|
Calcul de la Distance entre deux coordonnées GPS avec MySQL
Beaucoup de développeurs utilisent des services de localisation comme Google Map pour rechercher des lieux et donner la distance pour s'y rendre a partir d'un point précis. A l'heure actuelle il est simple de construire soi même ses propres outils.
Une des solutions est d'utiliser ce snippet PHP puis d'extraire toutes les données pour calculer la distance, de trier par distance croissante et de stocker le nombre d'enregistrements à traiter. Cette solution est fort coûteuse en ressource système puisqu'elle nécessite d'extraire toutes les données. La base de données est vite saturée. En y ajoutant le traitement PHP pour trier et filtrer cela pose de sérieux problème de transfert entre le moteur php et les caches MYSQL vite surchargée.
Création de la Fonction MySQL pour calculer la distance en mètres entre 2 points
La solution idéale est d'utiliser la puissance des fonctions MySQL pour réaliser ce traitement complexe et d'extraire en une seule requête les données souhaitées sans risquer de surcharger le serveur.
Plusieurs parties sont à voir, dans un premier temps il faut trier et extraire un nombre limité d'enregistrements et MySQL fait cela très bien avec les options SORT BY et LIMIT. Ensuite il faut déterminer la distance entre deux points sur Terre.
Mathématiquement il s'agit de l'orthodromie qui est le chemin le plus court entre deux points d'une sphère. Dans un langage simple, nous dirions qu'il faut mesurer la longueur de l'arc de grand cercle passant par deux points.
Afin de calculer la distance entre 2 points il faut créer une fonction MySQL (nous parlons ici de procédure stockée) qui prend en paramètre la latitude et la longitude de 2 points (exprimées en degrés).
Pour créer cette fonction il faut soit passer par la console MySQL ou par phpMyAdmin.
Le plus simple est d’utiliser la console MySQL car l'opération se fait en 1 étape mais elle n'est pas toujours accessible (notamment sur des serveurs mutualisés).
C'est pour cela que nous vous proposons les 2 méthodes.
Première méthode depuis la console MySQL
A partir de la console MySQL (avec Wampserver 2.0, c’est dans le menu MySQL), voici les lignes qu’il faut entrer :
Une des solutions est d'utiliser ce snippet PHP puis d'extraire toutes les données pour calculer la distance, de trier par distance croissante et de stocker le nombre d'enregistrements à traiter. Cette solution est fort coûteuse en ressource système puisqu'elle nécessite d'extraire toutes les données. La base de données est vite saturée. En y ajoutant le traitement PHP pour trier et filtrer cela pose de sérieux problème de transfert entre le moteur php et les caches MYSQL vite surchargée.
Création de la Fonction MySQL pour calculer la distance en mètres entre 2 points
La solution idéale est d'utiliser la puissance des fonctions MySQL pour réaliser ce traitement complexe et d'extraire en une seule requête les données souhaitées sans risquer de surcharger le serveur.
Plusieurs parties sont à voir, dans un premier temps il faut trier et extraire un nombre limité d'enregistrements et MySQL fait cela très bien avec les options SORT BY et LIMIT. Ensuite il faut déterminer la distance entre deux points sur Terre.
Mathématiquement il s'agit de l'orthodromie qui est le chemin le plus court entre deux points d'une sphère. Dans un langage simple, nous dirions qu'il faut mesurer la longueur de l'arc de grand cercle passant par deux points.
Afin de calculer la distance entre 2 points il faut créer une fonction MySQL (nous parlons ici de procédure stockée) qui prend en paramètre la latitude et la longitude de 2 points (exprimées en degrés).
Pour créer cette fonction il faut soit passer par la console MySQL ou par phpMyAdmin.
Le plus simple est d’utiliser la console MySQL car l'opération se fait en 1 étape mais elle n'est pas toujours accessible (notamment sur des serveurs mutualisés).
C'est pour cela que nous vous proposons les 2 méthodes.
Première méthode depuis la console MySQL
A partir de la console MySQL (avec Wampserver 2.0, c’est dans le menu MySQL), voici les lignes qu’il faut entrer :
|
.01 .02 .03 .04 .05 .06 .07 .08 .09 .10 .11 .12 .13 .14 .15 .16 .17 .18 .19 .20 .21 .22 .23 |
DELIMITER | DROP FUNCTION IF EXISTS get_distance_metres| CREATE FUNCTION get_distance_metres (lat1 DOUBLE, lng1 DOUBLE, lat2 DOUBLE, lng2 DOUBLE) RETURNS DOUBLE BEGIN DECLARE rlo1 DOUBLE; DECLARE rla1 DOUBLE; DECLARE rlo2 DOUBLE; DECLARE rla2 DOUBLE; DECLARE dlo DOUBLE; DECLARE dla DOUBLE; DECLARE a DOUBLE; SET rlo1 = RADIANS(lng1); SET rla1 = RADIANS(lat1); SET rlo2 = RADIANS(lng2); SET rla2 = RADIANS(lat2); SET dlo = (rlo2 - rlo1) / 2; SET dla = (rla2 - rla1) / 2; SET a = SIN(dla) * SIN(dla) + COS(rla1) * COS(rla2) * SIN(dlo) * SIN(dlo); RETURN (6378137 * 2 * ATAN2(SQRT(a), SQRT(1 - a))); END| DELIMITER ; |
La première ligne supprime la fonction stockée si elle existe. La seconde ligne permet de changer le délimiteur séparant les instructions (il est obligatoire de remplacer le délimiteur ";" par défaut pour qu'il ne soit pas confondu avec les points virgule en fin d'instruction de la fonction (dans le bloc BEGIN/END). L'instruction CREATE est donc terminée par le nouveau délimiteur "|" (après END).
Seconde méthode depuis phpMyAdmin
A partir de phpMyAdmin l'opération se fait à partir de l'onglet SQL en saisissant les lignes suivantes :
|
.01 .02 .03 .04 .05 .06 .07 .08 .09 .10 .11 .12 .13 .14 .15 .16 .17 .18 .19 .20 .21 |
DROP FUNCTION IF EXISTS get_distance_metres| CREATE FUNCTION get_distance_metres (lat1 DOUBLE, lng1 DOUBLE, lat2 DOUBLE, lng2 DOUBLE) RETURNS DOUBLE BEGIN DECLARE rlo1 DOUBLE; DECLARE rla1 DOUBLE; DECLARE rlo2 DOUBLE; DECLARE rla2 DOUBLE; DECLARE dlo DOUBLE; DECLARE dla DOUBLE; DECLARE a DOUBLE; SET rlo1 = RADIANS(lng1); SET rla1 = RADIANS(lat1); SET rlo2 = RADIANS(lng2); SET rla2 = RADIANS(lat2); SET dlo = (rlo2 - rlo1) / 2; SET dla = (rla2 - rla1) / 2; SET a = SIN(dla) * SIN(dla) + COS(rla1) * COS(rla2) * SIN(dlo) * SIN(dlo); RETURN (6378137 * 2 * ATAN2(SQRT(a), SQRT(1 - a))); END| |
Il ne faut pas oublier de changer le délimiteur ";" par défaut et mettre "|" (voir copie d'écran). Ce délimiteur permet de séparer plusieurs instructions SQL. Si vous laissez le délimiteur par défaut vous aller avoir une erreur de syntaxe car l'instruction CREATE FUNCTION contient des ";" dans son bloc BEGIN/END.
Voir image:

Utilisation de la fonction avec une requête MySQL
Maintenant que la fonction est créée, nous pouvons l'utiliser. A noter qu'elle s'utilise comme n'importe quelle autre fonction MySQL existante.
Recherchons par exemple les 10 restaurants les plus proches de la Tour Eiffel à Paris (latitude : 48.858205, longitude : 2.294359) et dont la distance est inférieure à 1 kilomètre.
La base de données contient une table "restaurants". Cette table contient, entre autre, les champs "lat" et "lng" correspondant à la latitude et à la longitude du restaurant.
La requête SQL:
|
.01 .02 .03 .04 .05 .06 |
SELECT *, get_distance_metres('48.858205', '2.294359', lat, lng)
AS proximite
FROM restaurants
WHERE proximite < 1000 ORDER BY proximite ASC
LIMIT 10
|
Commentaires
Article vraiment intéressant et qui déporte le calcul à la source SQL à la place des langages de programmation.
Par contre, je ne comprends pas comment cette requête fonctionne :
J'obtient l'erreur suivante :
Par contre, je ne comprends pas comment cette requête fonctionne :
SELECT *,
get_distance_metres('48.858205', '2.294359', lat, lng) AS proximite FROM restaurants
WHERE proximite < 1000 ORDER BY proximite ASC LIMIT 10J'obtient l'erreur suivante :
#1054 - Unknown column 'proximite' in 'where clause'
Pas mal et très intéressant, toutefois impossible à appliquer sur mon serveur MySQL 5.0.44 qui me dit :
requête SQL:
CREATE FUNCTION get_distance_metres(
) RETURNS DOUBLE BEGIN DECLARE rlo1 DOUBLE;
MySQL a répondu:
requête SQL:
CREATE FUNCTION get_distance_metres(
lat1 DOUBLE,
lng1 DOUBLE,
lat2 DOUBLE,
lng2 DOUBLE
lng1 DOUBLE,
lat2 DOUBLE,
lng2 DOUBLE
) RETURNS DOUBLE BEGIN DECLARE rlo1 DOUBLE;
MySQL a répondu:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 3 Pourtant je rentre bien des ; il les transforme en , mais il n'aime pas la syntaxe... que faire ???Alex
L'idéal et de se connecter en ligne de commande et copier/coller le code SQL de la section Première méthode depuis la console MySQL afin de garder la déclaration et utilisation d'un délimiteur différent, le |
J'ai rencontré un autre problème ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) résolue sur placeoweb.
J'ai rencontré un autre problème ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable) résolue sur placeoweb.
Ennorme !
Merci beaucoup, tu me sauve la vie.
ps :
#1054 - Unknown column 'proximite' in 'where clause'
Ca parrais clair comme erreur non ? ajoute un champs 'proximite' dans ta table ...
Merci beaucoup, tu me sauve la vie.
ps :
#1054 - Unknown column 'proximite' in 'where clause'
Ca parrais clair comme erreur non ? ajoute un champs 'proximite' dans ta table ...
Super
Mais j'ai aussi le même problème
#1054 - Unknown column 'proximite' in 'where clause'
Il ne faut pas ajouter un champ dans la table car ce champ est généré dynamiquement pas la requete avec AS proximite.
Comment contournez vous ce problème merci
Mais j'ai aussi le même problème
#1054 - Unknown column 'proximite' in 'where clause'
Il ne faut pas ajouter un champ dans la table car ce champ est généré dynamiquement pas la requete avec AS proximite.
Comment contournez vous ce problème merci
Bonjour,
Tout d'abord merci pour ce code bien pratique.
Cependant je rencontre exactement le même problème qu'Alex sur 5.0.83.
Aie-je oublié quelque chose ou est-ce simplement pas compatible avec ma version de mysql ?
Merci d'avance.
Hugo
Tout d'abord merci pour ce code bien pratique.
Cependant je rencontre exactement le même problème qu'Alex sur 5.0.83.
Aie-je oublié quelque chose ou est-ce simplement pas compatible avec ma version de mysql ?
Merci d'avance.
Hugo
Avec phpMyAdmin, il ne faut pas oublier de remplacer le délimiteur ";" par "|" pour créer la fonction stockée.
Cette solution marche chez OVH mais il se peut que d'autres hébergeurs l'empêchent. Il semble impossible de créer la fonction stockée chez 1&1 par exemple.
Cette solution marche chez OVH mais il se peut que d'autres hébergeurs l'empêchent. Il semble impossible de créer la fonction stockée chez 1&1 par exemple.
Cette méthode est très pratique; comment pourrai-je ajouter un test dans la procédure stockée spécifiant:
Si
lat2 n'est pas null ou vide et lng2 n'est pas null ni vide
alors
calcul...
Merci encore pour cette excellente source!
Si
lat2 n'est pas null ou vide et lng2 n'est pas null ni vide
alors
calcul...
Merci encore pour cette excellente source!
Tu peux ajouter un test dans le WHERE de ton SELECT :
SELECT *, get_distance_metres('48.858205', '2.294359', lat, lng)
AS proximite
FROM restaurants
WHERE proximite < 1000
AND lat <> ''
AND lng <> ''
ORDER BY proximite ASC
LIMIT 10
Tout simple ;), j'étais parti sur bien plus compliqué, merci pour le retour !
J'ai appliqué votre méthode est j'ai bien créer la procédure get_distance_metre
J'ai testé la requête sql donné plus haut par thor76160 ça fonctionne bien il affiche bien la liste des villes dans un rayon de 10km autour de Rouen .
SELECT nom, get_distance_metres('49.433331', '1.08333', latitude, longitude) AS distance
FROM maps_ville
WHERE get_distance_metres('49.433331', '1.08333', latitude, longitude) <=10000
ORDER BY 2
si je voulais afficher dynamiquement une liste de villes par rapport au résultat d'une requête comme la suivante , ou dois je mettre les variables pour les colonnes longitude et latitude.
$colname_villetoto = "-1";
IF (isset($_GET['id_ville'])) { $colname_villetoto = (get_magic_quotes_gpc()) ? $_GET['id_ville'] : addslashes($_GET['id_ville']);
}
mysql_select_db($database_baseville, $baseville);
$query_villetoto = sprintf("SELECT id_ville, nom_ville, longitude, latitude, get_distance_metres( '49.0364', '3.45222', longitude, latitude )
AS distance FROM communes WHERE get_distance_metres
( '49.0364', '3.45222', longitude, latitude ) <=10000 ORDER BY 2
AND id_ville = %s", GetSQLValueString($colname_villetoto, "int"));
$villetoto = mysql_query($query_villetoto, $baseville) OR die(mysql_error());
$row_villetoto = mysql_fetch_assoc($villetoto);
$totalRows_villetoto = mysql_num_rows($villetoto);
merci à vous et bravo pour ce tutorial !
J'ai testé la requête sql donné plus haut par thor76160 ça fonctionne bien il affiche bien la liste des villes dans un rayon de 10km autour de Rouen .
SELECT nom, get_distance_metres('49.433331', '1.08333', latitude, longitude) AS distance
FROM maps_ville
WHERE get_distance_metres('49.433331', '1.08333', latitude, longitude) <=10000
ORDER BY 2
si je voulais afficher dynamiquement une liste de villes par rapport au résultat d'une requête comme la suivante , ou dois je mettre les variables pour les colonnes longitude et latitude.
$colname_villetoto = "-1";
IF (isset($_GET['id_ville'])) { $colname_villetoto = (get_magic_quotes_gpc()) ? $_GET['id_ville'] : addslashes($_GET['id_ville']);
}
mysql_select_db($database_baseville, $baseville);
$query_villetoto = sprintf("SELECT id_ville, nom_ville, longitude, latitude, get_distance_metres( '49.0364', '3.45222', longitude, latitude )
AS distance FROM communes WHERE get_distance_metres
( '49.0364', '3.45222', longitude, latitude ) <=10000 ORDER BY 2
AND id_ville = %s", GetSQLValueString($colname_villetoto, "int"));
$villetoto = mysql_query($query_villetoto, $baseville) OR die(mysql_error());
$row_villetoto = mysql_fetch_assoc($villetoto);
$totalRows_villetoto = mysql_num_rows($villetoto);
merci à vous et bravo pour ce tutorial !
j'ai trouvé une solution pour mon probleme posté ci dessus ,
quant les gens tapent dans le formulaire de recherche le nom de la ville ça envoie aussi la longitude et latitude d'un champ hidden, je récupère dans la page de résultat , je met en variable et dans la requête sql je met ces variables. ça marche.
SAUF que j'avais pas fait attention mais ça m'affiche aussi la ville cible.
par exemple pour Paris , ça affiche dans la liste des villes dans un rayon de 10 kilomètres la ville de....Paris
soit c'est une petite erreur dans la requête , soit on peut trouver une solution en affichant pas la ville d'où on est censé partir.. peut etre que c'est du à la petite marge d'erreur inerant à ce calcul de distance de deux points gps
quant les gens tapent dans le formulaire de recherche le nom de la ville ça envoie aussi la longitude et latitude d'un champ hidden, je récupère dans la page de résultat , je met en variable et dans la requête sql je met ces variables. ça marche.
SAUF que j'avais pas fait attention mais ça m'affiche aussi la ville cible.
par exemple pour Paris , ça affiche dans la liste des villes dans un rayon de 10 kilomètres la ville de....Paris
soit c'est une petite erreur dans la requête , soit on peut trouver une solution en affichant pas la ville d'où on est censé partir.. peut etre que c'est du à la petite marge d'erreur inerant à ce calcul de distance de deux points gps
Pour solutionner le problème du
#1054 - Unknown column 'proximite' in 'where clause'
il changer
WHERE proximite < 1000
par
HAVING proximite < 1000
et le mettre en fin de requête.
#1054 - Unknown column 'proximite' in 'where clause'
il changer
WHERE proximite < 1000
par
HAVING proximite < 1000
et le mettre en fin de requête.
pour ne pas afficher Paris en cherchant les villes autour de Paris il faut ajouter une condition du style : WHERE ... AND ville <> 'Paris'
Bonjour,
Si comme moi vous avez ce message d'erreur :
ajoutez DETERMINISTIC entre RETURNS DOUBLE et BEGIN
Bonne journée et merci pour la fonction :)
Si comme moi vous avez ce message d'erreur :
ERROR 1418 (HY000): This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)ajoutez DETERMINISTIC entre RETURNS DOUBLE et BEGIN
ou desactivez les logs binaires (ce qui est impossible pour moi car j'ai une réplication).
Bonne journée et merci pour la fonction :)
Ajouter un commentaire
Quelques articles qui devraient vous intéresser










Connexion
Les derniers!

