Calcul de la distance entre deux points (Latitude, Longitude)

88

J'essaye de calculer la distance entre deux positions sur une carte. J'ai stocké dans mes données: Longitude, Latitude, X POS, Y POS.

J'ai déjà utilisé l'extrait ci-dessous.

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

Cependant, je ne fais pas confiance aux données qui en découlent, cela semble donner des résultats légèrement inexacts.

Quelques exemples de données au cas où vous en auriez besoin

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

Quelqu'un pourrait-il m'aider avec mon code, cela ne me dérange pas si vous voulez réparer ce que j'ai déjà si vous avez une nouvelle façon d'y parvenir, ce serait génial.

Veuillez indiquer dans quelle unité de mesure se trouvent vos résultats.

Waller
la source
Vous ne devez pas diviser l'argument en sinus par le / 2 supplémentaire. Vous pourriez également avoir plus de précision dans le rayon de la Terre, ainsi que l'utilisation de certaines données utilisées par exemple par le système GPS (WGS-84) qui se rapproche de la Terre par un ellipsoïde (avec différents rayons à l'équateur et aux pôles)
Aki Suihkonen
@Waller, pourquoi n'utilisez-vous pas le type Géographie / Géométrie (Spatiale) pour y parvenir?
Habib
3
J'ai vérifié votre calcul avec Mathematica; il pense que la distance en milles terrestres (5280 pieds) est de 42,997, ce qui suggère que votre calcul n'est pas légèrement inexact , mais plutôt extrêmement inexact .
High Performance Mark

Réponses:

127

Puisque vous utilisez SQL Server 2008, vous disposez du geographytype de données disponible, qui est conçu pour exactement ce type de données:

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

SELECT @source.STDistance(@target)

Donne

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

(1 row(s) affected)

En nous disant qu'il est à environ 538 km de (près de) Londres à (près) d'Édimbourg.

Naturellement, il y aura beaucoup d'apprentissage à faire en premier, mais une fois que vous le savez, c'est beaucoup plus facile que d'implémenter votre propre calcul Haversine; de plus, vous obtenez BEAUCOUP de fonctionnalités.


Si vous souhaitez conserver votre structure de données existante, vous pouvez toujours utiliser STDistance, en construisant des geographyinstances appropriées à l'aide de la Pointméthode:

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
la source
6
@nezam non - la longitude sera négative pour les endroits à l'ouest du méridien principal et positive pour les endroits à l'est de celui
AakashM
Vous avez sauvé ma journée! .. Merci beaucoup!
Dhrumil Bhankhar
1
L'utilisation de la fonction intégrée semble être TRÈS lente. Par exemple, dans une boucle de 100 000 éléments, cela prend 23 secondes au lieu de 1,4 seconde pour ma fonction définie par l'utilisateur (voir la réponse de Durai).
NickG
1
Je voulais juste intervenir et confirmer la recommandation de @AakashM pour les index spatiaux + ... pour une application ETL, la différence était de plusieurs ordres de grandeur meilleure après la mise en œuvre des index spatiaux
Bill Anton
3
FYI: POINT (LONGITUDE LATITUDE) alors que geography :: Point (LATITUDE, LONGITUDE, 4326)
Mzn
41

La fonction ci-dessous donne la distance entre deux géocoordonnées en miles

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 fonction ci-dessous donne la distance entre deux géocoordonnées en kilomètres

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 fonction ci-dessous donne la distance entre deux géocoordonnées en kilomètres à l' aide du type de données Géographie qui a été introduit dans 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);

Usage:

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

Référence: Ref1 , Ref2

Durai Amuthan.H
la source
2
J'avais besoin de calculer la distance pour les codes postaux de 35K par rapport aux codes postaux de divers événements classés par distance à un code postal. La liste des coordonnées était trop longue pour effectuer des calculs à l'aide du type de données géographie. Lorsque je suis passé à la solution basée sur les fonctions trigonométriques sur une seule ligne ci-dessus, elle a fonctionné beaucoup plus rapidement. Donc, utiliser des types de géographie juste pour calculer une distance semble être coûteux. Attention l'acheteur.
Tombala
Très utile pour calculer la distance dans la clause WHERE d'une requête. J'ai dû enrouler un ABS () autour de l'expression "set @ d =", car j'ai trouvé des cas où la fonction retournait une distance négative.
Joe Irby du
1
La fonction "distance entre deux géocoordonnées en kilomètres" échoue si nous comparons 2 points égaux, elle vous donne l'erreur "Une opération en virgule flottante non valide s'est produite"
RRM
2
C'était génial mais ne fonctionne pas pour les courtes distances car "decimal (8,4)" ne fournit pas assez de précision.
influent
1
@influent est correct, ce n'est pas utile pour les courtes distances (5 miles dans mon cas)
Roger
15

On dirait que Microsoft a envahi le cerveau de tous les autres répondants et leur a fait écrire des solutions aussi compliquées que possible. Voici le moyen le plus simple sans fonctions / déclarations supplémentaires:

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

Il suffit de remplacer vos données au lieu de LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2par exemple:

SELECT geography::Point(53.429108, -2.500953, 4326).STDistance(geography::Point(c.Latitude, c.Longitude, 4326))
from coordinates c
Stalinko
la source
1
pour référence: STDistance () renvoie les distances dans l'unité de mesure linéaire du système de référence spatiale dans lequel vos données géographiques sont définies. Vous utilisez le SRID 4326, ce qui signifie que STDistance () renvoie les distances en mètres.
Bryan Stump
4

Comme vous utilisez SQL 2008 ou version ultérieure, je vous recommande de vérifier le type de données GEOGRAPHY . SQL a intégré la prise en charge des requêtes géospatiales.

par exemple, vous auriez une colonne dans votre table de type GEOGRAPHY qui serait remplie avec une représentation géospatiale des coordonnées (consultez la référence MSDN liée ci-dessus pour des exemples). Ce type de données expose ensuite des méthodes vous permettant d'effectuer toute une série de requêtes géospatiales (par exemple, trouver la distance entre 2 points)

AdaTheDev
la source
Juste pour ajouter, j'ai essayé le type de champ de géographie, mais j'ai trouvé que l'utilisation de la fonction de Durai (utilisant directement les valeurs de longitude et de latitude) était beaucoup plus rapide. Voir mon exemple ici: 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

Usage:

sélectionnez dbo.DistanceKM (37.848832506474, 37.848732506474, 27.83935546875, 27.83905546875)

Les sorties:

0,02849639

Vous pouvez modifier le paramètre @R avec des flottants commentés.

Fatih K.
la source
Fonctionne parfaitement
Tejasvi Hegde
1

En plus des réponses précédentes, voici un moyen de calculer la distance à l'intérieur d'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

Usage:

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

Les fonctions scalaires fonctionnent également, mais elles sont très inefficaces lors du calcul d'une grande quantité de données.

J'espère que cela pourrait aider quelqu'un.

Thurfir
la source