Trouver la latitude / longitude la plus proche avec une requête SQL


173

J'ai la latitude et la longitude et je veux extraire l'enregistrement de la base de données, qui a la latitude et la longitude les plus proches en fonction de la distance, si cette distance est plus longue que celle spécifiée, ne la récupérez pas.

Structure de la table:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
C'est en quelque sorte une copie de la question de recherche de proximité .
Darius Bacon

1
Il y a un ensemble de diapositives d'Alexander Rubin sur la recherche géographique (proximité) avec MySQL (lien PDF)
Martijn Pieters

Réponses:


209
SELECT latitude, longitude, SQRT(
    POW(69.1 * (latitude - [startlat]), 2) +
    POW(69.1 * ([startlng] - longitude) * COS(latitude / 57.3), 2)) AS distance
FROM TableName HAVING distance < 25 ORDER BY distance;

[starlat] et [startlng] est la position où commencer à mesurer la distance.


49
Juste une note de performance, il est préférable de ne pas saisir la variable de distance, mais plutôt de mettre au carré la valeur du test `` 25 '' ... plus tard, affichez les résultats qui ont réussi si vous avez besoin d'afficher la distance
sradforth

9
Quelle serait la même requête pour la distance en mètres? (qui est actuellement en miles, non?)
httpete

8
Quelle mesure est-ce 25?
Steffan Donal

16
Juste pour clarifier ici, 69,1 est le facteur de conversion des miles en degrés de latitude. 57,3 est environ 180 / pi, donc c'est la conversion de degrés en radians, pour la fonction cosinus. 25 est le rayon de recherche en miles. C'est la formule à utiliser lors de l'utilisation des degrés décimaux et des milles terrestres.
John Vance

8
De plus, il ne prend pas en compte la courbure de la terre. Ce ne serait pas un problème pour les rayons de recherche courts. Sinon, les réponses d'Evan et d'Igor sont plus complètes.
John Vance

63

La solution de Google:

Créer la table

Lorsque vous créez la table MySQL, vous souhaitez porter une attention particulière aux attributs lat et lng. Avec les capacités de zoom actuelles de Google Maps, vous ne devriez avoir besoin que de 6 chiffres de précision après la décimale. Pour conserver au minimum l'espace de stockage requis pour votre table, vous pouvez spécifier que les attributs lat et lng sont des flottants de taille (10,6). Cela permettra aux champs de stocker 6 chiffres après la décimale, plus jusqu'à 4 chiffres avant la décimale, par exemple -123,456789 degrés. Votre table doit également avoir un attribut id pour servir de clé primaire.

