Berechnung der Entfernung zwischen zwei Punkten (Breite, Länge)

88

Ich versuche, die Entfernung zwischen zwei Positionen auf einer Karte zu berechnen. Ich habe in meinen Daten gespeichert: Längengrad, Breitengrad, X POS, Y POS.

Ich habe zuvor das folgende Snippet verwendet.

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

Ich vertraue jedoch nicht den Daten, die daraus hervorgehen, es scheint etwas ungenaue Ergebnisse zu liefern.

Einige Beispieldaten für den Fall, dass Sie sie benötigen

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

Könnte mir jemand mit meinem Code helfen, es macht mir nichts aus, wenn Sie das reparieren möchten, was ich bereits habe, wenn Sie einen neuen Weg haben, dies zu erreichen, der großartig wäre.

Bitte geben Sie an, in welcher Maßeinheit sich Ihre Ergebnisse befinden.

Waller
quelle
Sie sollten das Argument zum Sinus nicht durch das zusätzliche / 2 teilen. Sie könnten auch eine genauere Darstellung des Erdradius haben und ein Datum verwenden, das beispielsweise vom GPS-System (WGS-84) verwendet wird und das die Erde durch ein Ellipsoid (mit unterschiedlichen Radien am Äquator und an den Polen) approximiert
Aki Suihkonen,
@Waller, warum verwenden Sie nicht den Typ Geographie / Geometrie (räumlich), um dies zu erreichen?
Habib
3
Ich habe Ihre Berechnung mit Mathematica überprüft. Es wird davon ausgegangen, dass die Entfernung in gesetzlichen Meilen (5280 Fuß) 42,997 beträgt, was darauf hindeutet, dass Ihre Berechnung nicht leicht ungenau , sondern äußerst ungenau ist .
High Performance Mark

Antworten:

126

Da Sie SQL Server 2008 verwenden, steht Ihnen der geographyDatentyp zur Verfügung, der genau für diese Art von Daten ausgelegt ist:

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

SELECT @source.STDistance(@target)

Gibt

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

(1 row(s) affected)

Es ist ungefähr 538 km von (nahe) London nach (nahe) Edinburgh entfernt.

Natürlich wird es zuerst eine Menge zu lernen geben, aber sobald Sie es wissen, ist es weitaus einfacher, als Ihre eigene Haversine-Berechnung zu implementieren. Außerdem erhalten Sie eine Menge Funktionen.


Wenn Sie Ihre vorhandene Datenstruktur beibehalten möchten, können Sie sie weiterhin verwenden STDistance, indem Sie geographymithilfe der folgenden PointMethode geeignete Instanzen erstellen :

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
quelle
6
@nezam nein - wird die Länge negativ sein für Orte westlich des Nullmeridians und positiv für Orte östlich davon
AakashM
Du hast meinen Tag gerettet! Vielen Dank!
Dhrumil Bhankhar
1
Die Verwendung der eingebauten Funktion scheint SEHR langsam zu sein. Beispielsweise dauert es in einer 100.000-Artikel-Schleife 23 Sekunden statt 1,4 Sekunden für meine benutzerdefinierte Funktion (siehe Durais Antwort).
NickG
1
Ich wollte nur die Empfehlung von @AakashM für räumliche Indizes + ... für eine ETL-Anwendung bestätigen und bestätigen. Der Unterschied war nach der Implementierung der räumlichen Indizes um mehrere Größenordnungen besser
Bill Anton,
3
Zu Ihrer Information: POINT (LONGITUDE LATITUDE), während Geographie :: Point (LATITUDE, LONGITUDE, 4326)
Mzn
41

Die folgende Funktion gibt den Abstand zwischen zwei Geokoordinaten in Meilen an

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 

Die folgende Funktion gibt den Abstand zwischen zwei Geokoordinaten in Kilometern an

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

Die unten Funktion gibt Abstand zwischen zwei Geokoordinaten in Kilometern mit Geographie - Datentyp , die in SQL Server 2008 eingeführt wurde

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

