Calcolo della distanza tra due punti (latitudine, longitudine)


89

Sto cercando di calcolare la distanza tra due posizioni su una mappa. Ho memorizzato nei miei dati: Longitudine, Latitudine, X POS, Y POS.

Ho già utilizzato lo snippet di seguito.

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

Tuttavia non mi fido dei dati che ne derivano, sembra che stiano dando risultati leggermente imprecisi.

Alcuni dati di esempio nel caso ne avessi bisogno

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

Qualcuno potrebbe aiutarmi con il mio codice, non mi importa se vuoi aggiustare quello che ho già se hai un nuovo modo per ottenerlo sarebbe fantastico.

Indica in quale unità di misura si trovano i tuoi risultati.


Non dovresti dividere l'argomento per sine per l'addizionale / 2. Inoltre potresti avere una maggiore precisione nel raggio della Terra, così come usare alcuni Datum usati ad esempio dal sistema GPS (WGS-84) che approssima la Terra da un ellissoide (con raggi diversi all'equatore e ai poli)
Aki Suihkonen

@ Waller, perché non usi il tipo Geografia / Geometria (spaziale) per ottenere questo risultato?
Habib

3
Ho controllato i tuoi calcoli con Mathematica; pensa che la distanza in miglia statali (5280 piedi) sia 42,997, il che suggerisce che il tuo calcolo non è leggermente impreciso , piuttosto è selvaggiamente impreciso .
High Performance Mark

Risposte:


130

Poiché stai utilizzando SQL Server 2008, hai a disposizione il geographytipo di dati, progettato esattamente per questo tipo di dati:

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

SELECT @source.STDistance(@target)

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

(1 row(s) affected)

Dicendoci che si trova a circa 538 km da (vicino) Londra a (vicino) Edimburgo.

Naturalmente ci sarà una quantità di apprendimento da fare prima, ma una volta che lo sai è molto più facile che implementare il tuo calcolo Haversine; inoltre ottieni un SACCO di funzionalità.


Se si desidera mantenere la struttura dati esistente, è comunque possibile utilizzarla STDistance, costruendo geographyistanze adeguate utilizzando il Pointmetodo:

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 - la longitudine sarà negativa per i luoghi ad ovest del Primo Meridiano e positiva per i luoghi ad est di esso
AakashM

Mi hai salvato la giornata! .. Grazie mille!
Dhrumil Bhankhar

1
L'uso della funzione incorporata sembra essere MOLTO lento. Ad esempio, in un ciclo di 100.000 oggetti, ci vogliono 23 secondi invece di 1,4 secondi per la mia funzione definita dall'utente (vedi la risposta di Durai).
NickG

1
Volevo solo intervenire e confermare la raccomandazione di @AakashM per gli indici spaziali + ... per un'applicazione ETL, la differenza era migliore di diversi ordini di grandezza dopo aver implementato gli indici spaziali
Bill Anton

3
FYI: POINT (LONGITUDE LATITUDE) mentre geografia :: Point (LATITUDE, LONGITUDE, 4326)
Mzn

42

La seguente funzione fornisce la distanza tra due geocoordinate in miglia

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 

La seguente funzione fornisce la distanza tra due geocoordinati in chilometri

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

La seguente funzione fornisce la distanza tra due geocoordinati in chilometri utilizzando il tipo di dati Geografia che è stato introdotto in 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);

Utilizzo:

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

Riferimento: Ref1 , Ref2


2
Avevo bisogno di calcolare la distanza per i codici postali 35K rispetto ai codici postali di vari eventi ordinati in base alla distanza da un codice postale. Era un elenco di coordinate troppo grande per eseguire calcoli utilizzando il tipo di dati geography. Quando sono passato a utilizzare la soluzione basata su funzioni trigonometriche a riga singola sopra, ha funzionato molto più velocemente. Quindi usare i tipi di geografia solo per calcolare una distanza sembra essere costoso. Compratore stai attento.
Tombala

Molto utile per calcolare la distanza nella clausola WHERE di una query. Ho dovuto racchiudere un ABS () attorno all'espressione "set @ d =", perché ho trovato alcuni casi in cui la funzione restituiva una distanza negativa.
Joe Irby

1
La funzione per "distanza tra due coordinate geografiche in chilometri" non riesce se confrontiamo 2 punti uguali, ti dà l'errore "Si è verificata un'operazione in virgola mobile non valida"
RRM

2
Questo è stato ottimo ma non funziona per brevi distanze perché "decimal (8,4)" non fornisce una precisione sufficiente.
influente

1
@influent è corretto, questo non è utile per brevi distanze (5 miglia nel mio caso)
Roger

16

Sembra che Microsoft abbia invaso il cervello di tutti gli altri intervistati e li abbia costretti a scrivere soluzioni il più complicate possibile. Ecco il modo più semplice senza funzioni aggiuntive / dichiarazioni di dichiarazione:

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

Basta sostituire i dati, invece di LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2ad esempio:

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

2
per riferimento: STDistance () restituisce le distanze nell'unità di misura lineare del sistema di riferimento spaziale in cui sono definiti i dati geografici. Stai usando SRID 4326, il che significa che STDistance () restituisce le distanze in metri.
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

Utilizzo:

selezionare dbo.DistanceKM (37.848832506474, 37.848732506474, 27.83935546875, 27.83905546875)

Uscite:

0,02849639

È possibile modificare il parametro @R con float commentati.


Funziona perfettamente
Tejasvi Hegde

4

Poiché stai utilizzando SQL 2008 o versioni successive, ti consiglio di controllare il tipo di dati GEOGRAPHY . SQL ha il supporto integrato per le query geospaziali.

es. avresti una colonna nella tua tabella di tipo GEOGRAFIA che verrebbe popolata con una rappresentazione geospaziale delle coordinate (controlla il riferimento MSDN collegato sopra per esempi). Questo tipo di dati espone quindi metodi che ti consentono di eseguire un'intera serie di query geospaziali (ad esempio, trovare la distanza tra 2 punti)


Solo per aggiungere, ho provato il tipo di campo geografico, ma ho scoperto che l'utilizzo della funzione di Durai (utilizzando direttamente i valori di longitudine e latitudine) è molto più veloce. Guarda il mio esempio qui: stackoverflow.com/a/37326089/391605
Mike Gledhill

1

Oltre alle risposte precedenti, ecco un modo per calcolare la distanza all'interno di un 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

Utilizzo:

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

Anche le funzioni scalari funzionano ma sono molto inefficienti quando si calcolano grandi quantità di dati.

Spero che questo possa aiutare qualcuno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.