CREATE TABLE `markers` (
  `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  `name` VARCHAR( 60 ) NOT NULL ,
  `address` VARCHAR( 80 ) NOT NULL ,
  `lat` FLOAT( 10, 6 ) NOT NULL ,
  `lng` FLOAT( 10, 6 ) NOT NULL
) ENGINE = MYISAM ;

Remplir la table

Après avoir créé la table, il est temps de la remplir avec des données. Les exemples de données ci-dessous concernent environ 180 pizzarias disséminées aux États-Unis. Dans phpMyAdmin, vous pouvez utiliser l'onglet IMPORT pour importer divers formats de fichiers, y compris CSV (valeurs séparées par des virgules). Les feuilles de calcul Microsoft Excel et Google sont toutes deux exportées au format CSV, ce qui vous permet de transférer facilement des données de feuilles de calcul vers des tables MySQL via l'exportation / l'importation de fichiers CSV.

INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Frankie Johnnie & Luigo Too','939 W El Camino Real, Mountain View, CA','37.386339','-122.085823');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Amici\'s East Coast Pizzeria','790 Castro St, Mountain View, CA','37.38714','-122.083235');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Kapp\'s Pizza Bar & Grill','191 Castro St, Mountain View, CA','37.393885','-122.078916');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Round Table Pizza: Mountain View','570 N Shoreline Blvd, Mountain View, CA','37.402653','-122.079354');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Tony & Alba\'s Pizza & Pasta','619 Escuela Ave, Mountain View, CA','37.394011','-122.095528');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Oregano\'s Wood-Fired Pizza','4546 El Camino Real, Los Altos, CA','37.401724','-122.114646');

Recherche d'emplacements avec MySQL

Pour rechercher des emplacements dans votre table de marqueurs qui se trouvent à une certaine distance de rayon d'une latitude / longitude donnée, vous pouvez utiliser une instruction SELECT basée sur la formule Haversine. La formule Haversine est généralement utilisée pour calculer les distances des grands cercles entre deux paires de coordonnées sur une sphère. Une explication mathématique approfondie est donnée par Wikipedia et une bonne discussion de la formule en ce qui concerne la programmation est sur le site de Movable Type.

Voici l'instruction SQL qui trouvera les 20 emplacements les plus proches dans un rayon de 25 miles de la coordonnée 37, -122. Il calcule la distance en fonction de la latitude / longitude de cette ligne et de la latitude / longitude cible, puis demande uniquement les lignes où la valeur de distance est inférieure à 25, classe la requête entière par distance et la limite à 20 résultats. Pour rechercher en kilomètres au lieu de miles, remplacez 3959 par 6371.

SELECT 
id, 
(
   3959 *
   acos(cos(radians(37)) * 
   cos(radians(lat)) * 
   cos(radians(lng) - 
   radians(-122)) + 
   sin(radians(37)) * 
   sin(radians(lat )))
) AS distance 
FROM markers 
HAVING distance < 28 
ORDER BY distance LIMIT 0, 20;

Celui-ci consiste à trouver des latitudes et des longitudes sur une distance inférieure à 28 milles.

Une autre consiste à les trouver à une distance comprise entre 28 et 29 miles:

SELECT 
id, 
(
   3959 *
   acos(cos(radians(37)) * 
   cos(radians(lat)) * 
   cos(radians(lng) - 
   radians(-122)) + 
   sin(radians(37)) * 
   sin(radians(lat )))
) AS distance 
FROM markers 
HAVING distance < 29 and distance > 28 
ORDER BY distance LIMIT 0, 20;

https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map


1
Devrait-il être HAVING distance < 25comme nous interrogeons des emplacements dans un rayon de 25 miles?
Vitalii Elenhaupt

il devrait être> 25, puis il recherchera tous les enregistrements
vidur punj

êtes-vous sûr que @vidurpunj a environ 25 ans?
Amranur Rahman le

J'ai essayé la requête sql en utilisant: distance <25 mais je n'ai trouvé aucun résultat .. car les distances des marqueurs d'échantillon sont toutes supérieures à 25 ...
IbrahimShendy

37, coordonnée -122, cette latitude et cette longitude correspondent-elles à la position à partir de laquelle nous devons trouver la distance?
Prasobh.Kollattu

28

Voici ma solution complète implémentée en PHP.

Cette solution utilise la formule Haversine telle que présentée dans http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Il est à noter que la formule Haversine connaît des faiblesses autour des pôles. Cette réponse montre comment implémenter la formule de vincenty Great Circle Distance pour contourner ce problème , mais j'ai choisi d'utiliser simplement Haversine car c'est assez bon pour mes besoins.

Je stocke la latitude comme DECIMAL (10,8) et la longitude comme DECIMAL (11,8). Espérons que cela aide!

showClosest.php

<?PHP
/**
 * Use the Haversine Formula to display the 100 closest matches to $origLat, $origLon
 * Only search the MySQL table $tableName for matches within a 10 mile ($dist) radius.
 */
include("./assets/db/db.php"); // Include database connection function
$db = new database(); // Initiate a new MySQL connection
$tableName = "db.table";
$origLat = 42.1365;
$origLon = -71.7559;
$dist = 10; // This is the maximum distance (in miles) away from $origLat, $origLon in which to search
$query = "SELECT name, latitude, longitude, 3956 * 2 * 
          ASIN(SQRT( POWER(SIN(($origLat - latitude)*pi()/180/2),2)
          +COS($origLat*pi()/180 )*COS(latitude*pi()/180)
          *POWER(SIN(($origLon-longitude)*pi()/180/2),2))) 
          as distance FROM $tableName WHERE 
          longitude between ($origLon-$dist/cos(radians($origLat))*69) 
          and ($origLon+$dist/cos(radians($origLat))*69) 
          and latitude between ($origLat-($dist/69)) 
          and ($origLat+($dist/69)) 
          having distance < $dist ORDER BY distance limit 100"; 
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_assoc($result)) {
    echo $row['name']." > ".$row['distance']."<BR>";
}
mysql_close($db);
?>

./assets/db/db.php

<?PHP
/**
 * Class to initiate a new MySQL connection based on $dbInfo settings found in dbSettings.php
 *
 * @example $db = new database(); // Initiate a new database connection
 * @example mysql_close($db); // close the connection
 */
class database{
    protected $databaseLink;
    function __construct(){
        include "dbSettings.php";
        $this->database = $dbInfo['host'];
        $this->mysql_user = $dbInfo['user'];
        $this->mysql_pass = $dbInfo['pass'];
        $this->openConnection();
        return $this->get_link();
    }
    function openConnection(){
    $this->databaseLink = mysql_connect($this->database, $this->mysql_user, $this->mysql_pass);
    }

    function get_link(){
    return $this->databaseLink;
    }
}
?>

./assets/db/dbSettings.php

<?php
$dbInfo = array(
    'host'      => "localhost",
    'user'      => "root",
    'pass'      => "password"
);
?>

Il peut être possible d'augmenter les performances en utilisant une procédure stockée MySQL comme suggéré par l'article "Geo-Distance-Search-with-MySQL" posté ci-dessus.

J'ai une base de données d'environ 17 000 emplacements et le temps d'exécution de la requête est de 0,054 seconde.


Comment puis-je obtenir la distance en km ou en mètres? Cordialement!
chimitaxie

2
mile * 1.609344 = km
Sinan Dizdarević

2
AVERTISSEMENT. Excellente solution, mais elle a un bogue. Tous les absdevraient être supprimés. Il n'est pas nécessaire de prendre la valeur abs lors de la conversion des degrés en radians et, même si vous l'avez fait, vous le faites sous une seule des latitudes. Veuillez le modifier pour qu'il corrige le bogue.
Chango

1
Et pour tous ceux qui le souhaitent en mètres: Convertissez 3956 miles en kilomètres: le rayon de la Terre; Convertir 69 miles en kilomètres: la longueur approximative de 1 degré de latitude en km; Et entrez la distance en kilomètres.
Chango

1
Et remplacer 69par 111,044736(j'ai oublié cela dans le commentaire ci-dessus)
rkeet

24

Juste au cas où vous êtes paresseux comme moi, voici une solution amalgamée à partir de ceci et d'autres réponses sur SO.

set @orig_lat=37.46; 
set @orig_long=-122.25; 
set @bounding_distance=1;

SELECT
*
,((ACOS(SIN(@orig_lat * PI() / 180) * SIN(`lat` * PI() / 180) + COS(@orig_lat * PI() / 180) * COS(`lat` * PI() / 180) * COS((@orig_long - `long`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance` 
FROM `cities` 
WHERE
(
  `lat` BETWEEN (@orig_lat - @bounding_distance) AND (@orig_lat + @bounding_distance)
  AND `long` BETWEEN (@orig_long - @bounding_distance) AND (@orig_long + @bounding_distance)
)
ORDER BY `distance` ASC
limit 25;

1
que bounding_distancereprésente exactement ? cette valeur limite-t-elle les résultats à un montant en mille? donc dans ce cas, il renverra des résultats dans un délai de 1 mile?
james

1
@ bounding_distance est ici en degrés et est utilisé pour accélérer les calculs en limitant la zone de recherche effective. Par exemple, si vous savez que votre utilisateur se trouve dans une certaine ville et que vous savez que vous avez quelques points dans cette ville, vous pouvez définir en toute sécurité votre distance de délimitation à quelques degrés.
Evan

1
Quelle formule de distance géographique utilise-t-il?
bbodenmiller

12

Facile ;)

SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;

Remplacez simplement les coordonnées par vos coordonnées. Les valeurs doivent être stockées en double. Ceci est un exemple fonctionnel de MySQL 5.x.

À votre santé


2
Je n'ai aucune idée de la raison pour laquelle OP veut limiter et ordonner par une certaine distance, pas par 30 et dx+dy
passer

3
Cela l'a fait pour moi. Ce n'est pas ce que voulait l'OP, mais c'est ce que je voulais, alors merci d'avoir répondu! :)
Webmaster G

ABS () le plus à l'extérieur est suffisant.
dzona

6

Essayez ceci, il affiche les points les plus proches des coordonnées fournies (à moins de 50 km). Cela fonctionne parfaitement:

SELECT m.name,
    m.lat, m.lon,
    p.distance_unit
             * DEGREES(ACOS(COS(RADIANS(p.latpoint))
             * COS(RADIANS(m.lat))
             * COS(RADIANS(p.longpoint) - RADIANS(m.lon))
             + SIN(RADIANS(p.latpoint))
             * SIN(RADIANS(m.lat)))) AS distance_in_km
FROM <table_name> AS m
JOIN (
      SELECT <userLat> AS latpoint, <userLon> AS longpoint,
             50.0 AS radius, 111.045 AS distance_unit
     ) AS p ON 1=1
