Найти ближайшую широту / долготу с помощью запроса SQL

173

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

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

id
latitude
longitude
place name
city
country
state
zip
sealevel
Basit
источник
1
Это своего рода дубликат вопроса поиска близости .
Дариус Бэкон
1
Александр Рубин представляет набор слайдов, посвященных поиску гео (близость) с MySQL (ссылка в формате PDF)
Мартин Питерс

Ответы:

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] - это позиция, с которой начинается измерение расстояния.

Kaletha
источник
49
Просто примечание о производительности, лучше не помещать переменную расстояния в квадрат, а вместо этого возводить в квадрат тестовое значение «25» ... позже представим результаты, которые прошли, если вам нужно показать расстояние
sradforth
9
Какой будет тот же запрос для расстояния в метрах? (который в настоящее время находится в милях, верно?)
httpete
8
Какое измерение это 25?
Штефан Донал
16
Просто для пояснения здесь 69.1 - это коэффициент пересчета миль в градусы широты. 57,3 примерно 180 / пи, так что это преобразование из градусов в радианы для функции косинуса. 25 - радиус поиска в милях. Это формула для использования при использовании десятичных градусов и уставных миль.
Джон Вэнс
8
Кроме того, он не учитывает кривизну земли. Это не будет проблемой для коротких радиусов поиска. В противном случае ответы Эвана и Игоря являются более полными.
Джон Вэнс
63

Решение Google:

Создание таблицы

Когда вы создаете таблицу MySQL, вы хотите обратить особое внимание на атрибуты lat и lng. С текущими возможностями масштабирования Google Maps, вам нужно только 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 вы можете использовать вкладку ИМПОРТ для импорта файлов различных форматов, включая CSV (значения, разделенные запятыми). Microsoft Excel и Google Spreadsheets оба экспортируют в формат 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-запрос, используя: distance <25, но не нашел результатов .. так как в образцах маркеров расстояния все выше 25 ...
IbrahimShendy
37, координата -122, это широта и долгота для позиции, из которой нам нужно найти расстояние?
Prasobh.Kollattu
28

Вот мое полное решение, реализованное на PHP.

В этом решении используется формула Хаверсайна, представленная в http://www.scribed.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Следует отметить, что формула Хаверсина испытывает слабости вокруг полюсов. Этот ответ показывает, как реализовать формулу Винсенти Великого расстояния круга, чтобы обойти это, однако я решил просто использовать Haversine, потому что это достаточно хорошо для моих целей.

Я храню широту как десятичное (10,8) и долготу как десятичное (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 миль в километры: приблизительная длина 1 градуса широты в км; И введите расстояние в километрах.
Чанго
1
И заменить 69на 111,044736(забыл, что в приведенном выше комментарии)
rkeet
24

На всякий случай, если вы ленивы, как я, вот решение, объединенное из этого и других ответов на 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
источник
1
что именно bounding_distanceпредставляет? ограничивает ли это значение результаты милей? так что в этом случае он вернет результаты в течение 1 мили?
Джеймс
1
Здесь @ bounding_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
Понятия не имею, почему upvote, OP хочет ограничивать и заказывать на определенном расстоянии, а не ограничивать на 30 и заказыватьdx+dy
okm
3
Это сделало это для меня. Это не то, что хотел ОП, но это то, что я хотел, так что спасибо за ответ! :)
Веб-мастер G
Внешний ABS () достаточно.
Дзона
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-nearest-loc/

Smartmouse
источник
6

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

Теперь вы можете сделать что-то вроде:

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 документы здесь

Sherman
источник
Если возможно, пожалуйста, добавьте версию 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

Вы ищете такие вещи, как формула haversine . Смотрите здесь также.

Есть и другие, но это наиболее часто упоминаемые.

Если вы ищете что-то еще более надежное, вам, возможно, захочется взглянуть на возможности ГИС ваших баз данных. Они способны на некоторые интересные вещи, такие как сообщение о том, появляется ли точка (Город) в пределах данного полигона (Регион, Страна, Континент).

Koobz
источник
Это действительно самый цитируемый, но многие статьи ссылаются на старое вычислительное оборудование, когда речь идет об утверждениях о неточных вычислениях с использованием других методов. См. Также 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.
JuanManuelFigueroa
источник
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
источник
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 Edition здесь:

        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
источник
0

Похоже, вы должны просто использовать PostGIS, SpatialLite, SQLServer2008 или Oracle Spatial. Все они могут ответить на этот вопрос для вас с пространственным SQL.

TheSteve0
источник
7
Похоже, вы просто НЕ должны предлагать, чтобы люди переключали всю свою платформу базы данных и приводили к тому, что нерелевантные результаты появлялись в моем поиске в Google, когда я выполнял явный поиск по «Оракулу» ...
однажды я боролся с медведем.
0

В крайних случаях этот подход не работает, но для производительности я пропустил тригонометрию и просто вычислил диагональный квадрат.

user1032402
источник
-13

Эта проблема совсем не сложная, но она усложняется, если вам нужно ее оптимизировать.

Я имею в виду, у вас есть 100 мест в вашей базе данных или 100 миллионов? Это имеет большое значение.

Если количество мест невелико, выведите их из SQL в код, просто выполнив ->

Select * from Location

Как только вы получите их в код, рассчитайте расстояние между каждым широтой и долготой и вашим оригиналом по формуле Хаверсайна и отсортируйте его.

chamiltongt
источник