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

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

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

id
latitude
longitude
place name
city
country
state
zip
sealevel

Ответ 1

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

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

Ответ 2

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

Ответ 3

Решение 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\ 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 < 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

Ответ 4

Вот мое полное решение, реализованное в 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 секунды.

Ответ 5

На всякий случай, когда вы ленитесь, как я, вот решение, объединенное из этого и других ответов на 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;

Ответ 6

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

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

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

Приветствия

Ответ 7

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

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

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

Ответ 8

Проверить этот код на основе статьи 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.

Ответ 9

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

Ответ 10

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();
    }

Ответ 11

Оригинальные ответы на этот вопрос хороши, но более новые версии 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 вместо миль, используйте .0001 вместо .000621371192

MySql документы здесь

Ответ 12

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

Ответ 13

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

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

На основе формула 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 (в метрах)

Ответ 14

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

Ответ 16

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

Ответ 17

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

Ответ 18

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

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

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

Select * from Location

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