WHERE m.lat
BETWEEN p.latpoint  - (p.radius / p.distance_unit)
    AND p.latpoint  + (p.radius / p.distance_unit)
    AND m.lon BETWEEN p.longpoint - (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
    AND p.longpoint + (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
ORDER BY distance_in_km

Changez simplement <table_name>. <userLat>et<userLon>

Vous pouvez en savoir plus sur cette solution ici: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/


6

Les réponses originales à la question sont bonnes, mais les nouvelles versions de mysql (MySQL 5.7.6 on) prennent en charge les requêtes géographiques, vous pouvez donc maintenant utiliser les fonctionnalités intégrées plutôt que de faire des requêtes complexes.

Vous pouvez maintenant faire quelque chose comme:

select *, ST_Distance_Sphere( point ('input_longitude', 'input_latitude'), 
                              point(longitude, latitude)) * .000621371192 
          as `distance_in_miles` 
  from `TableName`
having `distance_in_miles` <= 'input_max_distance'
 order by `distance_in_miles` asc

Les résultats sont renvoyés au format meters. Donc, si vous voulez KMsimplement utiliser à la .001place de .000621371192(ce qui est pour des miles).

Les documents MySql sont ici


Si possible, veuillez ajouter la version mysql dans la réponse.
Parixit

ST_Distance_Spheren'existe pas dans install ( mysql Ver 15.1 Distrib 10.2.23-MariaDB) de mon hôte . J'ai lu quelque part pour remplacer, ST_Distancemais les distances sont loin.
ashleedawg

@ashleedawg - De la version, je pense que vous utilisez MariaDB, qui est un fork de mysql. D'après cette conversation, il semble que MariaDB n'a pas été implémentéeST_Distance_Sphere
Sherman

5

Vous recherchez des choses comme la formule haversine . Voir aussi ici .

Il y en a d'autres mais c'est le plus souvent cité.

Si vous recherchez quelque chose d'encore plus robuste, vous voudrez peut-être examiner les capacités SIG de vos bases de données. Ils sont capables de certaines choses intéressantes comme vous dire si un point (ville) apparaît dans un polygone donné (région, pays, continent).


C'est en effet le plus cité, mais de nombreux articles font référence à du matériel informatique ancien lorsqu'il s'agit de déclarations sur des calculs inexacts utilisant d'autres méthodes. Voir aussi movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan

4

Vérifiez ce code basé sur l'article Geo-Distance-Search-with-MySQL :

Exemple: trouvez les 10 hôtels les plus proches de ma position actuelle dans un rayon de 10 miles:

#Please notice that (lat,lng) values mustn't be negatives to perform all calculations

set @my_lat=34.6087674878572; 
set @my_lng=58.3783670308302;
set @dist=10; #10 miles radius

SELECT dest.id, dest.lat, dest.lng,  3956 * 2 * ASIN(SQRT(POWER(SIN((@my_lat -abs(dest.lat)) * pi()/180 / 2),2) + COS(@my_lat * pi()/180 ) * COS(abs(dest.lat) *  pi()/180) * POWER(SIN((@my_lng - abs(dest.lng)) *  pi()/180 / 2), 2))
) as distance
FROM hotel as dest
having distance < @dist
ORDER BY distance limit 10;

#Also notice that distance are expressed in terms of radius.

3
simpledb.execSQL("CREATE TABLE IF NOT EXISTS " + tablename + "(id INTEGER PRIMARY KEY   AUTOINCREMENT,lat double,lng double,address varchar)");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2891001','70.780154','craftbox');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2901396','70.7782428','kotecha');");//22.2904718 //70.7783906
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2863155','70.772108','kkv Hall');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.275993','70.778076','nana mava');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2667148','70.7609386','Govani boys hostal');");


    double curentlat=22.2667258;  //22.2677258
    double curentlong=70.76096826;//70.76096826

    double curentlat1=curentlat+0.0010000;
    double curentlat2=curentlat-0.0010000;

    double curentlong1=curentlong+0.0010000;
    double curentlong2=curentlong-0.0010000;

    try{

        Cursor c=simpledb.rawQuery("select * from '"+tablename+"' where (lat BETWEEN '"+curentlat2+"' and '"+curentlat1+"') or (lng BETWEEN         '"+curentlong2+"' and '"+curentlong1+"')",null);

        Log.d("SQL ", c.toString());
        if(c.getCount()>0)
        {
            while (c.moveToNext())
            {
                double d=c.getDouble(1);
                double d1=c.getDouble(2);

            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }

2

Il semble que vous vouliez faire une recherche de voisin le plus proche avec une limite sur la distance. SQL ne prend en charge rien de tel pour autant que je sache et vous auriez besoin d'utiliser une structure de données alternative telle qu'un R-tree ou kd-tree .


2

Trouver les utilisateurs les plus proches de mon:

Distance en mètres

Basé sur la formule de Vincenty

J'ai la table des utilisateurs:

+----+-----------------------+---------+--------------+---------------+
| id | email                 | name    | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 13 | xxxxxx@xxxxxxxxxx.com | Isaac   | 17.2675625   | -97.6802361   |
| 14 | xxxx@xxxxxxx.com.mx   | Monse   | 19.392702    | -99.172596    |
+----+-----------------------+---------+--------------+---------------+

sql:

-- my location:  lat   19.391124   -99.165660
SELECT 
(ATAN(
    SQRT(
        POW(COS(RADIANS(users.location_lat)) * SIN(RADIANS(users.location_long) - RADIANS(-99.165660)), 2) +
        POW(COS(RADIANS(19.391124)) * SIN(RADIANS(users.location_lat)) - 
       SIN(RADIANS(19.391124)) * cos(RADIANS(users.location_lat)) * cos(RADIANS(users.location_long) - RADIANS(-99.165660)), 2)
    )
    ,
    SIN(RADIANS(19.391124)) * 
    SIN(RADIANS(users.location_lat)) + 
    COS(RADIANS(19.391124)) * 
    COS(RADIANS(users.location_lat)) * 
    COS(RADIANS(users.location_long) - RADIANS(-99.165660))
 ) * 6371000) as distance,
users.id
FROM users
ORDER BY distance ASC

rayon de la terre: 6371000 (en mètres)


1

MS SQL Edition ici:

        DECLARE @SLAT AS FLOAT
        DECLARE @SLON AS FLOAT

        SET @SLAT = 38.150785
        SET @SLON = 27.360249

        SELECT TOP 10 [LATITUDE], [LONGITUDE], SQRT(
            POWER(69.1 * ([LATITUDE] - @SLAT), 2) +
            POWER(69.1 * (@SLON - [LONGITUDE]) * COS([LATITUDE] / 57.3), 2)) AS distance
        FROM [TABLE] ORDER BY 3

0

On dirait que vous devriez simplement utiliser PostGIS, SpatialLite, SQLServer2008 ou Oracle Spatial. Ils peuvent tous répondre à cette question pour vous avec le SQL spatial.


7
On dirait que vous ne devriez PAS suggérer aux gens de changer toute leur plate-forme de base de données et de faire apparaître des résultats non pertinents dans ma recherche Google lorsque je recherche explicitement «Oracle» ...
J'ai combattu un ours une fois.

0

Dans les cas extrêmes, cette approche échoue, mais pour les performances, j'ai sauté la trigonométrie et calculé simplement la diagonale au carré.


-13

Ce problème n'est pas très difficile du tout, mais il devient plus compliqué si vous devez l'optimiser.

Ce que je veux dire, c'est, avez-vous 100 emplacements dans votre base de données ou 100 millions? Ça fait une grande différence.

Si le nombre d'emplacements est petit, extrayez-les de SQL et insérez-les dans le code en faisant simplement ->

Select * from Location

Une fois que vous les avez introduits dans le code, calculez la distance entre chaque latitude / longitude et votre original avec la formule Haversine et triez-la.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.