3 votes

Distance entre deux coordonnées, comment puis-je simplifier cela et/ou utiliser une technique différente ?

J'ai besoin d'écrire une requête qui me permette de trouver tous les lieux situés dans un intervalle (miles) à partir d'un lieu donné.

Le tableau est le suivant :

id  |  name  |  lat  |  lng 

J'ai donc fait des recherches et j'ai trouvé : voici ma présentation sql

Je l'ai testé sur un tableau d'environ 100 lignes et j'en aurai bien d'autres ! - Il doit être évolutif.

J'ai d'abord essayé quelque chose de plus simple comme ça :

//just some test data this would be required by user input    
set @orig_lat=55.857807; set @orig_lng=-4.242511; set @dist=10;

SELECT *, 3956 * 2 * ASIN(
          SQRT( POWER(SIN((orig.lat - abs(dest.lat)) * pi()/180 / 2), 2) 
              + COS(orig.lat * pi()/180 ) * COS(abs(dest.lat) * pi()/180)  
              * POWER(SIN((orig.lng - dest.lng) * pi()/180 / 2), 2) )) 
          AS distance
  FROM locations dest, locations orig
 WHERE orig.id = '1'
HAVING distance < 1
 ORDER BY distance;

Cela a retourné des rangs dans environ 50ms ce qui est plutôt bien ! Toutefois, ce processus ralentirait considérablement à mesure que le nombre de rangs augmente.

EXPLAIN montre qu'il n'utilise que la clé PRIMAIRE, ce qui est évident.


Puis, après avoir lu l'article lié ci-dessus . J'ai essayé quelque chose comme ça :

// defining variables - this when made into a stored procedure will call
// the values with a SELECT query.
set @mylon = -4.242511;
set @mylat = 55.857807;
set @dist = 0.5;

-- calculate lon and lat for the rectangle:
set @lon1 = @mylon-@dist/abs(cos(radians(@mylat))*69);
set @lon2 = @mylon+@dist/abs(cos(radians(@mylat))*69);
set @lat1 = @mylat-(@dist/69); 
set @lat2 = @mylat+(@dist/69);

-- run the query:

SELECT *, 3956 * 2 * ASIN(
          SQRT( POWER(SIN((@mylat - abs(dest.lat)) * pi()/180 / 2) ,2)
              + COS(@mylat * pi()/180 ) * COS(abs(dest.lat) * pi()/180)
              * POWER(SIN((@mylon - dest.lng) * pi()/180 / 2), 2) ))
          AS distance
  FROM locations dest
 WHERE dest.lng BETWEEN @lon1 AND @lon2
   AND dest.lat BETWEEN @lat1 AND @lat2
HAVING distance < @dist
 ORDER BY distance;

L'heure de cette requête est environ 240 ms Ce n'est pas trop mal, mais c'est plus lent que le précédent. Mais je peux imaginer qu'avec un nombre de rangs beaucoup plus élevé, cela irait plus vite. Cependant, une EXPLAIN montre les clés possibles comme lat , lng o PRIMARY et utilisé PRIMARY .

Comment puis-je faire mieux ???

Je sais que je pourrais stocker la lat lng comme un POINT() ; mais je n'ai pas trouvé beaucoup de documentation à ce sujet qui montre si c'est plus rapide ou précis ?

Toute autre idée serait acceptée avec plaisir !

Merci beaucoup !

-Stefan


UPDATE :

Comme Jonathan Leffler l'a souligné, j'avais fait quelques erreurs que je n'avais pas remarquées :

J'avais seulement mis abs() sur une des valeurs de lat. Dans la deuxième requête, j'ai également utilisé une recherche par identifiant dans la clause WHERE, alors que ce n'était pas nécessaire. La première requête était purement expérimentale, la seconde est plus susceptible d'être utilisée en production.

Après ces changements EXPLAIN montre que la clé utilise maintenant lng et le temps moyen de réponse autour de 180ms maintenant, ce qui est une amélioration.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X