Obliczanie odległości między dwoma punktami (szerokość, długość)

89

Próbuję obliczyć odległość między dwoma pozycjami na mapie. Zapisałem w swoich danych: długość, szerokość geograficzną, X POS, Y POS.

Wcześniej korzystałem z poniższego fragmentu kodu.

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

Nie ufam jednak wynikającym z tego danym, wydaje się, że dają one nieco niedokładne wyniki.

Przykładowe dane na wypadek, gdybyś ich potrzebował

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

Czy ktoś mógłby mi pomóc z moim kodem, nie mam nic przeciwko, jeśli chcesz naprawić to, co już mam, jeśli masz nowy sposób osiągnięcia tego, który byłby świetny.

Podaj jednostkę miary, w której znajdują się Twoje wyniki.

Murarz dołowy
źródło
Nie należy dzielić argumentu sinus przez dodatkowe / 2. Możesz również mieć większą dokładność w promieniu Ziemi, a także używając niektórych Datum używanych np. Przez system GPS (WGS-84), który aproksymuje Ziemię za pomocą elipsoidy (z różnymi promieniami na równiku i do biegunów)
Aki Suihkonen
@Waller, dlaczego nie użyjesz typu Geography / Geometry (Spatial), aby to osiągnąć?
Habib
3
Sprawdziłem twoje obliczenia w Mathematica; uważa, że ​​odległość w milach ustawowych (5280 stóp) wynosi 42,997, co sugeruje, że obliczenia nie są trochę niedokładne , a raczej są bardzo niedokładne .
Znak wysokiej wydajności

Odpowiedzi:

129

Ponieważ używasz SQL Server 2008, masz geographydostępny typ danych, który jest przeznaczony dokładnie dla tego rodzaju danych:

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

SELECT @source.STDistance(@target)

Daje

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

(1 row(s) affected)

Mówiąc nam, że jest to około 538 km od (blisko) Londynu do (blisko) Edynburga.

Oczywiście najpierw trzeba się dużo nauczyć, ale kiedy już się zorientujesz, będzie to o wiele łatwiejsze niż wdrożenie własnych obliczeń Haversine; plus masz DUŻO funkcjonalności.


Jeśli chcesz zachować istniejącą strukturę danych, możesz nadal używać STDistance, konstruując odpowiednie geographyinstancje za pomocą Pointmetody:

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
AakashM
źródło
6
@nezam nie - długość geograficzna będzie ujemna dla miejsc na zachód od południka głównego i dodatnia dla miejsc na wschód od niego
AakashM
Uratowałeś mi dzień! .. Wielkie dzięki!
Dhrumil Bhankhar
1
Używanie wbudowanej funkcji wydaje się BARDZO powolne. Np. W pętli 100 000 pozycji, moja funkcja zdefiniowana przez użytkownika zajmuje 23 sekundy zamiast 1,4 sekundy (patrz odpowiedź Durai).
NickG
1
Chciałem tylko wbić się i potwierdzić zalecenie @AakashM dotyczące indeksów przestrzennych + ... dla aplikacji ETL różnica była o kilka rzędów wielkości lepsza po wdrożeniu indeksów przestrzennych
Bill Anton
3
FYI: POINT (LONGITUDE LATITUDE) natomiast geografia :: Point (LATITUDE, LONGITUDE, 4326)
Mzn
42

Poniższa funkcja podaje odległość między dwoma współrzędnymi geograficznymi w milach

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 

Poniższa funkcja podaje odległość między dwoma współrzędnymi geograficznymi w kilometrach

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

Poniższa funkcja podaje odległość między dwoma współrzędnymi geograficznymi w kilometrach przy użyciu typu danych Geography, który został wprowadzony na serwerze sql 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);

Stosowanie:

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

Odniesienie: Ref1 , Ref2

Durai Amuthan.H
źródło
2
Musiałem obliczyć odległość dla kodów pocztowych 35 000 na podstawie kodów pocztowych różnych wydarzeń uporządkowanych według odległości do kodu pocztowego. Lista współrzędnych była zbyt duża, aby wykonać obliczenia przy użyciu typu danych geograficznych. Kiedy przełączyłem się na rozwiązanie oparte na jednokreskowych funkcjach trygonometrycznych powyżej, działało znacznie szybciej. Dlatego używanie typów geograficznych tylko do obliczenia odległości wydaje się kosztowne. Kupujący, strzeż się.
Tombala
Bardzo przydatne do obliczania odległości w klauzuli WHERE zapytania. Musiałem owinąć ABS () wokół wyrażenia „set @ d =”, ponieważ znalazłem kilka przypadków, w których funkcja zwracała ujemną odległość.
Joe Irby,
1
Funkcja „odległość między dwoma współrzędnymi geograficznymi w kilometrach” zawodzi, jeśli porównamy 2 równe punkty, daje to błąd „Wystąpiła nieprawidłowa operacja zmiennoprzecinkowa”
RRM
2
To było świetne, ale nie działa na krótkich dystansach, ponieważ „decimal (8,4)” nie zapewnia wystarczającej precyzji.
wpływowy
1
@influent jest poprawne, nie jest to przydatne na krótkich dystansach (w moim przypadku 5 mil)
Roger
15

Wygląda na to, że Microsoft zaatakował mózgi wszystkich pozostałych respondentów i zmusił ich do napisania jak najbardziej skomplikowanych rozwiązań. Oto najprostszy sposób bez żadnych dodatkowych funkcji / deklaracji:

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

Wystarczy podstawić dane zamiast LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2np:

SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c
Stalinko
źródło
2
dla odniesienia: STDistance () zwraca odległości w liniowej jednostce miary systemu odniesień przestrzennych, w którym zdefiniowane są dane geograficzne. Używasz SRID 4326, co oznacza, że ​​STDistance () zwraca odległości w metrach.
Bryan Stump
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

Stosowanie:

wybierz dbo.DistanceKM (37,848832506474, 37,848732506474, 27,83935546875, 27,83905546875)

Wyjścia:

0,02849639

Możesz zmienić parametr @R za pomocą komentarzy zmiennoprzecinkowych.

Fatih K.
źródło
Działa idealnie
Tejasvi Hegde
4

Ponieważ używasz SQL 2008 lub nowszego, polecam zapoznać się z GEOGRAFIĄ typu danych . SQL ma wbudowaną obsługę zapytań geoprzestrzennych.

np. miałbyś kolumnę w swojej tabeli typu GEOGRAFIA, w której byłaby umieszczona geoprzestrzenna reprezentacja współrzędnych (przykłady można znaleźć w odnośniku MSDN, do którego link znajduje się powyżej). Ten typ danych udostępnia następnie metody umożliwiające wykonanie wielu zapytań geoprzestrzennych (np. Znajdowanie odległości między 2 punktami)

AdaTheDev
źródło
Aby dodać, wypróbowałem typ pola geograficznego, ale stwierdziłem, że użycie funkcji Durai (bezpośrednio przy użyciu wartości długości i szerokości geograficznej) jest znacznie szybsze. Zobacz mój przykład tutaj: stackoverflow.com/a/37326089/391605
Mike Gledhill
1

Oprócz poprzednich odpowiedzi, oto sposób obliczenia odległości wewnątrz 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

Stosowanie:

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

Funkcje skalarne również działają, ale są bardzo nieefektywne podczas obliczania dużej ilości danych.

Mam nadzieję, że to może komuś pomóc.

Thurfir
źródło