Trova la latitudine / longitudine più vicina con una query SQL


173

Ho latitudine e longitudine e voglio estrarre il record dal database, che ha la latitudine e la longitudine più vicine alla distanza, se quella distanza supera quella specificata, quindi non recuperarla.

Struttura del tavolo:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
Questa è una specie di duplicato della domanda di ricerca di prossimità .
Darius Bacon,

1
C'è una serie di diapositive di Alexander Rubin sulla ricerca Geo (prossimità) con MySQL (collegamento PDF)
Martijn Pieters

Risposte:


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;

dove [starlat] e [startlng] è la posizione da cui iniziare a misurare la distanza.


49
Solo una nota di prestazione, è meglio non sqrt la variabile di distanza ma invece quadrare il valore di test "25" ... successivamente sqrt i risultati che sono stati superati se è necessario mostrare la distanza
sradforth

9
Quale sarebbe la stessa query per la distanza in metri? (che è attualmente in miglia, giusto?)
http:

8
Che misura è quella 25?
Steffan Donal,

16
Giusto per chiarire qui 69.1 è il fattore di conversione da miglia a gradi di latitudine. 57.3 è circa 180 / pi, quindi questa è la conversione da gradi a radianti, per la funzione del coseno. 25 è il raggio di ricerca in miglia. Questa è la formula da utilizzare quando si usano gradi decimali e miglia statutarie.
John Vance,

8
Inoltre, non tiene conto della curvatura della terra. Questo non sarebbe un problema per brevi raggi di ricerca. Altrimenti le risposte di Evan e Igor sono più complete.
John Vance,

63

Soluzione di Google:

Creare la tabella

Quando si crea la tabella MySQL, si desidera prestare particolare attenzione agli attributi lat e lng. Con le attuali funzionalità di zoom di Google Maps, dovresti avere solo 6 cifre di precisione dopo il decimale. Per mantenere almeno lo spazio di archiviazione richiesto per la tabella, è possibile specificare che gli attributi lat e lng siano float di dimensioni (10,6). Ciò consentirà ai campi di memorizzare 6 cifre dopo il decimale, più fino a 4 cifre prima del decimale, ad esempio -123,456789 gradi. La tua tabella dovrebbe anche avere un attributo id da servire come chiave primaria.

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 ;

Popolazione della tabella

Dopo aver creato la tabella, è tempo di popolarla con i dati. I dati di esempio forniti di seguito si riferiscono a circa 180 pizzerie sparse negli Stati Uniti. In phpMyAdmin, è possibile utilizzare la scheda IMPORT per importare vari formati di file, incluso CSV (valori separati da virgola). Microsoft Excel e Google Spreadsheets esportano entrambi in formato CSV, quindi puoi facilmente trasferire dati da fogli di calcolo a tabelle MySQL attraverso l'esportazione / importazione di file 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');

Trovare posizioni con MySQL

Per trovare posizioni nella tabella dei marker che si trovano entro una certa distanza di raggio da una determinata latitudine / longitudine, è possibile utilizzare un'istruzione SELECT basata sulla formula di Haversine. La formula di Haversine viene generalmente utilizzata per calcolare le distanze del grande cerchio tra due coppie di coordinate su una sfera. Wikipedia fornisce una spiegazione matematica approfondita e una buona discussione della formula relativa alla programmazione si trova sul sito di Movable Type.

Ecco l'istruzione SQL che troverà le 20 posizioni più vicine entro un raggio di 25 miglia dalla coordinata 37, -122. Calcola la distanza in base alla latitudine / longitudine di quella riga e alla latitudine / longitudine target, quindi chiede solo le righe in cui il valore della distanza è inferiore a 25, ordina l'intera query in base alla distanza e lo limita a 20 risultati. Per cercare per chilometri invece di miglia, sostituire 3959 con 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;

Questo è quello di trovare latitudini e longitudini a una distanza inferiore a 28 miglia.

Un altro è quello di trovarli a una distanza tra 28 e 29 miglia:

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
Dovrebbe essere HAVING distance < 25mentre interroghiamo le località entro un raggio di 25 miglia?
Vitalii Elenhaupt,

dovrebbe essere> 25, quindi cercherà tutti i record
vidur punj

Sei sicuro che @vidurpunj abbia> 25 anni?
Amranur Rahman,

Ho provato la query sql usando: distance <25 ma non ho trovato risultati .. dato che le distanze dei marker di esempio sono tutte superiori a 25 ...
IbrahimShendy

37, -122 coordinate, è questa latitudine e longitudine per la posizione da dove dobbiamo trovare la distanza?
Prasobh.Kollattu,

28

Ecco la mia soluzione completa implementata in PHP.

Questa soluzione utilizza la formula Haversine come presentata in http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Va notato che la formula di Haversine presenta punti deboli attorno ai poli. Questa risposta mostra come implementare la formula vincenty Great Circle Distance per aggirare questo problema, tuttavia ho scelto di usare Haversine solo perché è abbastanza buono per i miei scopi.

Sto memorizzando latitudine come DECIMAL (10,8) e longitudine come DECIMAL (11,8). Speriamo che questo aiuti!

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"
);
?>

Potrebbe essere possibile aumentare le prestazioni utilizzando una procedura memorizzata MySQL come suggerito dall'articolo "Geo-Distance-Search-with-MySQL" pubblicato sopra.

Ho un database di ~ 17.000 posti e il tempo di esecuzione della query è 0,054 secondi.


Come posso ottenere la distanza in Km o Metri? Saluti!
chemitassi

