Calcular la distancia entre dos puntos (latitud, longitud)

89

Estoy tratando de calcular la distancia entre dos posiciones en un mapa. Tengo almacenados en mis datos: Longitud, Latitud, X POS, Y POS.

Anteriormente he estado usando el siguiente fragmento.

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

Sin embargo, no confío en los datos que surgen de esto, parece dar resultados ligeramente inexactos.

Algunos datos de muestra en caso de que los necesite

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

¿Alguien podría ayudarme con mi código? No me importa si quieres arreglar lo que ya tengo si tienes una nueva forma de lograrlo que sería genial.

Indique en qué unidad de medida se encuentran sus resultados.

Waller
fuente
No debe dividir el argumento en seno por el / 2 adicional. También podría tener más precisión en el radio de la Tierra, además de usar algunos Datum utilizados, por ejemplo, por el sistema GPS (WGS-84) que se aproxima a la Tierra mediante un elipsoide (con diferentes radios en el ecuador y los polos)
Aki Suihkonen
@Waller, ¿por qué no usa el tipo Geografía / Geometría (espacial) para lograr esto?
Habib
3
Verifiqué su cálculo con Mathematica; cree que la distancia en millas terrestres (5280 pies) es 42,997, lo que sugiere que su cálculo no es levemente inexacto , sino que es tremendamente inexacto .
Marca de alto rendimiento

Respuestas:

129

Dado que está utilizando SQL Server 2008, tiene el geographytipo de datos disponible, que está diseñado exactamente para este tipo de datos:

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

SELECT @source.STDistance(@target)

Da

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

(1 row(s) affected)

Nos dice que hay unos 538 km desde (cerca) de Londres a (cerca) de Edimburgo.

Naturalmente, primero habrá mucho que aprender, pero una vez que lo sepa, será mucho más fácil que implementar su propio cálculo de Haversine; además, obtienes MUCHA funcionalidad.


Si desea conservar su estructura de datos existente, aún puede usar STDistance, construyendo geographyinstancias adecuadas usando el Pointmétodo:

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
fuente
6
@nezam no: la longitud será negativa para los lugares al oeste del primer meridiano y positiva para los lugares al este del mismo
AakashM
¡Me salvaste el día! .. ¡Muchas gracias!
Dhrumil Bhankhar
1
El uso de la función incorporada parece ser MUY lento. Por ejemplo, en un ciclo de 100,000 elementos, toma 23 segundos en lugar de 1.4 segundos para mi función definida por el usuario (vea la respuesta de Durai).
NickG
1
Solo quería intervenir y confirmar la recomendación de @AakashM para índices espaciales + ... para una aplicación ETL, la diferencia fue varios órdenes de magnitud mejor después de implementar los índices espaciales
Bill Anton
3
FYI: POINT (LONGITUDE LATITUDE) mientras que geografía :: Point (LATITUDE, LONGITUDE, 4326)
Mzn
42

La siguiente función proporciona la distancia entre dos geocoordenadas en millas

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 siguiente función proporciona la distancia entre dos geocoordenadas en kilómetros

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 siguiente función proporciona la distancia entre dos geocoordenadas en kilómetros utilizando el tipo de datos Geografía que se introdujo en 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);

Uso:

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

Referencia: Ref1 , Ref2

Durai Amuthan.H
fuente
2
Necesitaba calcular la distancia para los códigos postales de 35K contra los códigos postales de varios eventos ordenados por distancia a un código postal. Era una lista de coordenadas demasiado grande para hacer cálculos utilizando el tipo de datos geográficos. Cuando cambié para usar la solución basada en funciones de activación de línea única anterior, se ejecutó mucho más rápido. Entonces, usar tipos de geografía solo para calcular una distancia parece ser costoso. El comprador tenga cuidado.
Tombala
Muy útil para calcular la distancia en la cláusula WHERE de una consulta. Tuve que envolver un ABS () alrededor de la expresión "set @ d =", porque encontré algunos casos en los que la función devolvía una distancia negativa.
Joe Irby
1
La función para "distancia entre dos geocoordenadas en kilómetros" falla si comparamos 2 puntos iguales, da el error "Ocurrió una operación de punto flotante no válida"
RRM
2
Esto fue genial, pero no funciona para distancias cortas porque "decimal (8,4)" no proporciona suficiente precisión.
influente
1
@influent es correcto, esto no es útil para distancias cortas (5 millas en mi caso)
Roger
15

Parece que Microsoft invadió los cerebros de todos los demás encuestados y les hizo escribir soluciones lo más complicadas posible. Esta es la forma más sencilla sin funciones adicionales / declaraciones de declaración:

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

Basta con sustituir sus datos en lugar de LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2por ejemplo:

SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c
Stalinko
fuente
2
como referencia: STDistance () devuelve distancias en la unidad de medida lineal del sistema de referencia espacial en el que se definen los datos geográficos. Está utilizando SRID 4326, lo que significa que STDistance () devuelve distancias en metros.
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

Uso:

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

Salidas:

0,02849639

Puede cambiar el parámetro @R con flotantes comentados.

Fatih K.
fuente
Funciona perfectamente
Tejasvi Hegde
4

Como está utilizando SQL 2008 o posterior, le recomiendo que consulte el tipo de datos GEOGRAPHY . SQL tiene soporte integrado para consultas geoespaciales.

por ejemplo, tendría una columna en su tabla de tipo GEOGRAPHY que se llenaría con una representación geoespacial de las coordenadas (consulte la referencia de MSDN vinculada anteriormente para ver ejemplos). Este tipo de datos luego expone métodos que le permiten realizar una gran cantidad de consultas geoespaciales (por ejemplo, encontrar la distancia entre 2 puntos)

AdaTheDev
fuente
Solo para agregar, probé el tipo de campo de geografía, pero descubrí que usar la función de Durai (usando directamente los valores de longitud y latitud) era mucho más rápido. Vea mi ejemplo aquí: stackoverflow.com/a/37326089/391605
Mike Gledhill
1

Además de las respuestas anteriores, aquí hay una forma de calcular la distancia dentro de 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

Uso:

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

Las funciones escalares también funcionan, pero son muy ineficientes cuando se calculan grandes cantidades de datos.

Espero que esto pueda ayudar a alguien.

Thurfir
fuente