Алгоритм пошуку всіх місцезнаходжень широти довготи на певній відстані від даного місцезнаходження Lat Lng


85

Враховуючи базу даних місць із розташуванням широти та довготи, наприклад 40.8120390, -73.4889650, як я можу знайти всі місця на певній відстані від конкретного місця?

Здається, не дуже ефективно вибрати усі місця з БД, а потім пройти їх по одному, отримуючи відстань від початкового місця, щоб побачити, чи знаходяться вони в межах вказаної відстані. Чи є хороший спосіб звузити спочатку вибрані місця розташування з БД? Після того, як у мене (чи ні?) Є звужений набір локацій, я все одно проходжу їх по одному, щоб перевірити відстань, чи є кращий спосіб?

Мова, якою я це роблю, насправді не має значення. Дякую!


4
Це може бути те, що вам потрібно: en.wikipedia.org/wiki/K-d_tree
biziclop

1
Чи не міг один запит SQL вирішити це? SELECT * FROM Місця WHERE (Lat -: Lat) ^ 2 + (Long -: Long) ^ 2 <=: відстань ^ 2 (OFC, деякі інші математики бере участь із Землею сферичності і все, це просто приклад)
Dialecticus

1
@Ashu, nOiAd, на жаль, мені довелося відмовитись від цього проекту, тому в підсумку я не вибрав рішення. Якщо ви, хлопці, використовуєте одне з рішень у своїх проектах, я та інші будуть дуже вдячні за ваші коментарі щодо цього тут.
Валера

Відповіді:


41

Почніть із порівняння відстані між широтами. Кожен градус широти знаходиться приблизно на відстані 111 кілометрів. Діапазон варіюється (внаслідок злегка еліпсоїдної форми Землі) від 110,567 км на екваторі до 68,403 милі на екваторі до 69,407 (111,699 км) на полюсах. Відстань між двома місцями буде дорівнювати або перевищувати відстань між їх широтами.

Зверніть увагу, що це не відповідає довготам - довжина кожного градуса довготи залежить від широти. Однак, якщо ваші дані обмежені якоюсь областю (наприклад, однією країною) - ви також можете розрахувати мінімальну і максимальну межі для довгот.


Продовжимо низькоточний швидкий розрахунок відстані, який передбачає сферичну землю:

Відстань великого кола d між двома точками з координатами {lat1, lon1} та {lat2, lon2} визначається як:

d = acos(sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lon1-lon2))

Математично еквівалентна формула, яка менше піддається помилці округлення на короткі відстані:

d = 2*asin(sqrt((sin((lat1-lat2)/2))^2 +
    cos(lat1)*cos(lat2)*(sin((lon1-lon2)/2))^2))

d - відстань у радіанах

distance_km ≈ radius_km * distance_radians ≈ 6371 * d

(6371 км - це середній радіус землі )

Цей метод обчислювальних вимог є мінімальним. Однак результат дуже точний на невеликих відстанях.


Тоді, якщо воно знаходиться на певній відстані, більш-менш, використовуйте більш точний метод.

GeographicLib - найточніша реалізація, яку я знаю, хоча може бути використана і зворотна формула Вінсенті .


Якщо ви використовуєте СУБД, встановіть широту як основний ключ, а довготу як вторинний ключ. Запитуйте діапазон широти або діапазон широти / довготи, як описано вище, а потім обчисліть точні відстані для набору результатів.

Зауважте, що сучасні версії всіх основних СУБД підтримують географічні типи даних та запити.


Просто з головою, перша ланка порушена.
kunruh

@kunruh: Дякую. Посилання вказувало на авіаційний формуляр Еда Вільямса, який, схоже, зараз поза мережею. Я замінив посилання на формулу.
Lior Kogan,

Це посилання пояснило майже всі пов'язані з цією темою movable-type.co.uk/scripts/…
madeinQuant

14

На основі широти поточного користувача, довготи та відстані, яку ви хочете знайти, запит sql подано нижче.

