Обчислення відстані між двома точками (широта, довгота)


88

Я намагаюся розрахувати відстань між двома положеннями на карті. Я зберігав у своїх даних: довготу, широту, X POS, Y POS.

Я раніше використовував наведений нижче фрагмент.

DECLARE @orig_lat DECIMAL
DECLARE @orig_lng DECIMAL
SET @orig_lat=53.381538 set @orig_lng=-1.463526
SELECT *,
    3956 * 2 * ASIN(
          SQRT( POWER(SIN((@orig_lat - abs(dest.Latitude)) * pi()/180 / 2), 2) 
              + COS(@orig_lng * pi()/180 ) * COS(abs(dest.Latitude) * pi()/180)  
              * POWER(SIN((@orig_lng - dest.Longitude) * pi()/180 / 2), 2) )) 
          AS distance
--INTO #includeDistances
FROM #orig dest

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

Деякі зразки даних на випадок, якщо вони вам потрібні

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

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

Будь ласка, вкажіть, в якій одиниці виміру знаходяться ваші результати.


Не слід ділити аргумент на синус на додатковий / 2. Також ви могли б отримати більшу точність в радіусі Землі, а також використовувати деякі дані, що використовуються, наприклад, системою GPS (WGS-84), яка наближує Землю еліпсоїдом (з різними радіусами на екваторі та полюсах)
Акі Суйхконен,

@Waller, чому ти не використовуєш для цього географічний / геометричний (просторовий) тип?
Хабіб,

3
Я перевірив ваш розрахунок у Mathematica; він вважає, що відстань у статутних милях (5280 футів) становить 42,997, що свідчить про те, що ваші обчислення є дещо неточними , скоріше вони дико неточними .
High Performance Mark

Відповіді:


128

Оскільки ви використовуєте SQL Server 2008, у вас є geographyдоступний тип даних, який розроблений саме для таких типів даних:

DECLARE @source geography = 'POINT(0 51.5)'
DECLARE @target geography = 'POINT(-3 56)'

SELECT @source.STDistance(@target)

Дає

----------------------
538404.100197555

(1 row(s) affected)

Як ми розповідаємо, це приблизно 538 км від (поблизу) Лондона до (поблизу) Единбурга.

Звичайно, навчитися робити потрібно буде спочатку, але як тільки ти це знаєш, це набагато простіше, ніж впровадження власного обчислення Haversine; плюс ви отримуєте БАГАТО функціональних можливостей.


Якщо ви хочете зберегти свою існуючу структуру даних, ви все одно можете використовувати STDistance, побудувавши відповідні geographyекземпляри за допомогою Pointметоду:

DECLARE @orig_lat DECIMAL(12, 9)
DECLARE @orig_lng DECIMAL(12, 9)
SET @orig_lat=53.381538 set @orig_lng=-1.463526

DECLARE @orig geography = geography::Point(@orig_lat, @orig_lng, 4326);

SELECT *,
    @orig.STDistance(geography::Point(dest.Latitude, dest.Longitude, 4326)) 
       AS distance
--INTO #includeDistances
FROM #orig dest

6
@nezam ні - довгота буде негативною для місць на захід від першого меридіана , а позитивна для місць на схід від нього
AakashM

Ти врятував мій день! .. Велике спасибі!
Друміл Бханхар

1
Використання вбудованої функції здається ДУЖЕ повільним. Наприклад, у циклі 100 000 предметів, для моєї функції, що визначається користувачем, потрібно 23 секунди замість 1,4 секунди (див. Відповідь Дюрая).
NickG 02

1
Просто хотів підслухати і підтвердити рекомендацію @AakashM щодо просторових індексів + ... для програми ETL різниця була на кілька порядків кращою після впровадження просторових індексів
Білл Антон,

3
FYI: POINT (LONGITUDE LATITUDE), тоді як географія :: Point (LOTITUDE, LONGITUDE, 4326)
Mzn

41

Наведена нижче функція дає відстань між двома геокоординатами в милях

create function [dbo].[fnCalcDistanceMiles] (@Lat1 decimal(8,4), @Long1 decimal(8,4), @Lat2 decimal(8,4), @Long2 decimal(8,4))
returns decimal (8,4) as
begin
declare @d decimal(28,10)
-- Convert to radians
set @Lat1 = @Lat1 / 57.2958
set @Long1 = @Long1 / 57.2958
set @Lat2 = @Lat2 / 57.2958
set @Long2 = @Long2 / 57.2958
-- Calc distance
set @d = (Sin(@Lat1) * Sin(@Lat2)) + (Cos(@Lat1) * Cos(@Lat2) * Cos(@Long2 - @Long1))
-- Convert to miles
if @d <> 0
begin
set @d = 3958.75 * Atan(Sqrt(1 - power(@d, 2)) / @d);
end
return @d
end 

Наведена нижче функція дає відстань між двома геокоординатами в кілометрах

CREATE FUNCTION dbo.fnCalcDistanceKM(@lat1 FLOAT, @lat2 FLOAT, @lon1 FLOAT, @lon2 FLOAT)
RETURNS FLOAT 
AS
BEGIN

    RETURN ACOS(SIN(PI()*@lat1/180.0)*SIN(PI()*@lat2/180.0)+COS(PI()*@lat1/180.0)*COS(PI()*@lat2/180.0)*COS(PI()*@lon2/180.0-PI()*@lon1/180.0))*6371
