SQL sorgusuyla en yakın enlem / boylamı bulun


173

Enlem ve boylamım var ve en yakın enlem ve boylam mesafesine göre veritabanından kayıt almak istiyorum, eğer bu mesafe belirtilenden daha uzun sürerse, o zaman geri almayın.

Tablo yapısı:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
Bu, yakınlık arama sorusunun bir nüshası .
Darius Bacon

1
Alexander Rubin tarafından MySQL ile Geo (yakınlık) aramasında bir dizi slayt var (PDF linki)
Martijn Pieters

Yanıtlar:


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;

burada [starlat] ve [startlng] mesafeyi ölçmeye başlanacak konumdur.


49
Sadece bir performans notu, mesafe değişkenini sqrt değil, bunun yerine '25' test değerinin karesini almak en iyisidir ... daha sonra mesafeyi göstermeniz gerekiyorsa geçen sonuçları sqrt
sradforth

9
Metre cinsinden uzaklık için aynı sorgu ne olurdu? (şu anda mil
cinsinden

8
Hangi ölçüm bu 25?
Steffan Donal

16
Burada açıklığa kavuşturmak gerekirse, 69.1 mil ila enlem derecelerinin dönüşüm faktörüdür. 57.3 kabaca 180 / pi'dir, bu nedenle kosinüs fonksiyonu için dereceden radyana dönüşür. 25, mil cinsinden arama yarıçapıdır. Bu, ondalık dereceler ve durum milleri kullanırken kullanılacak formüldür.
John Vance

8
Ek olarak, dünyanın eğriliğini dikkate almaz. Bu kısa arama yarıçapları için bir sorun olmaz. Aksi halde Evan ve Igor'un cevapları daha eksiksizdir.
John Vance

63

Google'ın çözümü:

Tablo Oluşturma

MySQL tablosunu oluşturduğunuzda, lat ve lng özniteliklerine özellikle dikkat etmek istersiniz. Google Haritalar'ın mevcut yakınlaştırma özellikleriyle, ondalık basamaktan sonra yalnızca 6 basamak hassasiyete ihtiyacınız vardır. Tablonuz için gereken depolama alanını minimumda tutmak için lat ve lng özniteliklerinin boyutta kayan (10,6) olacağını belirtebilirsiniz. Bu, alanların ondalık basamaktan sonra 6 basamak ve ondalık basamaktan önce 4 basamağa kadar depolamasını sağlar, örneğin -123.456789 derece. Tablonuzda ayrıca birincil anahtar olarak kullanılacak bir id niteliği de olmalıdır.

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 ;

Tabloyu doldurma

Tabloyu oluşturduktan sonra, verileri veri ile doldurma zamanı geldi. Aşağıda verilen örnek veriler, Amerika Birleşik Devletleri'ne dağılmış yaklaşık 180 pizzacı içindir. PhpMyAdmin'de, CSV (virgülle ayrılmış değerler) dahil olmak üzere çeşitli dosya biçimlerini içe aktarmak için İTHALAT sekmesini kullanabilirsiniz. Microsoft Excel ve Google E-Tablolar CSV biçiminde dışa aktarılır, böylece CSV dosyalarını dışa / içe aktararak elektronik tablolardan MySQL tablolarına kolayca veri aktarabilirsiniz.

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

MySQL ile Konum Bulma

İşaretler tablonuzda belirli bir enlem / boylamın belirli bir yarıçap mesafesinde olan konumları bulmak için, Haversine formülüne dayalı bir SELECT ifadesi kullanabilirsiniz. Haversine formülü genellikle bir küre üzerindeki iki çift koordinat arasındaki büyük daire mesafelerini hesaplamak için kullanılır. Vikipedi tarafından derinlemesine bir matematiksel açıklama verilir ve formülün programlama ile ilgili iyi bir tartışması Hareketli Tip'in sitesinde bulunmaktadır.

İşte 37, -122 koordinatına 25 mil yarıçap içinde en yakın 20 konumu bulan SQL deyimi. Mesafeyi, o satırın enlem / boylamına ve hedef enlem / boylama göre hesaplar ve sonra yalnızca mesafe değerinin 25'ten küçük olduğu satırları sorar, tüm sorguyu mesafeye göre sıralar ve 20 sonuçla sınırlar. Mil yerine kilometre ile arama yapmak için 3959 yerine 6371 yazın.

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;

Bu, 28 milden daha kısa bir mesafede enlem ve boylamları bulmaktır.

Bir diğeri, onları 28 ila 29 mil arasında bir mesafede bulmaktır:

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


1
HAVING distance < 2525 mil yarıçapındaki yerleri sorgularken böyle mi olmalıyız ?
Vitalii Elenhaupt

> 25 olmalıdır, o zaman tüm kayıtları
arayacaktır

@vidurpunj> 25 hakkında emin misiniz?
Amranur Rahman

Ben sql sorgusu kullanarak denedim: mesafe <25 ama hiçbir sonuç bulunamadı .. örnek işaretleri mesafelerinin hepsi 25 üzerinde olduğu gibi ...
IbrahimShendy

37, -122 koordinatı, mesafeyi bulmamız gereken yerden bu enlem ve boylam mı?
Prasobh.Kollattu

28

İşte benim tam çözüm PHP'de uygulanan.

Bu çözüm, http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL'de sunulduğu gibi Haversine formülünü kullanır .

Haversine formülünün kutuplar etrafında zayıflıklar yaşadığına dikkat edilmelidir. Bu cevap , bunun için vincenty Great Circle Distance formülünün nasıl uygulanacağını gösteriyor , ancak Haversine'i kullanmayı seçtim, çünkü bu benim amacım için yeterince iyi.

Enlemi DECIMAL (10,8) ve boylamı DECIMAL (11,8) olarak saklıyorum. Umarım bu yardımcı olur!

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

Yukarıda yayınlanan "MySQL ile Coğrafi Mesafe-Arama-ile Arama" makalesi tarafından önerilen bir MySQL saklı yordamı kullanarak performansı artırmak mümkün olabilir.

~ 17.000 yer bir veritabanı var ve sorgu yürütme süresi 0.054 saniyedir.


Km veya Metre cinsinden mesafeyi nasıl alabilirim? Saygılarımızla!
chemitaxis

2
mil * 1.609344 = km
Sinan Dizdarević

2
UYARI. Mükemmel bir çözüm, ancak bir hata var. Tüm abskaldırılmalıdır. Dereceden radyana dönüştürürken abs değerini almanıza gerek yoktur ve bunu yapsanız bile enlemlerden sadece birine yaparsınız. Lütfen hatayı düzelterek düzenleyin.
Chango

1
Ve bunu metre cinsinden isteyen herkes için: 3956 mil'i kilometreye çevirin: Dünya yarıçapı; 69 mil - Kilometre dönüştürme: yaklaşık 1 km enlem uzunluğu km; Ve mesafeyi kilometre olarak girin.
Chango

1
Ve değiştirin 69ile 111,044736(unuttuğu yukarıdaki yorum olarak)
rkeet

24

Benim gibi tembel olmanız durumunda, işte bundan ve SO'daki diğer cevaplardan bir çözüm.

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;

1
tam olarak neyi bounding_distancetemsil eder? bu değer sonuçları bir mil miktarıyla sınırlıyor mu? yani bu durumda 1 mil içinde sonuç döndürür?
james

1
@ bounding_distance burada derece cinsindendir ve etkili arama bölgesini sınırlandırarak hesaplamaları hızlandırmak için kullanılır. Örneğin, kullanıcınızın belirli bir şehirde olduğunu biliyorsanız ve o şehirde birkaç noktanız olduğunu biliyorsanız sınırlama mesafenizi birkaç dereceye kadar güvenle ayarlayabilirsiniz.
Evan

1
Bu hangi coğrafi uzaklık formülünü kullanıyor?
bbodenmiller

12

Kolay bir tanesi ;)

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

