Encuentre la latitud / longitud más cercana con una consulta SQL

173

Tengo latitud y longitud y quiero extraer el registro de la base de datos, que tiene la latitud y longitud más cercanas por la distancia, si esa distancia es más larga que la especificada, entonces no la recupere.

Estructura de la mesa:

id
latitude
longitude
place name
city
country
state
zip
sealevel
Basit
fuente
1
Esto es una especie de duplicado de la pregunta de búsqueda de proximidad .
Darius Bacon
1
Hay un conjunto de diapositivas de Alexander Rubin en la búsqueda Geo (proximidad) con MySQL (enlace PDF)
Martijn Pieters

Respuestas:

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;

donde [starlat] y [startlng] es la posición donde comenzar a medir la distancia.

Kaletha
fuente
49
Solo una nota de rendimiento, es mejor no cuadrar la variable de distancia, sino cuadrar el valor de prueba '25' ... luego cuadrar los resultados que han pasado si necesita mostrar la distancia
sradforth
9
¿Cuál sería la misma consulta para la distancia en metros? (que actualmente está en millas, ¿verdad?)
httpete
8
¿Qué medida es esa 25?
Steffan Donal
16
Solo para aclarar aquí 69.1 es el factor de conversión de millas a grados de latitud. 57.3 es aproximadamente 180 / pi, por lo que es la conversión de grados a radianes, para la función coseno. 25 es el radio de búsqueda en millas. Esta es la fórmula a usar cuando se usan grados decimales y millas de estatuto.
John Vance
8
Además, no tiene en cuenta la curvatura de la tierra. Esto no sería un problema para radios de búsqueda cortos. De lo contrario, las respuestas de Evan e Igor son más completas.
John Vance
63

La solución de Google:

Creando la tabla

Cuando crea la tabla MySQL, desea prestar especial atención a los atributos lat y lng. Con las capacidades de zoom actuales de Google Maps, solo debería necesitar 6 dígitos de precisión después del decimal. Para mantener al mínimo el espacio de almacenamiento requerido para su tabla, puede especificar que los atributos lat y lng sean flotantes de tamaño (10,6). Eso permitirá que los campos almacenen 6 dígitos después del decimal, más hasta 4 dígitos antes del decimal, por ejemplo, -123.456789 grados. Su tabla también debe tener un atributo id para servir como clave principal.

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 ;

Poblar la mesa

Después de crear la tabla, es hora de llenarla con datos. Los datos de muestra que se proporcionan a continuación corresponden a unas 180 pizzarias diseminadas por los Estados Unidos. En phpMyAdmin, puede usar la pestaña IMPORTAR para importar varios formatos de archivo, incluido CSV (valores separados por comas). Microsoft Excel y Google Spreadsheets exportan a formato CSV, por lo que puede transferir fácilmente datos de hojas de cálculo a tablas MySQL mediante la exportación / importación de archivos 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');

Encontrar ubicaciones con MySQL

Para buscar ubicaciones en su tabla de marcadores que estén dentro de una cierta distancia de radio de una latitud / longitud dada, puede usar una instrucción SELECT basada en la fórmula de Haversine. La fórmula de Haversine se usa generalmente para calcular distancias de gran círculo entre dos pares de coordenadas en una esfera. Wikipedia da una explicación matemática en profundidad y una buena discusión de la fórmula en relación con la programación se encuentra en el sitio de Movable Type.

Aquí está la instrucción SQL que encontrará las 20 ubicaciones más cercanas que están dentro de un radio de 25 millas a la coordenada 37, -122. Calcula la distancia en función de la latitud / longitud de esa fila y la latitud / longitud objetivo, y luego solicita solo filas donde el valor de la distancia es inferior a 25, ordena toda la consulta por distancia y la limita a 20 resultados. Para buscar por kilómetros en lugar de millas, reemplace 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;

Este es encontrar latitudes y longitudes en una distancia de menos de 28 millas.

Otro es encontrarlos en una distancia entre 28 y 29 millas:

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

Sviatoslav Oleksiv
fuente
1
¿Debería ser HAVING distance < 25cuando consultamos ubicaciones dentro de un radio de 25 millas?
Vitalii Elenhaupt 01 de
debería ser> 25, luego buscará todos los registros
vidur punj
¿Estás seguro @vidurpunj sobre> 25?
Amranur Rahman
Probé la consulta SQL usando: distancia <25, pero encontraron resultados .. como los marcadores de distancias de muestra todos están por encima de 25 ...
IbrahimShendy
37, -122 coordenadas, ¿es esta latitud y longitud para la posición desde donde necesitamos encontrar la distancia?
Prasobh.Kollattu
28

Aquí está mi solución completa implementada en PHP.

Esta solución utiliza la fórmula de Haversine como se presenta en http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Cabe señalar que la fórmula de Haversine experimenta debilidades alrededor de los polos. Esta respuesta muestra cómo implementar la fórmula Vincenty Great Circle Distance para evitar esto, sin embargo, elegí usar Haversine porque es lo suficientemente bueno para mis propósitos.

Estoy almacenando la latitud como DECIMAL (10,8) y la longitud como DECIMAL (11,8). ¡Ojalá esto ayude!

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

Es posible aumentar el rendimiento mediante el uso de un procedimiento almacenado de MySQL como lo sugiere el artículo "Geo-Distance-Search-with-MySQL" publicado anteriormente.

Tengo una base de datos de ~ 17,000 lugares y el tiempo de ejecución de la consulta es de 0.054 segundos.