END

Наведена нижче функція надає відстань між двома геокоординатами в кілометрах, використовуючи тип даних Географія, який був введений в SQL Server 2008

DECLARE @g geography;
DECLARE @h geography;
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);
SET @h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);
SELECT @g.STDistance(@h);

Використання:

select [dbo].[fnCalcDistanceKM](13.077085,80.262675,13.065701,80.258916)

Довідково: Ref1 , Ref2


2
Мені потрібно було розрахувати відстань для поштових індексів 35 КБ на основі поштових індексів різних подій, упорядкованих за відстанню до поштового індексу. Це був занадто великий перелік координат, щоб робити обчислення, використовуючи тип географічних даних. Коли я перейшов на використання однолінійного рішення на основі функцій тригера, воно працювало набагато швидше. Тому використання типів географії просто для обчислення відстані здається дорогим. Покупець обережно.
Томбала

Дуже корисно для розрахунку відстані в реченні запиту WHERE. Мені довелося обернути ABS () навколо виразу "set @ d =", тому що я знайшов деякі випадки, коли функція повертала від'ємну відстань.
Джо Ірбі,

1
Функція "відстань між двома геокоординатами в кілометрах" виходить з ладу, якщо порівнювати 2 рівні точки, це видає помилку "Сталася недійсна операція з плаваючою точкою"
RRM

2
Це було чудово, але не працює на короткі відстані, оскільки "десятковий (8,4)" не забезпечує достатньої точності.
впливовий

1
@influent правильний, це не корисно на коротких відстанях (у моєму випадку 5 миль)
Роджер

15

Схоже, Microsoft вторглася в мозок усіх інших респондентів і змусила їх писати якомога складніші рішення. Ось найпростіший спосіб без будь-яких додаткових операторів / оголошень:

SELECT geography::Point(LATITUDE_1, LONGITUDE_1, 4326).STDistance(geography::Point(LATITUDE_2, LONGITUDE_2, 4326))

Просто підставляйте свої дані , а LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2наприклад:

SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c

2
для довідки: STDistance () повертає відстані в лінійній одиниці виміру просторової системи відліку, в якій визначені ваші географічні дані. Ви використовуєте SRID 4326, що означає, що STDistance () повертає відстань у метрах.
Брайан Стамп

4

Оскільки ви використовуєте SQL 2008 або пізнішої версії, я рекомендую перевірити тип даних GEOGRAPHY . SQL вбудував підтримку геопросторових запитів.

наприклад, у вашій таблиці буде стовпець типу GEOGRAPHY, який буде заповнений геопросторовим поданням координат (перегляньте посилання MSDN, зв’язане вище для прикладів). Потім цей тип даних надає методи, що дозволяють виконувати цілий набір геопросторових запитів (наприклад, знаходити відстань між 2 точками)


Щоб додати лише, я спробував тип поля географії, але виявив, що використання функції Дурея (безпосередньо з використанням значень довготи та широти) було набагато швидшим. Подивіться мій приклад тут: stackoverflow.com/a/37326089/391605
Майк Гледхілл

4
Create Function [dbo].[DistanceKM] 
( 
      @Lat1 Float(18),  
      @Lat2 Float(18), 
      @Long1 Float(18), 
      @Long2 Float(18)
)
Returns Float(18)
AS
Begin
      Declare @R Float(8); 
      Declare @dLat Float(18); 
      Declare @dLon Float(18); 
      Declare @a Float(18); 
      Declare @c Float(18); 
      Declare @d Float(18);
      Set @R =  6367.45
            --Miles 3956.55  
            --Kilometers 6367.45 
            --Feet 20890584 
            --Meters 6367450 


      Set @dLat = Radians(@lat2 - @lat1);
      Set @dLon = Radians(@long2 - @long1);
      Set @a = Sin(@dLat / 2)  
                 * Sin(@dLat / 2)  
                 + Cos(Radians(@lat1)) 
                 * Cos(Radians(@lat2))  
                 * Sin(@dLon / 2)  
                 * Sin(@dLon / 2); 
      Set @c = 2 * Asin(Min(Sqrt(@a))); 

      Set @d = @R * @c; 
      Return @d; 

End
GO

Використання:

виберіть dbo.DistanceKM (37,848832506474, 37,848732506474, 27,83935546875, 27,83905546875)

Виходи:

0,02849639

Ви можете змінити параметр @R за допомогою коментованих поплавців.


Працює ідеально
Теясві Хегде

1

На додаток до попередніх відповідей, ось спосіб обчислення відстані всередині SELECT:

CREATE FUNCTION Get_Distance
(   
    @La1 float , @Lo1 float , @La2 float, @Lo2 float
)
RETURNS TABLE 
AS
RETURN 
    -- Distance in Meters
    SELECT GEOGRAPHY::Point(@La1, @Lo1, 4326).STDistance(GEOGRAPHY::Point(@La2, @Lo2, 4326))
    AS Distance
GO

Використання:

select Distance
from Place P1,
     Place P2,
outer apply dbo.Get_Distance(P1.latitude, P1.longitude, P2.latitude, P2.longitude)

Скалярні функції також працюють, але вони дуже неефективні при обчисленні великого обсягу даних.

Сподіваюсь, це може комусь допомогти.

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