2012-10-23 60 views
60

Sto provando a calcolare la distanza tra due posizioni su una mappa. Ho memorizzato i miei dati: Longitudine, Latitudine, X POS, Y POS.Calcolo della distanza tra due punti (latitudine, longitudine)

Ho utilizzato in precedenza il frammento 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 

Non mi fido comunque dei dati che ne derivano, sembra che dia risultati leggermente imprecisi.

Alcuni dati di esempio in caso di necessità

Latitude  Longitude  Distance 
53.429108  -2.500953  85.2981833133896 

Qualcuno potrebbe aiutarmi con il mio codice, non mi importa se si vuole risolvere quello che ho già avere se si dispone di un nuovo modo per raggiungere questo sarebbe fantastico.

si prega di indicare quale unità di misura i risultati sono in.

+0

Quale sistema di database? – AakashM

+0

MSSQL 2008 R2 .. – Waller

+0

Non si deve dividere l'argomento di seno con l'ulteriore/2. Inoltre, potresti avere una maggiore precisione nel raggio della Terra e utilizzare alcuni _Datum_ utilizzati per es.dal sistema GPS (WGS-84) che approssima la Terra con un ellissoide (con raggi diversi all'equatore e ai poli) –

risposta

90

Dato che si utilizza SQL Server 2008, è disponibile il tipo di dati geography, 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) 

che ci dice che si trova a circa 538 km da (quasi) Londra (vicino) Edimburgo.

Naturalmente ci sarà una quantità di apprendimento da fare prima, ma una volta che lo si conosce è molto più facile che implementare il proprio calcolo di Haversine; in più ottieni una MOLTA funzionalità.


Se si desidera mantenere la vostra struttura dati esistenti, è comunque possibile utilizzare STDistance, costruendo opportuni geography casi utilizzando il metodo 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 
+1

è obbligatorio che la longitudine sia in negativo? – Nezam

+5

@nezam no - la longitudine sarà negativa per i luoghi ad ovest del [Prime Meridian] (http://en.wikipedia.org/wiki/Prime_meridian), e positiva per i luoghi ad est di esso – AakashM

+0

Hai salvato la mia giornata! .. Molte grazie! –

3

Come stai usando SQL 2008 o versioni successive, mi consiglia di verificare il tipo di dati GEOGRAPHY. SQL ha incorporato il supporto per le query geospaziali.

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

+0

Giusto per aggiungere, ho provato il tipo di campo geografico, ma trovato usando la funzione di Durai (usando direttamente i valori di longitudine e latitudine) per essere * molto * più veloce. Vedere il mio esempio qui: http://stackoverflow.com/a/37326089/391605 –

31

La funzione qui sotto dà distanza tra due geocoordinates 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 funzione qui sotto dà distanza tra due geocoordinates in Km

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 funzione seguente fornisce distanza tra due geocoordinates in Km utilizzando Geography tipo di dati che è stato introdotto nel 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); 

utilizzati:

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

Riferimento:Ref1, Ref2

+1

Avevo bisogno di fare il calcolo della distanza per codici postali 35K contro i codici postali di vari eventi ordinati per distanza a un codice postale . Era troppo grande di un elenco di coordinate per eseguire calcoli utilizzando il tipo di dati geografici. Quando sono passato a utilizzare la soluzione basata su funzioni trigonometriche a linea singola sopra, è andato molto più veloce. Quindi usare i tipi di geografia solo per calcolare una distanza sembra essere costoso. Compratore stai attento. – Tombala

+0

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

+0

La funzione per "distanza tra due geocoordinati in chilometri" fallisce se confrontiamo 2 punti uguali, ti dà errore "Un'operazione virgola mobile non valida avvenuta" – RRM

2
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 

utilizzati:

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

Uscite:

0,02849639

si può cambiare @R parametro con carri commentati.

0

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

Usage: funzioni

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

scalari anche lavorare, ma sono molto inefficiente quando calcolo di una grande quantità di dati.

Spero che questo possa aiutare qualcuno.

Problemi correlati