Temukan lintang / bujur terdekat dengan kueri SQL

173

Saya memiliki garis lintang dan bujur dan saya ingin menarik catatan dari basis data, yang memiliki garis lintang dan bujur terdekat dari jarak, jika jarak itu lebih panjang dari yang ditentukan, maka jangan mengambilnya.

Struktur meja:

id
latitude
longitude
place name
city
country
state
zip
sealevel
Basit
sumber
1
Ini adalah semacam duplikat dari pertanyaan pencarian kedekatan .
Darius Bacon
1
Ada satu set slide oleh Alexander Rubin di pencarian Geo (proximity) dengan MySQL (tautan PDF)
Martijn Pieters

Jawaban:

209
SELECT latitude, longitude, SQRT(
    POW(69.1 * (latitude - [startlat]), 2) +
    POW(69.1 * ([startlng] - longitude) * COS(latitude / 57.3), 2)) AS distance
FROM TableName HAVING distance < 25 ORDER BY distance;

di mana [starlat] dan [startlng] adalah posisi untuk mulai mengukur jarak.

Kaletha
sumber
49
Hanya catatan kinerja, yang terbaik adalah tidak sqrt variabel jarak tetapi alih-alih
kuadratkan
9
Apa yang akan menjadi kueri yang sama untuk jarak menjadi dalam meter? (yang saat ini dalam mil, kan?)
httpete
8
Apa ukuran 25 itu?
Steffan Donal
16
Hanya untuk memperjelas di sini 69.1 adalah faktor konversi untuk mil ke derajat lintang. 57,3 kira-kira 180 / pi, jadi itu konversi dari derajat ke radian, untuk fungsi cosinus. 25 adalah radius pencarian dalam mil. Ini adalah rumus yang digunakan saat menggunakan derajat desimal dan jarak statute.
John Vance
8
Selain itu, itu tidak memperhitungkan kelengkungan bumi. Ini tidak akan menjadi masalah untuk jari-jari pencarian pendek. Kalau tidak, jawaban Evan dan Igor lebih lengkap.
John Vance
63

Solusi Google:

Menciptakan Tabel

Saat Anda membuat tabel MySQL, Anda ingin memberi perhatian khusus pada atribut lat dan lng. Dengan kemampuan zoom Google Maps saat ini, Anda seharusnya hanya membutuhkan 6 digit presisi setelah desimal. Untuk menjaga ruang penyimpanan yang diperlukan untuk meja Anda minimum, Anda dapat menentukan bahwa atribut lat dan lng mengapung ukuran (10,6). Itu akan membiarkan bidang menyimpan 6 digit setelah desimal, ditambah hingga 4 digit sebelum desimal, misalnya -123.456789 derajat. Tabel Anda juga harus memiliki atribut id untuk dijadikan sebagai kunci utama.

