Menghitung jarak antara dua titik (Latitude, Longitude)


89

Saya mencoba menghitung jarak antara dua posisi di peta. Saya telah menyimpan data saya: Bujur, Lintang, X POS, Y POS.

Saya sebelumnya telah menggunakan potongan di bawah ini.

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

Namun saya tidak mempercayai data yang keluar dari ini, tampaknya memberikan hasil yang sedikit tidak akurat.

Beberapa contoh data jika Anda membutuhkannya

Latitude        Longitude     Distance 
53.429108       -2.500953     85.2981833133896

Adakah yang bisa membantu saya dengan kode saya, saya tidak keberatan jika Anda ingin memperbaiki apa yang sudah saya miliki jika Anda memiliki cara baru untuk mencapai ini, itu akan sangat bagus.

Sebutkan dalam unit pengukuran hasil Anda.


Anda tidak boleh membagi argumen menjadi sinus dengan tambahan / 2. Anda juga bisa memiliki akurasi lebih dalam radius Bumi, serta menggunakan beberapa Datum yang digunakan misalnya oleh sistem GPS (WGS-84) yang mendekati Bumi dengan elipsoid (dengan radius berbeda di ekuator dan kutub)
Aki Suihkonen

@ Waller, mengapa Anda tidak menggunakan tipe Geografi / Geometri (Spasial) untuk mencapai ini?
Habib

3
Saya memeriksa perhitungan Anda dengan Mathematica; menurutnya jarak dalam statute miles (5.280 kaki) adalah 42,997, yang menunjukkan bahwa penghitungan Anda tidak sedikit tidak akurat , melainkan sangat tidak akurat .
Kinerja Tinggi Mark

Jawaban:


129

Karena Anda menggunakan SQL Server 2008, Anda memiliki geographytipe data yang tersedia, yang dirancang untuk jenis data ini:

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

SELECT @source.STDistance(@target)

Memberi

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

(1 row(s) affected)

Memberitahu kami jaraknya sekitar 538 km dari (dekat) London ke (dekat) Edinburgh.

Tentu akan ada sejumlah pembelajaran yang harus dilakukan terlebih dahulu, tetapi begitu Anda mengetahuinya, itu jauh lebih mudah daripada menerapkan perhitungan Haversine Anda sendiri; ditambah Anda mendapatkan BANYAK fungsi.


Jika Anda ingin mempertahankan struktur data yang ada, Anda masih dapat menggunakan STDistance, dengan membuat geographyinstance yang sesuai menggunakan Pointmetode:

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 - garis bujur akan negatif untuk tempat-tempat di sebelah barat Meridian Utama , dan positif untuk tempat-tempat di sebelah
Timurnya

Anda menyelamatkan hari saya! .. Terima kasih banyak!
Dhrumil Bhankhar

1
Menggunakan fungsi bawaan tampaknya SANGAT lambat. Misalnya dalam 100.000 item loop, dibutuhkan 23 detik, bukan 1,4 detik untuk fungsi yang ditentukan pengguna (lihat jawaban Durai).
NickG

1
Hanya ingin bergabung dan mengonfirmasi rekomendasi @AakashM untuk indeks spasial + ... untuk aplikasi ETL, perbedaannya adalah beberapa kali lipat lebih baik setelah menerapkan indeks spasial
Bill Anton

3
FYI: POINT (LONGITUDE LATITUDE) sedangkan geografi :: Point (LATITUDE, LONGITUDE, 4326)
Mzn

42

Fungsi di bawah ini memberikan jarak antara dua koordinat geografis dalam mil

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 

Fungsi di bawah ini memberikan jarak antara dua koordinat geografis dalam kilometer

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

Fungsi di bawah ini memberikan jarak antara dua koordinat geografis dalam kilometer menggunakan tipe data Geografi yang diperkenalkan di 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);

Pemakaian:

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

Referensi: Ref1 , Ref2


2
Saya perlu melakukan penghitungan jarak untuk kode pos 35K terhadap kode pos berbagai peristiwa yang diurutkan berdasarkan jarak ke kode pos. Daftar koordinatnya terlalu besar untuk dilakukan penghitungan menggunakan tipe data geografi. Ketika saya beralih untuk menggunakan solusi berbasis fungsi trigonometri baris tunggal di atas, itu berjalan jauh lebih cepat. Jadi menggunakan tipe geografi hanya untuk menghitung jarak tampaknya mahal. Pembeli berhati-hatilah.
Tombala

Sangat berguna untuk menghitung jarak di klausa WHERE dari sebuah kueri. Saya memang harus membungkus ABS () di sekitar ekspresi "set @ d =", karena saya menemukan beberapa kasus di mana fungsi mengembalikan jarak negatif.
Joe Irby

1
Fungsi untuk "jarak antara dua koordinat geografis dalam kilometer" gagal jika kita membandingkan 2 titik yang sama, ini memberi Anda kesalahan "Terjadi operasi titik mengambang yang tidak valid"
RRM

2
Ini bagus tetapi tidak berfungsi untuk jarak pendek karena "desimal (8,4)" tidak memberikan cukup presisi.
berpengaruh

1
@influent benar, ini tidak berguna untuk jarak pendek (5 mil dalam kasus saya)
Roger

15

Sepertinya Microsoft menyerang otak semua responden lain dan membuat mereka menulis solusi serumit mungkin. Berikut adalah cara termudah tanpa pernyataan fungsi / deklarasi tambahan:

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

Cukup mengganti data Anda bukan LATITUDE_1, LONGITUDE_1, LATITUDE_2, LONGITUDE_2misalnya:

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

2
untuk referensi: STDistance () mengembalikan jarak dalam satuan ukuran linier dari sistem referensi spasial tempat data geografi Anda ditentukan. Anda menggunakan SRID 4326, yang berarti STDistance () mengembalikan jarak dalam meter.
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

Pemakaian:

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

Keluaran:

0,02849639

Anda dapat mengubah parameter @R dengan float yang dikomentari.


Bekerja dengan sempurna
Tejasvi Hegde

4

Karena Anda menggunakan SQL 2008 atau yang lebih baru, saya sarankan untuk memeriksa GEOGRAFI tipe data . SQL telah membangun dukungan untuk kueri geospasial.

misalnya Anda akan memiliki kolom di tabel Anda dengan tipe GEOGRAFI yang akan diisi dengan representasi geospasial dari koordinat (lihat referensi MSDN yang ditautkan di atas untuk contoh). Jenis data ini kemudian memaparkan metode yang memungkinkan Anda melakukan seluruh host kueri geospasial (misalnya menemukan jarak antara 2 titik)


Sekadar menambahkan, saya mencoba tipe bidang geografi, tetapi ternyata menggunakan fungsi Durai (langsung menggunakan nilai garis bujur dan garis lintang) jauh lebih cepat. Lihat contoh saya di sini: stackoverflow.com/a/37326089/391605
Mike Gledhill

1

Selain jawaban sebelumnya, berikut ini cara menghitung jarak di dalam 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

Pemakaian:

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

Fungsi skalar juga berfungsi tetapi sangat tidak efisien saat menghitung data dalam jumlah besar.

Saya harap ini bisa membantu seseorang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.