web-dev-qa-db-fra.com

latitude / longitude recherche latitude / longitude la plus proche - calcul complexe de sql ou complexe

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 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
159
Basit

Ce dont vous avez besoin est de traduire la distance en degrés de longitude et de latitude, de filtrer en fonction de ceux utilisés pour délimiter les entrées situées approximativement dans le cadre de sélection, puis de créer un filtre de distance plus précis. Voici un excellent article qui explique comment faire tout cela:

http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL

132
Igor Zevaka
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.

184
Kaletha

La solution de Google:

Créer la table

Lorsque vous créez la table MySQL, vous souhaitez accorder 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 virgule. Pour limiter 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 laissera les champs stocker 6 chiffres après la décimale, plus jusqu'à 4 chiffres avant la décimale, par exemple. -123,456789 degrés. Votre table devrait également avoir un attribut id servant 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 renseigner avec des données. Les exemples de données fournis ci-dessous concernent environ 180 pizzarias réparties 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). Microsoft Excel et Google Spreadsheets exportent tous les deux au format CSV afin que vous puissiez facilement transférer des données de feuilles de calcul vers des tables MySQL via l'exportation/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');

Trouver des emplacements avec MySQL

Pour rechercher dans la table des marqueurs des emplacements situés dans un rayon déterminé d'une latitude/longitude donnée, vous pouvez utiliser une instruction SELECT basée sur la formule de Haversine. La formule de Haversine est généralement utilisée pour calculer les distances de grand cercle 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 se trouve sur le site de Movable Type.

Voici l'instruction SQL qui trouvera les 20 emplacements les plus proches se trouvant 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 ne demande que les lignes dont la valeur de distance est inférieure à 25, ordonne la totalité de la requête par distance et la limite à 20 résultats. Pour rechercher par 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 est de trouver des latitudes et longitudes dans une distance inférieure à 28 miles.

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

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

50
Sviatoslav Oleksiv

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 convient de noter que la formule de Haversine présente des faiblesses autour des pôles. Cette réponse montre comment implémenter la formule vincenty Great Circle Distance pour contourner cela, mais j’ai choisi d’utiliser Haversine uniquement parce que c’est suffisant pour mes besoins.

Je stocke la latitude en tant que DECIMAL (10,8) et la longitude en tant que 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 est possible d'améliorer les performances en utilisant une procédure stockée MySQL, comme le suggère l'article "Géolocalisation-Recherche avec MySQL" publié ci-dessus.

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

27
circuitry

Juste au cas où vous êtes paresseux comme moi, voici une solution issue de ceci et d’autres réponses à ce sujet.

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;
24
Evan

Facile ;)

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

Il suffit de remplacer les coordonnées par celles requises. Les valeurs doivent être stockées en double. Ceci est un exemple fonctionnel de MySQL 5.x.

À votre santé

10
Nicholas

Vous cherchez des choses comme formule haversine . Voir ici aussi bien.

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 choses intéressantes comme vous dire si un point (ville) apparaît dans un polygone donné (région, pays, continent).

5
Koobz

Essayez ceci, il affiche les points les plus proches des coordonnées fournies (dans un rayon 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

Il suffit de changer <table_name>. <userLat> et <userLon>

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

4
smartmouse

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

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

#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.
4
JuanManuelFigueroa
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();
    }
3
Hardip

Les réponses originales à la question sont bonnes, mais les nouvelles versions de mysql (MySQL 5.7.6) 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 retournés dans meters donc si vous voulez KM au lieu de miles, utilisez .0001 au lieu de .000621371192

les documents MySql sont ici

3
Sherman

Il semble que vous souhaitiez effectuer une recherche avec le voisin le plus proche avec des bornes sur la distance. Autant que je sache, SQL ne prend en charge rien de ce type et vous auriez besoin d'une structure de données alternative telle qu'un arbre R ou n arbre kd .

2
Chris de Vries

Trouver les utilisateurs les plus proches de mon:

Distance en mètres

Basé dans formule de Vincenty

j'ai la table utilisateur:

+----+-----------------------+---------+--------------+---------------+
| id | email                 | name    | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 13 | [email protected] | Isaac   | 17.2675625   | -97.6802361   |
| 14 | [email protected]   | 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)

2
Isaac Limón

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

1
TheSteve0

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
1
B.Tekkan

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

1
user1032402
1
anhnv