2
miglio * 1.609344 = km
Sinan Dizdarević

2
AVVERTIMENTO. Soluzione eccellente, ma ha un bug. Tutto absdovrebbe essere rimosso. Non è necessario prendere il valore degli addominali durante la conversione da gradi a radianti e, anche se lo hai fatto, lo stai facendo solo in una delle latitudini. Si prega di modificarlo in modo che risolva il bug.
Chango,

1
E per chiunque lo voglia in metri: converti 3956 miglia in chilometri: raggio terrestre; Convertire 69 miglia in chilometri: la lunghezza aproxímate di 1 grado di latitudine in km; E inserisci la distanza in chilometri.
Chango,

1
E sostituiscilo 69con 111,044736(dimenticato quello nel commento sopra)
rkeet,

24

Nel caso in cui tu sia pigro come me, ecco una soluzione combinata da questa e altre risposte su 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
cosa bounding_distancerappresenta esattamente ? questo valore limita i risultati a un miglio? quindi in questo caso restituirà risultati entro 1 miglio?
Giacomo,

1
@ bounding_distance è in gradi qui e viene utilizzato per accelerare i calcoli limitando l'area di ricerca effettiva. Ad esempio, se sai che il tuo utente si trova in una determinata città e sai di avere alcuni punti all'interno di quella città, puoi tranquillamente impostare la distanza limite a pochi gradi.
Evan,

1
Quale formula della distanza geografica sta usando?
bbodenmiller,

12

Facile;)

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

Sostituisci le coordinate con quelle richieste. I valori devono essere memorizzati come doppio. Questo è un esempio di MySQL 5.x funzionante.

Saluti


2
Non ho idea del perché, OP desidera limitare e ordinare entro una certa distanza, non limitare di 30 e ordinare entrodx+dy
km

3
Questo è stato per me. Non è quello che voleva l'OP, ma è quello che volevo, quindi grazie per aver risposto! :)
Webmaster G

l'ABS più esterno () è sufficiente.
dzona,

6

Prova questo, mostra i punti più vicini alle coordinate fornite (entro 50 km). Funziona perfettamente:

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

Basta cambiare <table_name>. <userLat>e<userLon>

Puoi leggere di più su questa soluzione qui: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/


6

Le risposte originali alla domanda sono buone, ma le versioni più recenti di mysql (MySQL 5.7.6 in poi) supportano le query geografiche, quindi ora puoi utilizzare funzionalità integrate anziché fare query complesse.

Ora puoi fare qualcosa del tipo:

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

I risultati vengono restituiti meters. Quindi, se vuoi KMsemplicemente usare .001invece di .000621371192(che è per miglia).

I documenti di MySql sono qui


Se possibile, aggiungi la versione mysql in risposta.
Parixit,

ST_Distance_Spherenon esiste nell'install ( mysql Ver 15.1 Distrib 10.2.23-MariaDB) del mio host . Ho letto da qualche parte per sostituire, ST_Distancema le distanze sono lontane.
ashleedawg,

@ashleedawg - Dalla versione, penso che tu stia usando MariaDB, che è un fork di mysql. Da questa conversazione sembra che MariaDB non abbia implementatoST_Distance_Sphere
Sherman il

5

Stai cercando cose come la formula haversine . Vedi qui come bene.

Ce ne sono altri ma questo è il più comunemente citato.

Se stai cercando qualcosa di ancora più robusto, potresti voler esaminare le funzionalità GIS dei tuoi database. Sono in grado di fare cose interessanti come dirti se un punto (Città) appare all'interno di un dato poligono (Regione, Paese, Continente).


È in effetti il ​​più citato, ma molti articoli fanno riferimento al vecchio hardware informatico quando si tratta di affermazioni sull'informatica imprecisa usando altri metodi. Vedi anche movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan

4

Controlla questo codice in base all'articolo Geo-Distance-Search-with-MySQL :

Esempio: trova i 10 hotel più vicini alla mia posizione attuale in un raggio di 10 miglia:

#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

Sembra che tu voglia fare una ricerca del vicino più vicino con qualche limite sulla distanza. SQL non supporta nulla di simile per quanto ne so e sarebbe necessario utilizzare una struttura di dati alternativa come un R-tree o kd-tree .


2

Trova gli utenti più vicini al mio:

Distanza in metri

Basato sulla formula di Vincenty

ho una tabella utente:

+----+-----------------------+---------+--------------+---------------+
| 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

raggio della terra: 6371000 (in metri)


1

Edizione MS SQL qui:

        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

Sembra che dovresti semplicemente utilizzare PostGIS, SpatialLite, SQLServer2008 o Oracle Spatial. Tutti possono rispondere a questa domanda con SQL spaziale.


7
sembra che NON dovresti semplicemente suggerire alle persone di cambiare la loro intera piattaforma di database e causare risultati irrilevanti nella mia ricerca su Google quando
cerco

0

In casi estremi questo approccio fallisce, ma per prestazioni ho saltato la trigonometria e ho semplicemente calcolato il quadrato diagonale.


-13

Questo problema non è affatto difficile, ma diventa più complicato se è necessario ottimizzarlo.

Voglio dire, hai 100 posizioni nel tuo database o 100 milioni? Fa una grande differenza.

Se il numero di posizioni è piccolo, estrailo da SQL e inseriscilo nel codice semplicemente facendo ->

Select * from Location

Una volta inseriti nel codice, calcola la distanza tra ogni lat / lon e il tuo originale con la formula Haversine e ordinalo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.