Sadece koordinatları gerekli olanlarla değiştirin. Değerler çift olarak saklanmalıdır. Bu çalışan bir MySQL 5.x örneğidir.

Şerefe


2
Hiçbir fikre neden upvote, OP sınırlamak ve belirli bir mesafeye göre sipariş, 30 ile sınırlamak değil ve sipariş etmek istiyordx+dy
okm

3
Bu benim için yaptı. OP'nin istediği bu değildi, ama istediğim şeydi, bu yüzden cevap verdiğiniz için teşekkürler! :)
Webmaster G

en dıştaki ABS () yeterlidir.
dzona

6

Bunu deneyin, sağlanan koordinatlara en yakın noktaları gösterir (50 km içinde). Mükemmel çalışıyor:

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

Sadece değiştir <table_name>. <userLat>ve<userLon>

Bu çözüm hakkında daha fazla bilgiyi buradan edinebilirsiniz: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/


6

Sorunun orijinal cevapları iyidir, ancak mysql'in (MySQL 5.7.6 on) daha yeni sürümleri coğrafi sorguları destekler, böylece artık karmaşık sorgular yapmak yerine yerleşik işlevleri kullanabilirsiniz.

Şimdi şöyle bir şey yapabilirsiniz:

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

Sonuçlar döndürülür meters. Yani KMsadece .001yerine kullanmak istiyorsanız .000621371192(ki mil içindir).

