Знайдіть найближчу широту / довготу за допомогою SQL-запиту


173

У мене є широта і довгота, і я хочу витягнути запис із бази даних, яка має найближчу широту і довготу на відстань, якщо ця відстань стає довшою зазначеної, то не завантажуйте її.

Структура таблиці:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
Це свого роду дублікат запитання про близькість пошуку .
Дарій Бекон

1
Є набір слайдів Олександра Рубіна в пошуках гео (близькості) за допомогою MySQL (PDF посилання)
Martijn Pieters

Відповіді:


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] і [startlng] - це положення, з якого слід починати вимірювання відстані.


49
Просто примітка про ефективність, найкраще не квартувати змінну відстані, а замість неї
викласти

9
Який же запит має бути відстань у метрах? (який наразі в милях, правда?)
https:

8
Яке вимірювання - це 25?
Steffan Donal

16
Тільки для уточнення тут 69.1 - коефіцієнт перетворення за милі до градусів широти. 57.3 становить приблизно 180 / пі, тому для косинусної функції це перетворення від градусів до радіанів. 25 - радіус пошуку в милях. Це формула, яку слід використовувати при використанні десяткових градусів і статутних миль.
Джон Ванс

8
Крім того, він не враховує викривлення землі. Це не буде проблемою для коротких радіусів пошуку. Інакше відповіді Евана та Ігоря є більш повними.
Джон Ванс

63

Рішення Google:

Створення таблиці

Створюючи таблицю MySQL, ви хочете звернути особливу увагу на атрибути lat та lng. При поточних можливостях масштабування на Картах Google вам потрібно лише 6 цифр точності після десяткових. Щоб мінімізувати місце для зберігання, необхідне для вашої таблиці, ви можете вказати, що атрибути lat та lng є плаваючими розмірами (10,6). Це дозволить полям зберігати 6 цифр після десяткових знаків плюс до 4 цифр перед десятковою, наприклад, -123,456789 градусів. У вашій таблиці також повинен бути атрибут id, який служить основним ключем.

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 ;

Населення таблиці

Після створення таблиці настав час заповнити її даними. Наведені нижче вибіркові дані стосуються приблизно 180 піцарій, розкиданих по Сполучених Штатах. У phpMyAdmin ви можете використовувати вкладку IMPORT для імпорту різних форматів файлів, включаючи CSV (значення, розділені комами). Електронні таблиці Microsoft Excel та Google експортують у формат CSV, тому ви можете легко переносити дані з електронних таблиць у таблиці MySQL шляхом експорту / імпорту файлів 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');

Пошук локацій за допомогою MySQL

Щоб знайти місця в таблиці ваших маркерів, які знаходяться на певній відстані радіусу від заданої широти / довготи, ви можете використовувати оператор SELECT, заснований на формулі Гаверсіна. Формула Гаверсіна зазвичай використовується для обчислення відстаней великого кола між двома парами координат на кулі. Поглиблене математичне пояснення дає Вікіпедія, і хороша дискусія щодо формули, що стосується програмування, знаходиться на сайті Movable Type.

Ось оператор SQL, який знайде найближчі 20 локацій, які знаходяться в радіусі 25 миль до координати 37, -122. Він обчислює відстань на основі широти / довготи цього рядка та цільової широти / довготи, а потім запитує лише рядки, де значення відстані менше 25, впорядковує весь запит на відстань і обмежує його на 20 результатів. Щоб шукати кілометри замість миль, замініть 3959 на 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;

Це - знайти широти та довготу на відстані менше 28 миль.

Ще один - знайти їх на відстані від 28 до 29 миль:

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
Чи має бути так, HAVING distance < 25як ми запитуємо місця в радіусі 25 миль?
Віталій Еленхаупт

це повинно бути> 25, тоді він шукатиме всі записи
vidur punj

Ви впевнені @vidurpunj про> 25?
Амранур Рахман

Я спробував sql-запит, використовуючи: відстань <25, але не знайшов результатів .. так як відстані з маркерами зразків всі вище 25 ...
IbrahimShendy

37, -122 координати, це широта і довгота для положення, звідки нам потрібно знайти відстань?
Прасох.Колатту

28

Ось моє повне рішення, реалізоване в PHP.

Це рішення використовує формулу Haversine, представлену в http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Слід зазначити, що формула Гаверсіна відчуває слабкі місця навколо полюсів. Ця відповідь показує, як реалізувати формулу Вінсенті Great Circle Distance, щоб обійти це, однак я вирішив просто використовувати Haversine, оскільки це досить добре для моїх цілей.

Я зберігаю широту як DECIMAL (10,8), а довготу як DECIMAL (11,8). Сподіваємось, це допомагає!

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

Підвищити продуктивність можливо, використовуючи процедуру, що зберігається в MySQL, як це запропоновано в статті "Geo-Distance-Search-with-MySQL", розміщеній вище.

У мене база даних становить ~ 17 000 місць, час виконання запитів - 0,054 секунди.


Як я можу отримати відстань у км або метрах? З повагою!
хімікат

2
миля * 1.609344 = км
Синан Діздаревич

2
УВАГА. Відмінне рішення, але в ньому є помилка. Все absслід видалити. Не потрібно приймати значення abs при перетворенні від градусів на радіани, і, навіть якщо ви це зробили, ви робите це лише в одній із широт. Відредагуйте його, щоб воно виправляло помилку.
Чанго

