İki nokta arasındaki mesafenin hesaplanması (Enlem, Boylam)


89

Bir haritada iki konum arasındaki mesafeyi hesaplamaya çalışıyorum. Verilerimde sakladım: Boylam, Enlem, X POS, Y POS.

Daha önce aşağıdaki pasajı kullanıyordum.

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

Bununla birlikte, bundan çıkan verilere güvenmiyorum, biraz yanlış sonuçlar veriyor gibi görünüyor.

İhtiyaç duymanız durumunda bazı örnek veriler

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

Herhangi biri bana kodumla yardımcı olabilir mi, zaten sahip olduğum şeyi düzeltmek isteyip istemediğinizi umursamıyorum, bunu başarmak için yeni bir yolunuz varsa harika olurdu.

Lütfen sonuçlarınızın hangi ölçü biriminde olduğunu belirtin.


Sinüs argümanını ek / 2'ye bölmemelisiniz. Ayrıca Dünya yarıçapında daha fazla doğruluğa sahip olmanın yanı sıra örneğin GPS sistemi (WGS-84) tarafından Dünya'ya bir elipsoid (ekvatorda ve kutuplarda farklı yarıçaplarla) yaklaşan bazı Verileri kullanarak daha fazla doğruluk elde edebilirsiniz
Aki Suihkonen

@Waller, bunu başarmak için neden Coğrafya / Geometri (Uzamsal) türünü kullanmıyorsun?
Habib

3
Hesaplamanızı Mathematica ile kontrol ettim; tüzük mili (5280 fit) cinsinden mesafenin 42.997 olduğunu düşünüyor, bu da hesaplamanızın biraz yanlış olmadığını , bunun yerine çılgınca yanlış olduğunu gösteriyor .
Yüksek Performans Mark

Yanıtlar:


130

SQL Server 2008 kullandığınız geographyiçin, tam olarak bu tür veriler için tasarlanmış mevcut veri türüne sahipsiniz :

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

SELECT @source.STDistance(@target)

Verir

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

(1 row(s) affected)

Bize Londra'dan (yakın) Edinburgh'a (yakın) yaklaşık 538 km olduğunu söylüyor.

Doğal olarak, ilk önce yapılması gereken bir miktar öğrenme olacaktır, ancak bunu bir kez öğrendiğinizde, kendi Haversine hesaplamanızı uygulamaktan çok daha kolaydır; artı çok sayıda işlevsellik elde edersiniz.


Mevcut veri yapınızı korumak istiyorsanız , yöntemi kullanarak STDistanceuygun geographyörnekleri oluşturarak kullanmaya devam edebilirsiniz 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

6
hayır @nezam - boylam Batı yerleri için negatif olacaktır Meridyene bunun Doğu yerler için, ve pozitif
AakashM

Günümü kurtardın! .. Çok teşekkürler!
Dhrumil Bhankhar

