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

89

Я пытаюсь рассчитать расстояние между двумя позициями на карте. Я сохранил в своих данных: Долгота, Широта, 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, почему бы вам не использовать тип Geography / Geometry (Spatial) для этого?
Хабиб,
3
Я проверил ваш расчет в системе Mathematica; он считает, что расстояние в статутных милях (5280 футов) составляет 42,997, что говорит о том, что ваши вычисления не являются немного неточными , скорее, это дико неточно .
High Performance Mark

Ответы:

129

Поскольку вы используете 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 км от (около) Лондона до (около) Эдинбурга.

Естественно, сначала нужно будет поучиться, но как только вы это узнаете, это намного проще, чем реализовать собственное вычисление Хаверсина; плюс вы получаете МНОГО функциональных возможностей.


Если вы хотите сохранить существующую структуру данных, вы все равно можете использовать 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 no - долгота будет отрицательной для мест к западу от премьер-меридиана и положительной для мест к востоку от него
AakashM
Ты спас мне день! .. Большое спасибо!
Друмил Бханкхар
1
Использование встроенной функции кажется ОЧЕНЬ медленным. Например, в цикле из 100000 элементов для моей пользовательской функции требуется 23 секунды вместо 1,4 секунды (см. Ответ Дурая).
NickG 02
1
Просто хотел вмешаться и подтвердить рекомендацию @AakashM для пространственных индексов + ... для приложения ETL разница была на несколько порядков лучше после внедрения пространственных индексов
Билл Антон
3
К вашему сведению: ТОЧКА (ДОЛГОВАЯ ШИРОТА), тогда как география :: Точка (ШИРОТА, ДОЛГОТА, 4326)
Mzn
42

Следующая функция показывает расстояние между двумя геокоординатами в милях.

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

Приведенная ниже функция дает расстояние между двумя географическими координатами в километрах с использованием типа данных Geography, который был введен в 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

Дурай Амутан.H
источник
2
Мне нужно было сделать расчет расстояния для почтовых индексов 35K по почтовым индексам различных событий, упорядоченных по расстоянию до почтового индекса. Список координат слишком велик для выполнения вычислений с использованием типа данных geography. Когда я переключился на решение на основе однострочных триггерных функций, описанное выше, оно работало намного быстрее. Таким образом, использование географических типов только для расчета расстояния кажется дорогостоящим. Покупатель, будь осторожен.
Tombala
Очень полезно для вычисления расстояния в предложении WHERE запроса. Мне пришлось обернуть ABS () вокруг выражения "set @ d =", потому что я обнаружил несколько случаев, когда функция возвращала отрицательное расстояние.
Джо Ирби
1
Функция «расстояние между двумя геокоординатами в километрах» не работает, если мы сравниваем две равные точки, выдает ошибку «Произошла недопустимая операция с плавающей запятой»
RRM
2
Это было здорово, но не работает на коротких расстояниях, потому что десятичное (8,4) не обеспечивает достаточной точности.
Influent
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 () возвращает расстояние в метрах.
Брайан Стамп
5
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 с помощью закомментированных поплавков.

Фатих К.
источник
Отлично работает
Tejasvi Hegde
4

Поскольку вы используете SQL 2008 или новее, я бы рекомендовал проверить тип данных GEOGRAPHY . SQL имеет встроенную поддержку геопространственных запросов.

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

AdaTheDev
источник
Чтобы добавить, я попробовал тип поля geography, но обнаружил, что использование функции Durai (напрямую с использованием значений долготы и широты) намного быстрее. См. Мой пример здесь: stackoverflow.com/a/37326089/391605
Майк Гледхилл,
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)

Скалярные функции также работают, но они очень неэффективны при вычислении больших объемов данных.

Надеюсь, это может кому-то помочь.

Турфир
источник