широта / долгота найти ближайшую широту / долготу - комплексный sql или комплексный расчет

152

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

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

id
latitude
longitude
place name
city
country
state
zip
sealevel
  • 1
    Это своего рода дубликат вопроса о поиске близости .
  • 1
    Подробный блог: goo.gl/oU7Krf
Теги:
math
computational-geometry
coordinates

18 ответов

124
Лучший ответ

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

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

  • 3
    спасибо, хорошая статья !!!
  • 2
    Очень хороший документ, просто чтобы заметить: чтобы использовать его в Км, просто конвертируйте единицу измерения радиуса Земли из мили в Км (3956 -> 6371)
Показать ещё 5 комментариев
170
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] - это позиция, в которой нужно начинать измерять расстояние.

  • 46
    Просто примечание о производительности, лучше не использовать квадрат расстояния с переменной, а вместо этого возводить в квадрат тестовое значение «25» ... позже результаты, которые были пройдены, если вам нужно показать расстояние
  • 8
    Какой будет тот же запрос для расстояния в метрах? (который в настоящее время в милях, верно?)
Показать ещё 9 комментариев
42

Решение 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 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\ East Coast Pizzeria','790 Castro St, Mountain View, CA','37.38714','-122.083235');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Kapp\ 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\ Pizza & Pasta','619 Escuela Ave, Mountain View, CA','37.394011','-122.095528');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Oregano\ 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 < 25 
ORDER BY distance LIMIT 0, 20;

https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map

  • 1
    Должно ли это быть HAVING distance < 25 когда мы запрашиваем местоположения в радиусе 25 миль?
  • 1
    Да, вы правы, я исправил это, спасибо.
Показать ещё 3 комментария
26

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

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

Следует отметить, что формула Хаверсина испытывает недостатки вокруг полюсов. Этот ответ показывает, как реализовать формулу Vincenty 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);
?>

./активы/дб/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;
    }
}
?>

./активы/дб/dbSettings.php

<?php
$dbInfo = array(
    'host'      => "localhost",
    'user'      => "root",
    'pass'      => "password"
);
?>

Возможно, можно увеличить производительность с помощью хранимой процедуры MySQL, как было предложено в статье "Geo-Distance-Search-with-MySQL", опубликованной выше.

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

  • 0
    Как я могу получить расстояние в километрах или метрах? С уважением!
  • 2
    миля * 1.609344 = км
Показать ещё 6 комментариев
22

На всякий случай, когда вы ленитесь, как я, вот решение, объединенное из этого и других ответов на 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;
  • 0
    что именно представляет bounding_distance ? ограничивает ли это значение результаты милей? так что в этом случае он вернет результаты в течение 1 мили?
  • 1
    @ bounding_distance здесь указывается в градусах и используется для ускорения вычислений путем ограничения эффективной области поиска. Например, если вы знаете, что ваш пользователь находится в определенном городе, и вы знаете, что у вас есть несколько точек в этом городе, вы можете безопасно установить ограничивающее расстояние в несколько градусов.
Показать ещё 1 комментарий
11

Легкий один;)

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

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

Приветствия

  • 1
    Понятия не имею, почему upvote, OP хочет ограничить и заказать на определенном расстоянии, а не ограничить на 30, а заказать на dx+dy
  • 2
    Это сделало это для меня. Это не то, что хотел ОП, но это то, что я хотел, так что спасибо за ответ! :)
5

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

Там другие, но это чаще всего цитируется.

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

  • 0
    Это действительно самый цитируемый, но многие статьи ссылаются на старое вычислительное оборудование, когда речь идет об утверждениях о неточных вычислениях, использующих другие методы. Смотрите также movable-type.co.uk/scripts/latlong.html#cosine-law
4

Попробуйте это, он покажет ближайшие точки в координатах (в пределах 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/

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

Найти ближайших пользователей к моему:

Расстояние в метрах

На основе формула Vincenty

У меня есть таблица пользователя:

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

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

2

Похоже, вы хотите сделать поиск ближайшего соседа с некоторой границей на расстоянии. SQL не поддерживает ничего подобного, насколько мне известно, и вам нужно будет использовать альтернативную структуру данных, такую ​​как R-tree или kd-tree.

1

Первоначальные ответы на вопрос - это хорошие, но более новые версии геоинформационных запросов mysql, поэтому теперь вы можете использовать встроенные функции, а не выполнять сложные запросы.

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

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 вместо миль, используйте .0001 вместо .000621371192

  • 0
    Если возможно, пожалуйста, добавьте версию mysql в ответ.
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
1

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

1
1

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

  • 5
    Похоже, вы просто НЕ должны предлагать, чтобы люди переключали всю свою платформу базы данных и приводили к тому, что нерелевантные результаты появлялись в моем поиске Google, когда я выполняю явный поиск по «Oracle» ...
-10

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

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

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

Select * from Location

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

  • 2
    я бы сказал, 100 тысяч

Ещё вопросы

Сообщество Overcoder
Наверх
Меню