1
Yerleşik işlevi kullanmak ÇOK yavaş görünüyor. Örneğin, 100.000 öğe döngüsünde, kullanıcı tanımlı işlevim için 1.4 saniye yerine 23 saniye sürüyor (Durai'nin cevabına bakın).
NickG

1
Bir ETL uygulaması için @AakashM'in uzamsal indeksler için önerisini onaylamak ve onaylamak istedim, fark, uzamsal indeksleri uyguladıktan sonra birkaç büyüklük sıralaması daha iyiydi
Bill Anton

3
Bilginize: POINT (LONGITUDE LATITUDE) oysa coğrafya :: Point (LATITUDE, LONGITUDE, 4326)
Mzn

42

Aşağıdaki fonksiyon mil cinsinden iki coğrafi koordinat arasındaki mesafeyi verir

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 

Aşağıdaki fonksiyon, kilometre cinsinden iki coğrafi koordinat arasındaki mesafeyi verir

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

Aşağıdaki fonksiyon, sql server 2008'de tanıtılan Coğrafya veri türünü kullanarak kilometre cinsinden iki coğrafi koordinat arasındaki mesafeyi verir.

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

Kullanım:

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

Referans: Ref1 , Ref2


2
Bir posta koduna olan mesafeye göre sıralanan çeşitli etkinliklerin posta kodlarına karşı 35K posta kodları için mesafe hesaplaması yapmam gerekiyordu. Coğrafya veri türünü kullanarak hesaplamalar yapmak için çok büyük bir koordinat listesi vardı. Yukarıdaki tek satırlı trigonometri fonksiyonlarına dayalı çözümü kullanmaya başladığımda çok daha hızlı çalıştı. Bu nedenle, yalnızca bir mesafeyi hesaplamak için coğrafya türlerini kullanmak pahalı görünüyor. Alıcı dikkatli olun.
Tombala

Bir sorgunun WHERE cümlesindeki mesafeyi hesaplamak için çok kullanışlıdır. "Set @ d =" ifadesinin etrafına bir ABS () sarmak zorunda kaldım, çünkü fonksiyonun negatif bir mesafe döndürdüğü bazı durumlar buldum.
Joe Irby

1
"Kilometre cinsinden iki coğrafi koordinat arasındaki mesafe" işlevi, 2 eşit noktayı karşılaştırırsak başarısız olur, size "Geçersiz bir kayan nokta işlemi oluştu" hatası verir
RRM

2
Bu harikaydı ama kısa mesafelerde işe yaramıyor çünkü "ondalık (8,4)" yeterli hassasiyet sağlamıyor.
akın

1
@influent doğru, bu kısa mesafeler için kullanışlı değil (benim durumumda 5 mil)
Roger

16

Görünüşe göre Microsoft diğer tüm katılımcıların beyinlerini işgal etmiş ve mümkün olduğunca karmaşık çözümler yazmalarını sağlamıştır. Ek işlevler / bildirimler olmadan en basit yol şu şekildedir:

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

Basitçe yerine verilerinizi yerine LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2örneğin:

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

2
referans için: STDistance (), coğrafya verilerinizin tanımlandığı uzamsal referans sisteminin doğrusal ölçü birimindeki mesafeleri döndürür. SRID 4326 kullanıyorsunuz, bu da STDistance () 'ın mesafeleri metre cinsinden döndürdüğü anlamına gelir.
Bryan Kütük

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

Kullanım:

dbo.DistanceKM'yi seçin (37.848832506474, 37.848732506474, 27.83935546875, 27.83905546875)

Çıktılar:

0,02849639

@R parametresini yorumlu kayan sayılarla değiştirebilirsiniz.


Mükemmel çalışıyor
Tejasvi Hegde

4

SQL 2008 veya sonraki bir sürümünü kullandığınız için, GEOGRAPHY veri türünü kontrol etmenizi öneririm . SQL, jeo-uzamsal sorgular için yerleşik desteğe sahiptir.

Örneğin, tablonuzda koordinatların jeo-uzamsal gösterimi ile doldurulacak olan GEOGRAPHY türünde bir sütununuz olur (örnekler için yukarıda bağlantılı MSDN referansına bakın). Bu veri türü daha sonra tüm jeo-uzamsal sorguları gerçekleştirmenize izin veren yöntemleri ortaya çıkarır (örneğin, 2 nokta arasındaki mesafeyi bulma)


Eklemek gerekirse, coğrafya alan türünü denedim, ancak Durai'nin işlevini (doğrudan boylam ve enlem değerlerini kullanarak) kullanarak çok daha hızlı buldum . Buradaki
örneğime

1

Önceki yanıtlara ek olarak, burada bir SEÇİM içindeki mesafeyi hesaplamanın bir yolu vardır:

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

Kullanım:

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

Skaler işlevler de çalışır, ancak büyük miktarda veriyi hesaplarken çok verimsizdirler.

Umarım bu birine yardımcı olabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.