SELECT * FROM(
    SELECT *,(((acos(sin((@latitude*pi()/180)) * sin((Latitude*pi()/180))+cos((@latitude*pi()/180)) * cos((Latitude*pi()/180)) * cos(((@longitude - Longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344) as distance FROM Distances) t
WHERE distance <= @distance

@ широта та @ довжина - це широта та довгота точки. Широта і довгота - це стовпці таблиці відстаней. Значення pi дорівнює 22/7


2
Параметр @distance - у KM або милях?
garfbradaz

Я припускаю, що відстань у KM, або мій сценарій буде помилковим. Хтось, будь ласка, дайте відповідь на вищезазначене питання.
Омар Аббас

7

Розширення ГІС PostgreSQL можуть бути корисними - як, наприклад, вони вже можуть реалізувати більшу частину функціональних можливостей, які ви думаєте впровадити.


5

Йогохостинг танка

У моїй базі даних є одна група таблиць з Open Streep Maps, і я пройшов успішне тестування.

Відстань добре працює в метрах.

SET @orig_lat=-8.116137;
SET @orig_lon=-34.897488;
SET @dist=1000;

SELECT *,(((acos(sin((@orig_lat*pi()/180)) * sin((dest.latitude*pi()/180))+cos((@orig_lat*pi()/180))*cos((dest.latitude*pi()/180))*cos(((@orig_lon-dest.longitude)*pi()/180))))*180/pi())*60*1.1515*1609.344) as distance FROM nodes AS dest HAVING distance < @dist ORDER BY distance ASC LIMIT 100;

Світ - це не сфера!
Toby Speight

Яка ваша пропозиція?
Гельмут Кемпер

2

Хоча це теоретично може дати відповідь на питання, переважно було б включити сюди основні частини відповіді та надати посилання для довідки.
Toby Speight

2

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


Хоча це може бути цінним підказкою для вирішення проблеми, відповідь дійсно повинна продемонструвати рішення. Будь ласка, відредагуйте, щоб навести приклад коду, щоб показати, що ви маєте на увазі. Крім того, розгляньте можливість писати це як коментар.
Toby Speight

1
Я насправді вважаю, що код тут буде відволікаючим - він буде занадто специфічним для бібліотеки, що містить деревоподібну структуру та певну обрану мову (зауважте, що це питання не позначено мовою.)
templatetypedef

1

Що вам потрібно, це просторовий пошук. Ви можете використовувати просторовий пошук Solr . Він також має вбудований тип даних lat / long, перевірте тут .


Хоча це теоретично може дати відповідь на питання, переважно було б включити сюди основні частини відповіді та надати посилання для довідки.
Toby Speight

0

Ви можете перетворити широту-довготу у формат UTM, який є метричним форматом, який може допомогти вам розрахувати відстані. Тоді ви легко зможете вирішити, чи потрапляє точка в певне місце.


1
Хоча це може бути цінним підказкою для вирішення проблеми, відповідь дійсно повинна продемонструвати рішення. Будь ласка, відредагуйте, щоб навести приклад коду, щоб показати, що ви маєте на увазі. Крім того, розгляньте можливість писати це як коментар.
Toby Speight

0

Оскільки ви говорите, що будь-яка мова прийнятна, природним вибором є PostGIS:

SELECT * FROM places
WHERE ST_DistanceSpheroid(geom, $location, $spheroid) < $max_metres;

Якщо ви хочете використовувати дату WGS, вам слід встановити $spheroidзначення'SPHEROID["WGS 84",6378137,298.257223563]'

Якщо припустити , що у вас є індексовані placesпо geomколонку, це повинно бути досить ефективним.


0

Завдяки рішенню, наданому @yogihosting, мені вдалося досягти подібного результату з безсхемових стовпців mysql з кодами, показаними нижче:

// @params - will be bound to named query parameters
$criteria = [];
$criteria['latitude'] = '9.0285183';
$criteria['longitude'] = '7.4869546';
$criteria['distance'] = 500;
$criteria['skill'] = 'software developer';

// Get doctrine connection 
$conn = $this->getEntityManager()->getConnection();

        $sql = '
               SELECT DISTINCT m.uuid AS phone, (((acos(sin((:latitude*pi()/180)) * sin((JSON_EXTRACT(m.location, "$.latitude")*pi()/180))+cos((:latitude*pi()/180)) * 
              cos((JSON_EXTRACT(m.location, "$.latitude")*pi()/180)) * 
              cos(((:longitude - JSON_EXTRACT(m.location, "$.longitude"))*pi()/180))))*180/pi())*60*1.1515*1.609344) AS distance FROM member_profile AS m 
               INNER JOIN member_card_subscription mcs ON mcs.primary_identity = m.uuid
               WHERE mcs.end > now() AND JSON_SEARCH(m.skill_logic, "one", :skill) IS NOT NULL  AND (((acos(sin((:latitude*pi()/180)) * sin((JSON_EXTRACT(m.location, "$.latitude")*pi()/180))+cos((:latitude*pi()/180)) * 
              cos((JSON_EXTRACT(m.location, "$.latitude")*pi()/180)) * 
              cos(((:longitude - JSON_EXTRACT(m.location, "$.longitude"))*pi()/180))))*180/pi())*60*1.1515*1.609344) <= :distance ORDER BY distance
               ';
        $stmt = $conn->prepare($sql);
        $stmt->execute(['latitude'=>$criteria['latitude'], 'longitude'=>$criteria['longitude'], 'skill'=>$criteria['skill'], 'distance'=>$criteria['distance']]);
        var_dump($stmt->fetchAll());

Зверніть увагу, що наведений вище фрагмент коду використовує доктрину підключення до бази даних та PHP


-2

Ви можете перевірити це рівняння, я думаю, це допоможе

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;

Хоча цей код може допомогти вирішити проблему, він не пояснює, чому та / або як він відповідає на питання. Забезпечення цього додаткового контексту суттєво покращило б його довгострокову освітню цінність. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення, включаючи обмеження та припущення. Зокрема, звідки беруться магічні значення 3959 та 37?
Toby Speight
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.