Verwendung:

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

Referenz: Ref1 , Ref2

Durai Amuthan.H
quelle
2
Ich musste eine Entfernungsberechnung für 35K-Postleitzahlen anhand der Postleitzahlen verschiedener Ereignisse durchführen, die nach Entfernung zu einer Postleitzahl geordnet waren. Die Liste der Koordinaten war zu groß, um Berechnungen mit dem Geografiedatentyp durchzuführen. Als ich auf die oben beschriebene Lösung mit einzeiligen Triggerfunktionen umstieg, lief sie viel schneller. Die Verwendung von Geografietypen nur zur Berechnung einer Entfernung scheint daher teuer zu sein. Käufer aufgepasst.
Tombala
Sehr nützlich für die Berechnung der Entfernung in der WHERE-Klausel einer Abfrage. Ich musste ein ABS () um den Ausdruck "set @ d =" wickeln, da ich einige Fälle fand, in denen die Funktion einen negativen Abstand zurückgab.
Joe Irby
1
Die Funktion für "Entfernung zwischen zwei Geokoordinaten in Kilometern" schlägt fehl, wenn wir 2 gleiche Punkte vergleichen. Sie erhalten den Fehler "Eine ungültige Gleitkommaoperation ist aufgetreten"
RRM
2
Dies war großartig, funktioniert aber nicht für kurze Entfernungen, da "decimal (8,4)" nicht genügend Präzision bietet.
Einfluss
1
@influent ist korrekt, dies ist nicht nützlich für kurze Strecken (5 Meilen in meinem Fall)
Roger
15

Es sieht so aus, als ob Microsoft in die Gehirne aller anderen Befragten eingedrungen ist und sie dazu gebracht hat, so komplizierte Lösungen wie möglich zu schreiben. Hier ist der einfachste Weg ohne zusätzliche Funktionen / Deklarationsanweisungen:

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

Ersetzen Sie einfach Ihre Daten statt LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2zB:

SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c
Stalinko
quelle
1
als Referenz: STDistance () gibt Entfernungen in der linearen Maßeinheit des räumlichen Bezugssystems zurück, in dem Ihre Geografiedaten definiert sind. Sie verwenden SRID 4326, was bedeutet, dass STDistance () Entfernungen in Metern zurückgibt.
Bryan Stump
4

Wenn Sie SQL 2008 oder höher verwenden, würde ich empfehlen, den Datentyp GEOGRAPHY zu überprüfen . SQL bietet Unterstützung für Geodatenabfragen.

Beispiel: In Ihrer Tabelle befindet sich eine Spalte vom Typ GEOGRAPHIE, die mit einer räumlichen Darstellung der Koordinaten gefüllt wird (Beispiele finden Sie in der oben verlinkten MSDN-Referenz). Dieser Datentyp stellt dann Methoden bereit, mit denen Sie eine ganze Reihe von Geodatenabfragen durchführen können (z. B. den Abstand zwischen zwei Punkten ermitteln).

AdaTheDev
quelle
Um es hinzuzufügen, habe ich den Geografiefeldtyp ausprobiert, aber festgestellt, dass die Verwendung der Durai-Funktion (direkt unter Verwendung von Längen- und Breitengraden) viel schneller ist. Siehe mein Beispiel hier: stackoverflow.com/a/37326089/391605
Mike Gledhill
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

Verwendung:

Wählen Sie dbo.DistanceKM (37.848832506474, 37.848732506474, 27.83935546875, 27.83905546875).

Ausgänge:

0,02849639

Sie können den @ R-Parameter mit kommentierten Floats ändern.

Fatih K.
quelle
Funktioniert perfekt
Tejasvi Hegde
1

Zusätzlich zu den vorherigen Antworten können Sie hier die Entfernung innerhalb eines SELECT berechnen:

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

Verwendung:

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

Skalarfunktionen funktionieren ebenfalls, sind jedoch bei der Berechnung großer Datenmengen sehr ineffizient.

Ich hoffe das könnte jemandem helfen.

Thurfir
quelle