circuitería
fuente
¿Cómo puedo obtener la distancia en km o metros? ¡Saludos!
chemitaxis
2
milla * 1.609344 = km
Sinan Dizdarević
2
ADVERTENCIA. Excelente solución, pero tiene un error. Todo lo que absdebe ser eliminado. No es necesario tomar el valor de abs al convertir de grados a radianes e, incluso si lo hiciste, lo estás haciendo solo en una de las latitudes. Edítelo para que corrija el error.
Chango
1
Y para cualquiera que quiera esto en metros: Convierta 3956 millas a kilómetros: el radio de la Tierra; Convertir 69 millas a kilómetros: la longitud aproximada de 1 grado de latitud en km; E ingrese la distancia en kilómetros.
Chango
1
Y reemplace 69con 111,044736(olvidé eso en el comentario anterior)
rkeet
24

En caso de que seas perezoso como yo, aquí hay una solución amalgamada de esta y otras respuestas sobre 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;
Evan
fuente
1
¿Qué bounding_distancerepresenta exactamente ? ¿este valor limita los resultados a una cantidad de millas? entonces, en este caso, ¿devolverá resultados dentro de 1 milla?
James
1
@bounding_distance está en grados aquí, y se usa para acelerar los cálculos al limitar la región de búsqueda efectiva. Por ejemplo, si sabe que su usuario se encuentra en una determinada ciudad y sabe que tiene algunos puntos dentro de esa ciudad, puede establecer de forma segura su distancia límite a unos pocos grados.
Evan
1
¿Qué fórmula de distancia geográfica está usando esto?
bbodenmiller
12

Fácil ;)

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

Simplemente reemplace las coordenadas con las requeridas. Los valores deben almacenarse como dobles. Este es un ejemplo de MySQL 5.x que funciona.

Salud

Nicholas
fuente
2
No tengo idea de por qué votar a favor, OP quiere limitar y ordenar por cierta distancia, no limitar por 30 y ordenar pordx+dy
okm
3
Esto lo hizo por mí. No es lo que quería el OP, pero es lo que yo quería, ¡así que gracias por responder! :)
Webmaster G
El ABS más externo () es suficiente.
dzona
6

Intente esto, muestra los puntos más cercanos a las coordenadas proporcionadas (a menos de 50 km). Funciona perfectamente:

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

Solo cambia <table_name>. <userLat>y<userLon>

Puede leer más sobre esta solución aquí: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

ratón inteligente
fuente
6

Las respuestas originales a la pregunta son buenas, pero las versiones más recientes de mysql (MySQL 5.7.6 en adelante) admiten consultas geográficas, por lo que ahora puede usar la funcionalidad integrada en lugar de hacer consultas complejas.

Ahora puedes hacer algo como:

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

Los resultados se devuelven en meters. Entonces, si lo desea, KMsimplemente use en .001lugar de .000621371192(que es por millas).

Los documentos de MySQL están aquí

Sherman
fuente
Si es posible, agregue la versión mysql en la respuesta.
Parixit
ST_Distance_Sphereno existe en la instalación de mi host ( mysql Ver 15.1 Distrib 10.2.23-MariaDB). Leí en alguna parte para sustituir, ST_Distancepero las distancias están muy lejos.
ashleedawg
@ashleedawg - De la versión, creo que estás usando MariaDB, que es una bifurcación de mysql. De esta conversación parece que MariaDB no se ha implementadoST_Distance_Sphere
Sherman
5

Estás buscando cosas como la fórmula de Haversine . Mira aquí también.

Hay otros, pero este es el más comúnmente citado.

Si está buscando algo aún más robusto, es posible que desee ver las capacidades SIG de sus bases de datos. Son capaces de algunas cosas interesantes como decirle si un punto (Ciudad) aparece dentro de un polígono dado (Región, País, Continente).

Koobz
fuente
De hecho, es el más citado, pero muchos artículos se refieren al hardware informático antiguo cuando se trata de declaraciones sobre computación inexacta utilizando otros métodos. Véase también movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan
4

Verifique este código basado en el artículo Geo-Distance-Search-with-MySQL :

Ejemplo: encontrar los 10 hoteles más cercanos a mi ubicación actual en un radio de 10 millas:

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

Parece que quiere hacer una búsqueda de vecino más cercano con algunos límites en la distancia. SQL no admite nada de esto, por lo que yo sé, y necesitaría usar una estructura de datos alternativa, como un árbol R o un árbol kd .

Chris de Vries
fuente
2

Encuentra usuarios más cercanos a mi:

Distancia en metros

Basado en la fórmula de Vincenty

Tengo tabla de usuario:

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

radio de la tierra: 6371000 (en metros)

Isaac Limón
fuente
1

MS SQL Edition aquí:

        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
B.Tekkan
fuente
0

Parece que debería usar PostGIS, SpatialLite, SQLServer2008 u Oracle Spatial. Todos pueden responder esta pregunta por usted con SQL espacial.

TheSteve0
fuente
77
Parece que simplemente NO debes sugerir que las personas cambien su plataforma de base de datos completa y que se muestren resultados irrelevantes en mi búsqueda de Google cuando busco explícitamente "Oracle" ...
Luché un oso una vez.
0

En casos extremos, este enfoque falla, pero por rendimiento, he omitido la trigonometría y simplemente calculé la diagonal al cuadrado.

usuario1032402
fuente
-13

Este problema no es muy difícil en absoluto, pero se vuelve más complicado si necesita optimizarlo.

Lo que quiero decir es, ¿tiene 100 ubicaciones en su base de datos o 100 millones? Eso hace una gran diferencia.

Si el número de ubicaciones es pequeño, sáquelas del SQL y del código simplemente haciendo ->

Select * from Location

Una vez que los ingrese en el código, calcule la distancia entre cada lat / lon y su original con la fórmula Haversine y ordénelo.

chamiltongt
fuente