1
А для тих, хто хоче цього в метрах: Перетворіть 3956 миль в кілометри: радіус Землі; Перетворіть 69 миль на кілометри: довжина aproxímate 1 градус широти в км; І введіть відстань у кілометрах.
Чанго

1
І замініть 69на 111,044736(забув, що в коментарі вище)
rkeet

24

На випадок, якщо ви ледачі, як я, ось рішення, зведене з цієї та інших відповідей на ТА.

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
що саме bounding_distanceявляє собою? чи обмежує це значення результати на милю? тож у такому випадку вона поверне результати протягом 1 милі?
Джеймс

1
@ limiting_distance знаходиться в градусах і використовується для прискорення обчислень, обмежуючи ефективну область пошуку. Наприклад, якщо ви знаєте, що ваш користувач перебуває у певному місті, і ви знаєте, що у вас є кілька точок у цьому місті, ви можете сміливо встановити обмежувальну відстань на кілька градусів.
Еван

1
Яку географічну формулу відстані використовуєте?
bbodenmiller

12

Легкий;)

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

Просто замініть координати на потрібні. Значення повинні зберігатися як подвійні. Це справжній приклад MySQL 5.x.

Ура


2
Не маю ідеї, чому підтримувати, ОП хоче обмежити та замовити певну відстань, а не обмежитись на 30, а замовитиdx+dy
okm

3
Це зробило це для мене. Це не те, що хотіла ОП, але це те, що я хотів, тож дякую за відповідь! :)
Вебмайстер G

самого зовнішнього АБС () достатньо.
дзона

6

Спробуйте це, він показує найближчі точки до вказаних координат (в межах 50 км). Це прекрасно працює:

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

Просто змінити <table_name>. <userLat>і<userLon>

Детальніше про це рішення ви можете прочитати тут: http://www.plumislandmedia.net/mysql/haversine-mysql-nevable-loc/


6

Оригінальні відповіді на питання хороші, але новіші версії mysql (MySQL 5.7.6 on) підтримують гео запити, тому тепер ви можете використовувати вбудовану функціональність, а не робити складні запити.

Тепер ви можете зробити щось на кшталт:

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

Результати повертаються meters. Тож якщо ви хочете KMпросто використовувати .001замість .000621371192(що за милі).

Документи MySql тут


Якщо можливо, додайте у відповідь версію mysql.
Parixit

ST_Distance_Sphereне існує в установці мого хоста ( mysql Ver 15.1 Distrib 10.2.23-MariaDB). Я десь читав, щоб підміняти, ST_Distanceале відстані далекі.
ashleedawg

@ashleedawg - З версії, я думаю, ви використовуєте MariaDB, що є форкою mysql. З цієї розмови виходить так, що MariaDB не реалізуєтьсяST_Distance_Sphere
Sherman

5

Ви шукаєте такі речі, як формула Гаверсина . Дивіться також тут .

Є й інші, але це найчастіше цитується.

Якщо ви шукаєте щось ще більш надійне, вам варто поглянути на свої можливості ГІС баз даних. Вони здатні робити деякі цікаві речі, як-от сказати вам, чи з’являється точка (Місто) в межах даного полігону (регіон, країна, континент).


Це дійсно найбільш цитується, але багато статей стосуються старого обчислювального обладнання, коли мова йде про твердження про неточні обчислення, використовуючи інші методи. Дивіться також movable-type.co.uk/scripts/latlong.html#cosine-law
Арьян

4

Перевірте цей код на основі статті Geo-Distance-Search-with-MySQL :

Приклад: знайдіть 10 найближчих готелів до мого поточного місцезнаходження в радіусі 10 миль:

#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

Здається, ви хочете здійснити пошук найближчого сусіда з деяким обмеженням на відстані. Наскільки мені відомо, SQL не підтримує нічого подібного, і вам знадобиться використовувати альтернативну структуру даних, таку як R-дерево або kd-дерево .


2

Знайти найближчих користувачів до мого:

Відстань у метрах

Спираючись на формулу Вінсенті

у мене є таблиця користувача:

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

радіус землі: 6371000 (в метрах)


1

Видання MS SQL тут:

        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

Схоже, вам слід просто використовувати PostGIS, SpatialLite, SQLServer2008 або Oracle Spatial. Усі вони можуть відповісти на це запитання за допомогою просторового SQL.


7
звучить так, що ви просто НЕ пропонуєте людям переключати всю свою платформу баз даних і викликати неактуальні результати в моєму пошуку Google, коли я явно шукаю сюди "Oracle" ...
Я боровся ведмедям один раз.

0

У крайньому випадку такий підхід не вдається, але для продуктивності я пропустив тригонометрію і просто обчислив діагональ квадрата.


-13

Ця проблема зовсім не дуже важка, але вона ускладнюється, якщо вам потрібно її оптимізувати.

Що я маю на увазі, чи є у вашій базі 100 локацій чи 100 мільйонів? Це робить велику різницю.

Якщо кількість місць невелика, вийміть їх із SQL та введіть у код, просто зробивши ->

Select * from Location

Як тільки ви вводите їх у код, обчисліть відстань між кожним lat / lon та оригіналом за формулою Haversine та відсортуйте його.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.