MySql dokümanları burada


Mümkünse lütfen yanıtta mysql sürümünü ekleyin.
Parixit

ST_Distance_Spheresunucumun install ( mysql Ver 15.1 Distrib 10.2.23-MariaDB) dosyasında mevcut değil . Değiştirmek için bir yer okudum ST_Distanceama mesafeler çok uzak.
ashleedawg

@ashleedawg - Sürümden, sanırım bir mysql çatalı olan MariaDB'yi kullanıyorsunuz. Gönderen bu konuşmanın mariadb uygulamadığını gibi görünüyorST_Distance_Sphere
Sherman

5

Haversine formülü gibi şeyler arıyorsunuz . Buraya da bakın .

Başkaları da var ama en çok alıntı yapılan bu.

Daha da sağlam bir şey arıyorsanız, veritabanlarınızın GIS özelliklerine bakmak isteyebilirsiniz. Belirli bir çokgen içinde (Bölge, Ülke, Kıta) bir noktanın (Şehir) görünüp görünmediğini söylemek gibi harika şeyler yapabilirler.


Gerçekten de en çok atıfta bulunulan şeydir, ancak birçok makale, diğer yöntemleri kullanarak yanlış bilgi işlemle ilgili ifadeler söz konusu olduğunda eski bilgi işlem donanımına atıfta bulunur. Ayrıca bkz. Movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan

4

MySQL ile Geo-Distance-Search- makalesine dayalı olarak bu kodu kontrol edin :

Örnek: Mevcut konumuma 10 mil yarıçapında en yakın 10 oteli bulun:

#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.

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

2

Kimi mesafeye bağlı olarak en yakın komşu arama yapmak istediğiniz gibi geliyor. SQL bildiğim kadarıyla böyle bir şeyi desteklemiyor ve R-ağacı veya kd-ağacı gibi alternatif bir veri yapısı kullanmanız gerekecek .


2

En yakın kullanıcıları bul:

Metre cinsinden uzaklık

Based VINCENTY formülüne

Kullanıcı tablosu var:

+----+-----------------------+---------+--------------+---------------+
| 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

dünyanın yarıçapı: 6371000 (metre cinsinden)


1

MS SQL Sürümü burada:

        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

0

Sadece PostGIS, SpatialLite, SQLServer2008 veya Oracle Spatial kullanmanız gerekir. Hepsi bu soruyu sizin için uzamsal SQL ile cevaplayabilir.


7
Eğer insanlar açıkça tüm "Oracle" arama yaptığımda insanlar tüm veritabanı platformunu değiştirmek ve alakasız sonuçları google arama göstermek için neden ÖNERMEMELİ gibi geliyor ...
Ben bir kez bir ayı güreşti.

0

Aşırı durumlarda bu yaklaşım başarısız olur, ancak performans için trigonometriyi atladım ve sadece diyagonal kareyi hesapladım.


-13

Bu sorun hiç de zor değil, ancak optimize etmeniz gerekiyorsa daha karmaşık hale geliyor.

Demek istediğim, veritabanınızda 100 konum mu var yoksa 100 milyon mu? Büyük bir fark yaratıyor.

Konum sayısı azsa, bunları yaparak SQL'den ve koda dönüştürün ->

Select * from Location

Kod haline getirdikten sonra, her lat / lon ve orijinaliniz arasındaki mesafeyi Haversine formülü ile hesaplayın ve sıralayın.

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.