CREATE TABLE `markers` (
  `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
  `name` VARCHAR( 60 ) NOT NULL ,
  `address` VARCHAR( 80 ) NOT NULL ,
  `lat` FLOAT( 10, 6 ) NOT NULL ,
  `lng` FLOAT( 10, 6 ) NOT NULL
) ENGINE = MYISAM ;

Mengisi Tabel

Setelah membuat tabel, saatnya mengisinya dengan data. Data sampel yang disediakan di bawah ini adalah untuk sekitar 180 pizza yang tersebar di seluruh Amerika Serikat. Di phpMyAdmin, Anda dapat menggunakan tab IMPORT untuk mengimpor berbagai format file, termasuk CSV (nilai yang dipisahkan koma). Microsoft Excel dan Google Spreadsheets sama-sama mengekspor ke format CSV, sehingga Anda dapat dengan mudah mentransfer data dari spreadsheet ke tabel MySQL melalui mengekspor / mengimpor file CSV.

INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Frankie Johnnie & Luigo Too','939 W El Camino Real, Mountain View, CA','37.386339','-122.085823');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Amici\'s East Coast Pizzeria','790 Castro St, Mountain View, CA','37.38714','-122.083235');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Kapp\'s Pizza Bar & Grill','191 Castro St, Mountain View, CA','37.393885','-122.078916');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Round Table Pizza: Mountain View','570 N Shoreline Blvd, Mountain View, CA','37.402653','-122.079354');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Tony & Alba\'s Pizza & Pasta','619 Escuela Ave, Mountain View, CA','37.394011','-122.095528');
INSERT INTO `markers` (`name`, `address`, `lat`, `lng`) VALUES ('Oregano\'s Wood-Fired Pizza','4546 El Camino Real, Los Altos, CA','37.401724','-122.114646');

Menemukan Lokasi dengan MySQL

Untuk menemukan lokasi dalam tabel marker Anda yang berada dalam jarak radius tertentu dari garis lintang / bujur, Anda dapat menggunakan pernyataan SELECT berdasarkan rumus Haversine. Formula Haversine digunakan secara umum untuk menghitung jarak lingkaran besar antara dua pasang koordinat pada sebuah bola. Penjelasan matematis yang mendalam diberikan oleh Wikipedia dan diskusi yang baik tentang rumus yang berkaitan dengan pemrograman ada di situs Movable Type.

Inilah pernyataan SQL yang akan menemukan 20 lokasi terdekat yang berada dalam radius 25 mil ke 37, -122 koordinat. Ini menghitung jarak berdasarkan garis lintang / garis bujur dari baris itu dan garis lintang / garis bujur target, dan kemudian meminta hanya baris di mana nilai jaraknya kurang dari 25, memesan seluruh kueri berdasarkan jarak, dan membatasinya hingga 20 hasil. Untuk mencari menurut kilometer bukannya mil, ganti 3959 dengan 6371.

SELECT 
id, 
(
   3959 *
   acos(cos(radians(37)) * 
   cos(radians(lat)) * 
   cos(radians(lng) - 
   radians(-122)) + 
   sin(radians(37)) * 
   sin(radians(lat )))
) AS distance 
FROM markers 
HAVING distance < 28 
ORDER BY distance LIMIT 0, 20;

Yang ini adalah untuk menemukan garis lintang dan bujur dalam jarak kurang dari 28 mil.

Satu lagi adalah menemukan mereka dalam jarak antara 28 dan 29 mil:

SELECT 
id, 
(
   3959 *
   acos(cos(radians(37)) * 
   cos(radians(lat)) * 
   cos(radians(lng) - 
   radians(-122)) + 
   sin(radians(37)) * 
   sin(radians(lat )))
) AS distance 
FROM markers 
HAVING distance < 29 and distance > 28 
ORDER BY distance LIMIT 0, 20;

https://developers.google.com/maps/articles/phpsqlsearch_v3#creating-the-map

Sviatoslav Oleksiv
sumber
1
Haruskah HAVING distance < 25kita menanyakan lokasi dalam radius 25 mil?
Vitalii Elenhaupt
seharusnya> 25, lalu akan mencari semua catatan
vidur punj
Anda yakin @vidurpunj tentang> 25?
Amranur Rahman
Saya mencoba kueri sql menggunakan: distance <25 tetapi tidak menemukan hasil .. karena jarak penanda sampel semuanya di atas 25 ...
IbrahimShendy
37, -122 Koordinat, apakah garis lintang dan bujur untuk posisi dari mana kita perlu menemukan jarak?
Prasobh.Kollattu
28

Inilah solusi lengkap saya yang diterapkan dalam PHP.

Solusi ini menggunakan rumus Haversine seperti yang disajikan di http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL .

Perlu dicatat bahwa formula Haversine mengalami kelemahan di sekitar kutub. Jawaban ini menunjukkan bagaimana menerapkan formula Great Circle Distance untuk mengatasi ini, namun saya memilih untuk menggunakan Haversine karena itu cukup baik untuk tujuan saya.

Saya menyimpan lintang sebagai DECIMAL (10,8) dan bujur sebagai DECIMAL (11,8). Semoga ini bisa membantu!

showClosest.php

<?PHP
/**
 * Use the Haversine Formula to display the 100 closest matches to $origLat, $origLon
 * Only search the MySQL table $tableName for matches within a 10 mile ($dist) radius.
 */
include("./assets/db/db.php"); // Include database connection function
$db = new database(); // Initiate a new MySQL connection
$tableName = "db.table";
$origLat = 42.1365;
$origLon = -71.7559;
$dist = 10; // This is the maximum distance (in miles) away from $origLat, $origLon in which to search
$query = "SELECT name, latitude, longitude, 3956 * 2 * 
          ASIN(SQRT( POWER(SIN(($origLat - latitude)*pi()/180/2),2)
          +COS($origLat*pi()/180 )*COS(latitude*pi()/180)
          *POWER(SIN(($origLon-longitude)*pi()/180/2),2))) 
          as distance FROM $tableName WHERE 
          longitude between ($origLon-$dist/cos(radians($origLat))*69) 
          and ($origLon+$dist/cos(radians($origLat))*69) 
          and latitude between ($origLat-($dist/69)) 
          and ($origLat+($dist/69)) 
          having distance < $dist ORDER BY distance limit 100"; 
$result = mysql_query($query) or die(mysql_error());
while($row = mysql_fetch_assoc($result)) {
    echo $row['name']." > ".$row['distance']."<BR>";
}
mysql_close($db);
?>

./assets/db/db.php

<?PHP
/**
 * Class to initiate a new MySQL connection based on $dbInfo settings found in dbSettings.php
 *
 * @example $db = new database(); // Initiate a new database connection
 * @example mysql_close($db); // close the connection
 */
class database{
    protected $databaseLink;
    function __construct(){
        include "dbSettings.php";
        $this->database = $dbInfo['host'];
        $this->mysql_user = $dbInfo['user'];
        $this->mysql_pass = $dbInfo['pass'];
        $this->openConnection();
        return $this->get_link();
    }
    function openConnection(){
    $this->databaseLink = mysql_connect($this->database, $this->mysql_user, $this->mysql_pass);
    }

    function get_link(){
    return $this->databaseLink;
    }
}
?>

./assets/db/dbSettings.php

<?php
$dbInfo = array(
    'host'      => "localhost",
    'user'      => "root",
    'pass'      => "password"
);
?>

Dimungkinkan untuk meningkatkan kinerja dengan menggunakan prosedur tersimpan MySQL seperti yang disarankan oleh artikel "Geo-Distance-Search-with-MySQL" yang diposting di atas.

Saya memiliki database ~ 17.000 tempat dan waktu pelaksanaan kueri adalah 0,054 detik.

sirkuit
sumber
Bagaimana saya bisa mendapatkan jarak dalam Km atau Meter? Salam!
chemitaxis
2
mil * 1.609344 = km
Sinan Dizdarević
2
PERINGATAN. Solusi Excelent, tetapi memiliki bug. Semua itu absharus dihapus. Tidak perlu mengambil nilai abs saat mengkonversi dari derajat ke radian, dan, bahkan jika Anda melakukannya, Anda melakukannya hanya pada salah satu lintang. Harap edit sehingga memperbaiki bug.
Chango
1
Dan bagi siapa saja yang menginginkan ini dalam meter: Konversikan 3956 mil ke kilometer: Jari-jari bumi; Konversi 69 mil ke kilometer: panjang puncak 1 derajat lintang dalam km; Dan masukkan jarak dalam kilometer.
Chango
1
Dan ganti 69dengan 111,044736(lupa bahwa dalam komentar di atas)
rkeet
24

Kalau-kalau Anda malas seperti saya, inilah solusi yang digabungkan dari ini dan jawaban lain pada SO.

set @orig_lat=37.46; 
set @orig_long=-122.25; 
set @bounding_distance=1;

SELECT
*
,((ACOS(SIN(@orig_lat * PI() / 180) * SIN(`lat` * PI() / 180) + COS(@orig_lat * PI() / 180) * COS(`lat` * PI() / 180) * COS((@orig_long - `long`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS `distance` 
FROM `cities` 
WHERE
(
  `lat` BETWEEN (@orig_lat - @bounding_distance) AND (@orig_lat + @bounding_distance)
  AND `long` BETWEEN (@orig_long - @bounding_distance) AND (@orig_long + @bounding_distance)
)
ORDER BY `distance` ASC
limit 25;
Evan
sumber
1
bounding_distancemewakili apa sebenarnya ? apakah nilai ini membatasi hasil hingga satu mil? jadi dalam hal ini akan mengembalikan hasil dalam 1 mil?
james
1
@ bounding_distance dalam derajat di sini, dan digunakan untuk mempercepat perhitungan dengan membatasi wilayah pencarian yang efektif. Misalnya, jika Anda tahu pengguna Anda berada di kota tertentu, dan Anda tahu Anda memiliki beberapa titik di dalam kota itu, Anda dapat dengan aman mengatur jarak batas Anda ke beberapa derajat.
Evan
1
Rumus jarak geografis manakah yang digunakan ini?
bbodenmiller
12

Mudah;)

SELECT * FROM `WAYPOINTS` W ORDER BY
ABS(ABS(W.`LATITUDE`-53.63) +
ABS(W.`LONGITUDE`-9.9)) ASC LIMIT 30;

Cukup ganti koordinat dengan yang Anda butuhkan. Nilai harus disimpan sebagai ganda. Ini adalah contoh MySQL 5.x yang berfungsi.

Bersulang

Nicholas
sumber
2
Tidak tahu mengapa upvote, OP ingin membatasi dan memesan dengan jarak tertentu, bukan membatasi dengan 30 dan memesan olehdx+dy
okm
3
Ini berhasil untuk saya. Bukan itu yang diinginkan OP, tapi itu yang saya inginkan, jadi terima kasih telah menjawab! :)
Webmaster G
ABS terluar () sudah cukup.
dzona
6

Coba ini, ini menunjukkan titik terdekat ke koordinat yang disediakan (dalam jarak 50 km). Ini bekerja dengan sempurna:

SELECT m.name,
    m.lat, m.lon,
    p.distance_unit
             * DEGREES(ACOS(COS(RADIANS(p.latpoint))
             * COS(RADIANS(m.lat))
             * COS(RADIANS(p.longpoint) - RADIANS(m.lon))
             + SIN(RADIANS(p.latpoint))
             * SIN(RADIANS(m.lat)))) AS distance_in_km
FROM <table_name> AS m
JOIN (
      SELECT <userLat> AS latpoint, <userLon> AS longpoint,
             50.0 AS radius, 111.045 AS distance_unit
     ) AS p ON 1=1
WHERE m.lat
BETWEEN p.latpoint  - (p.radius / p.distance_unit)
    AND p.latpoint  + (p.radius / p.distance_unit)
    AND m.lon BETWEEN p.longpoint - (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
    AND p.longpoint + (p.radius / (p.distance_unit * COS(RADIANS(p.latpoint))))
ORDER BY distance_in_km

Ubah saja <table_name>. <userLat>dan<userLon>

Anda dapat membaca lebih lanjut tentang solusi ini di sini: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/

smartmouse
sumber
6

Jawaban asli untuk pertanyaan itu bagus, tetapi versi yang lebih baru dari mysql (MySQL 5.7.6 on) mendukung kueri geo, jadi sekarang Anda dapat menggunakan fungsionalitas bawaan daripada melakukan kueri yang rumit.

Anda sekarang dapat melakukan sesuatu seperti:

select *, ST_Distance_Sphere( point ('input_longitude', 'input_latitude'), 
                              point(longitude, latitude)) * .000621371192 
          as `distance_in_miles` 
  from `TableName`
having `distance_in_miles` <= 'input_max_distance'
 order by `distance_in_miles` asc

Hasilnya dikembalikan dalam meters. Jadi, jika Anda ingin KMhanya menggunakan .001bukan .000621371192(yang untuk mil).

Dokumen MySql ada di sini

Sherman
sumber
Jika memungkinkan silakan tambahkan versi mysql sebagai jawaban.
Parixit
ST_Distance_Spheretidak ada di instal host saya ( mysql Ver 15.1 Distrib 10.2.23-MariaDB). Saya membaca di suatu tempat untuk menggantikan ST_Distancetetapi jaraknya jauh.
ashleedawg
@ashleedawg - Dari versi, saya pikir Anda menggunakan MariaDB, yang merupakan garpu dari mysql. Dari percakapan ini sepertinya MariaDB belum diimplementasikanST_Distance_Sphere
Sherman
5

Anda sedang mencari hal-hal seperti formula haversine . Lihat di sini juga.

Ada yang lain tapi ini yang paling sering dikutip.

Jika Anda mencari sesuatu yang lebih kuat, Anda mungkin ingin melihat kemampuan GIS database Anda. Mereka mampu melakukan beberapa hal keren seperti memberi tahu Anda apakah suatu titik (Kota) muncul dalam poligon tertentu (Wilayah, Negara, Benua).

Koobz
sumber
Ini memang yang paling banyak dikutip, tetapi banyak artikel merujuk pada perangkat keras komputasi lama ketika datang ke pernyataan tentang komputasi yang tidak akurat menggunakan metode lain. Lihat juga movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan
4

Periksa kode ini berdasarkan artikel Geo-Distance-Search-with-MySQL :

Contoh: cari 10 hotel terdekat ke lokasi saya saat ini dalam radius 10 mil:

#Please notice that (lat,lng) values mustn't be negatives to perform all calculations

set @my_lat=34.6087674878572; 
set @my_lng=58.3783670308302;
set @dist=10; #10 miles radius

SELECT dest.id, dest.lat, dest.lng,  3956 * 2 * ASIN(SQRT(POWER(SIN((@my_lat -abs(dest.lat)) * pi()/180 / 2),2) + COS(@my_lat * pi()/180 ) * COS(abs(dest.lat) *  pi()/180) * POWER(SIN((@my_lng - abs(dest.lng)) *  pi()/180 / 2), 2))
) as distance
FROM hotel as dest
having distance < @dist
ORDER BY distance limit 10;

#Also notice that distance are expressed in terms of radius.
JuanManuelFigueroa
sumber
3
simpledb.execSQL("CREATE TABLE IF NOT EXISTS " + tablename + "(id INTEGER PRIMARY KEY   AUTOINCREMENT,lat double,lng double,address varchar)");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2891001','70.780154','craftbox');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2901396','70.7782428','kotecha');");//22.2904718 //70.7783906
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2863155','70.772108','kkv Hall');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.275993','70.778076','nana mava');");
            simpledb.execSQL("insert into '" + tablename + "'(lat,lng,address)values('22.2667148','70.7609386','Govani boys hostal');");


    double curentlat=22.2667258;  //22.2677258
    double curentlong=70.76096826;//70.76096826

    double curentlat1=curentlat+0.0010000;
    double curentlat2=curentlat-0.0010000;

    double curentlong1=curentlong+0.0010000;
    double curentlong2=curentlong-0.0010000;

    try{

        Cursor c=simpledb.rawQuery("select * from '"+tablename+"' where (lat BETWEEN '"+curentlat2+"' and '"+curentlat1+"') or (lng BETWEEN         '"+curentlong2+"' and '"+curentlong1+"')",null);

        Log.d("SQL ", c.toString());
        if(c.getCount()>0)
        {
            while (c.moveToNext())
            {
                double d=c.getDouble(1);
                double d1=c.getDouble(2);

            }
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
Hardip
sumber
2

Sepertinya Anda ingin melakukan pencarian tetangga terdekat dengan beberapa terikat pada jarak. SQL tidak mendukung hal seperti ini sejauh yang saya ketahui dan Anda perlu menggunakan struktur data alternatif seperti R-tree atau kd-tree .

Chris de Vries
sumber
2

Temukan Pengguna terdekat ke saya:

Jarak dalam meter

Berbasis di rumus Vincenty

saya punya tabel pengguna:

+----+-----------------------+---------+--------------+---------------+
| id | email                 | name    | location_lat | location_long |
+----+-----------------------+---------+--------------+---------------+
| 13 | xxxxxx@xxxxxxxxxx.com | Isaac   | 17.2675625   | -97.6802361   |
| 14 | xxxx@xxxxxxx.com.mx   | Monse   | 19.392702    | -99.172596    |
+----+-----------------------+---------+--------------+---------------+

sql:

-- my location:  lat   19.391124   -99.165660
SELECT 
(ATAN(
    SQRT(
        POW(COS(RADIANS(users.location_lat)) * SIN(RADIANS(users.location_long) - RADIANS(-99.165660)), 2) +
        POW(COS(RADIANS(19.391124)) * SIN(RADIANS(users.location_lat)) - 
       SIN(RADIANS(19.391124)) * cos(RADIANS(users.location_lat)) * cos(RADIANS(users.location_long) - RADIANS(-99.165660)), 2)
    )
    ,
    SIN(RADIANS(19.391124)) * 
    SIN(RADIANS(users.location_lat)) + 
    COS(RADIANS(19.391124)) * 
    COS(RADIANS(users.location_lat)) * 
    COS(RADIANS(users.location_long) - RADIANS(-99.165660))
 ) * 6371000) as distance,
users.id
FROM users
ORDER BY distance ASC

jari-jari bumi: 6371000 (dalam meter)

Isaac Limón
sumber
1

MS SQL Edition di sini:

        DECLARE @SLAT AS FLOAT
        DECLARE @SLON AS FLOAT

        SET @SLAT = 38.150785
        SET @SLON = 27.360249

        SELECT TOP 10 [LATITUDE], [LONGITUDE], SQRT(
            POWER(69.1 * ([LATITUDE] - @SLAT), 2) +
            POWER(69.1 * (@SLON - [LONGITUDE]) * COS([LATITUDE] / 57.3), 2)) AS distance
        FROM [TABLE] ORDER BY 3
B.Tekkan
sumber
0

Kedengarannya seperti Anda harus menggunakan PostGIS, SpatialLite, SQLServer2008, atau Oracle Spatial. Mereka semua dapat menjawab pertanyaan ini untuk Anda dengan SQL spasial.

TheSteve0
sumber
7
Kedengarannya Anda seharusnya TIDAK menyarankan bahwa orang-orang mengganti seluruh platform database mereka dan menyebabkan hasil yang tidak relevan muncul di pencarian google saya ketika saya secara eksplisit mencari mondar-mandir "Oracle" ...
Saya pernah bergulat dengan beruang.
0

Dalam kasus ekstrim pendekatan ini gagal, tetapi untuk kinerja, saya telah melewatkan trigonometri dan hanya menghitung kuadrat diagonal.

pengguna1032402
sumber
-13

Masalah ini sama sekali tidak sulit, tetapi semakin rumit jika Anda perlu mengoptimalkannya.

Maksud saya adalah, apakah Anda memiliki 100 lokasi di basis data atau 100 juta? Itu membuat perbedaan besar.

Jika jumlah lokasi kecil, keluarkan mereka dari SQL dan ke dalam kode hanya dengan melakukan ->

Select * from Location

Setelah Anda memasukkannya ke dalam kode, hitung jarak antara setiap lat / lon dan sumber asli Anda dengan rumus Haversine dan urutkan.

chamiltongt
sumber