Finden Sie mit einer SQL-Abfrage den nächsten Breiten- / Längengrad


173

Ich habe Breiten- und Längengrade und möchte den Datensatz aus der Datenbank abrufen, deren Längen- und Breitengrad um die Entfernung am nächsten liegt. Wenn diese Entfernung länger als angegeben wird, rufen Sie sie nicht ab.

Tabellenstruktur:

id
latitude
longitude
place name
city
country
state
zip
sealevel

1
Dies ist eine Art Duplikat der Proximity-Suchfrage .
Darius Bacon

1
Es gibt eine Reihe von Folien von Alexander Rubin zur Geo-Suche (Nähe) mit MySQL (PDF-Link)
Martijn Pieters

Antworten:


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;

Dabei ist [starlat] und [startlng] die Position, an der mit der Entfernungsmessung begonnen werden soll.


49
Nur eine Leistungsnotiz, es ist am besten, nicht die Abstandsvariable zu quadrieren, sondern stattdessen den Testwert '25' zu
quadrieren

9
Was wäre die gleiche Abfrage für die Entfernung in Metern? (
Was

8
Welches Maß ist das 25?
Steffan Donal

16
Nur um dies hier zu verdeutlichen, ist 69,1 der Umrechnungsfaktor für Meilen in Breitengrade. 57,3 ist ungefähr 180 / pi, das ist also die Umrechnung von Grad in Bogenmaß für die Kosinusfunktion. 25 ist der Suchradius in Meilen. Dies ist die Formel, die bei Verwendung von Dezimalgraden und gesetzlichen Meilen verwendet wird.
John Vance

8
Außerdem wird die Krümmung der Erde nicht berücksichtigt. Dies wäre kein Problem für kurze Suchradien. Ansonsten sind die Antworten von Evan und Igor vollständiger.
John Vance

63

Googles Lösung:

Tabelle erstellen

Wenn Sie die MySQL-Tabelle erstellen, möchten Sie den Attributen lat und lng besondere Aufmerksamkeit widmen. Mit den aktuellen Zoomfunktionen von Google Maps sollten Sie nach der Dezimalstelle nur 6 Stellen Genauigkeit benötigen. Um den für Ihre Tabelle erforderlichen Speicherplatz auf ein Minimum zu beschränken, können Sie angeben, dass die Attribute lat und lng Floats der Größe (10,6) sind. Dadurch können die Felder 6 Stellen nach der Dezimalstelle sowie bis zu 4 Stellen vor der Dezimalstelle speichern, z. B. -123,456789 Grad. Ihre Tabelle sollte auch ein ID-Attribut haben, das als Primärschlüssel dient.

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 ;

Den Tisch füllen

Nach dem Erstellen der Tabelle ist es Zeit, sie mit Daten zu füllen. Die unten angegebenen Beispieldaten beziehen sich auf etwa 180 in den USA verteilte Pizzarias. In phpMyAdmin können Sie auf der Registerkarte IMPORT verschiedene Dateiformate importieren, einschließlich CSV (durch Kommas getrennte Werte). Microsoft Excel und Google Spreadsheets werden beide in das CSV-Format exportiert, sodass Sie durch Exportieren / Importieren von CSV-Dateien problemlos Daten aus Tabellenkalkulationen in MySQL-Tabellen übertragen können.

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

Suchen von Standorten mit MySQL

Um Positionen in Ihrer Markertabelle zu finden, die sich innerhalb eines bestimmten Radiusabstands zu einem bestimmten Breiten- / Längengrad befinden, können Sie eine SELECT-Anweisung verwenden, die auf der Haversine-Formel basiert. Die Haversine-Formel wird allgemein zur Berechnung von Großkreisabständen zwischen zwei Koordinatenpaaren auf einer Kugel verwendet. Eine ausführliche mathematische Erklärung wird von Wikipedia gegeben, und eine gute Diskussion der Formel in Bezug auf die Programmierung finden Sie auf der Website von Movable Type.

Hier ist die SQL-Anweisung, die die nächsten 20 Positionen findet, die sich in einem Radius von 25 Meilen zur 37, -122-Koordinate befinden. Es berechnet die Entfernung basierend auf dem Breiten- / Längengrad dieser Zeile und dem Zielbreiten- / Längengrad und fragt dann nur nach Zeilen, bei denen der Entfernungswert weniger als 25 beträgt, ordnet die gesamte Abfrage nach Entfernung und begrenzt sie auf 20 Ergebnisse. Um nach Kilometern statt nach Meilen zu suchen, ersetzen Sie 3959 durch 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;

Dieser soll Breiten- und Längengrade in einer Entfernung von weniger als 28 Meilen finden.

Eine andere ist, sie in einer Entfernung zwischen 28 und 29 Meilen zu finden:

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
Sollte es so sein HAVING distance < 25, dass wir Standorte in einem Umkreis von 25 Meilen abfragen?
Vitalii Elenhaupt

es sollte> 25 sein, dann werden alle Datensätze durchsucht
vidur punj

Sind Sie sicher, dass @vidurpunj ungefähr> 25 ist?
Amranur Rahman

Ich habe die SQL-Abfrage mit: distance <25 versucht, aber keine Ergebnisse gefunden. Da die Entfernungen der Beispielmarkierungen alle über 25 liegen ...
IbrahimShendy

37, -122 Koordinate, ist dies Längen- und Breitengrad für die Position, von der aus wir die Entfernung ermitteln müssen?
Prasobh.Kollattu

28

Hier ist meine vollständige Lösung in PHP implementiert.

Diese Lösung verwendet die Haversine-Formel, wie in http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL dargestellt .

Es ist zu beachten, dass die Haversine-Formel Schwächen an den Polen aufweist. Diese Antwort zeigt, wie man die vincenty Great Circle Distance-Formel implementiert , um dies zu umgehen. Ich habe mich jedoch für Haversine entschieden, weil es für meine Zwecke gut genug ist.

Ich speichere den Breitengrad als DEZIMAL (10,8) und den Längengrad als DEZIMAL (11,8). Hoffentlich hilft das!

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

Es kann möglich sein, die Leistung durch Verwendung einer gespeicherten MySQL-Prozedur zu steigern, wie im oben veröffentlichten Artikel "Geo-Distance-Search-with-MySQL" vorgeschlagen.

Ich habe eine Datenbank mit ~ 17.000 Stellen und die Ausführungszeit für Abfragen beträgt 0,054 Sekunden.


Wie kann ich die Entfernung in km oder Metern ermitteln? Grüße!
Chemitaxis

2
Meile * 1.609344 = km
Sinan Dizdarević

2
WARNUNG. Hervorragende Lösung, aber es hat einen Fehler. Alles abssollte entfernt werden. Es ist nicht erforderlich, den abs-Wert bei der Umrechnung von Grad in Bogenmaß zu verwenden, und selbst wenn Sie dies getan haben, tun Sie dies nur in einer der Breiten. Bitte bearbeiten Sie es, damit der Fehler behoben wird.
Chango

1
Und für alle, die dies in Metern wollen: Berechnen Sie 3956 Meilen in Kilometer: Erdradius; Konvertieren Sie 69 Meilen in Kilometer: die ungefähre Länge von 1 Grad Breite in km; Und geben Sie die Entfernung in Kilometern ein.
Chango

1
Und ersetzen Sie 69durch 111,044736(vergessen Sie das im obigen Kommentar)
rkeet

24

Nur für den Fall, dass Sie faul sind wie ich, hier ist eine Lösung, die aus dieser und anderen Antworten auf SO zusammengeführt wurde.

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
was genau bounding_distancerepräsentiert? Begrenzt dieser Wert die Ergebnisse auf einen Meilenbetrag? In diesem Fall werden die Ergebnisse innerhalb von 1 Meile zurückgegeben.
James

1
@ bounding_distance ist hier in Grad angegeben und wird verwendet, um Berechnungen zu beschleunigen, indem der effektive Suchbereich begrenzt wird. Wenn Sie beispielsweise wissen, dass sich Ihr Benutzer in einer bestimmten Stadt befindet und Sie wissen, dass Sie einige Punkte in dieser Stadt haben, können Sie den Grenzabstand sicher auf einige Grad einstellen.
Evan

1
Welche geografische Entfernungsformel wird verwendet?
bbodenmiller

12

Einfaches ;)

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

Ersetzen Sie einfach die Koordinaten durch Ihre gewünschten. Die Werte müssen doppelt gespeichert werden. Dies ist ein funktionierendes MySQL 5.x-Beispiel.

Prost


2
Keine Ahnung, warum Upvote, OP will um bestimmte Entfernung begrenzen und bestellen, nicht um 30 begrenzen und bestellen bisdx+dy
okm

3
Das hat es für mich getan. Es ist nicht das, was das OP wollte, aber es ist das, was ich wollte. Vielen Dank für Ihre Antwort! :)
Webmaster G

äußerstes ABS () ist ausreichend.
Dzona

6

Versuchen Sie dies, es werden die Punkte angezeigt, die den angegebenen Koordinaten am nächsten liegen (innerhalb von 50 km). Es funktioniert perfekt:

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

Einfach ändern <table_name>. <userLat>und<userLon>

Weitere Informationen zu dieser Lösung finden Sie hier: http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/


5

Sie suchen nach Dingen wie der Haversine-Formel . Siehe hier .

Es gibt andere, aber dies ist die am häufigsten zitierte.

Wenn Sie nach etwas noch Robusterem suchen, sollten Sie sich die GIS-Funktionen Ihrer Datenbanken ansehen. Sie können einige coole Dinge sagen, beispielsweise, ob ein Punkt (Stadt) in einem bestimmten Polygon (Region, Land, Kontinent) erscheint.


Es wird zwar am häufigsten zitiert, aber viele Artikel beziehen sich auf alte Computerhardware, wenn es um Aussagen über ungenaues Rechnen mit anderen Methoden geht. Siehe auch movable-type.co.uk/scripts/latlong.html#cosine-law
Arjan

5

Die ursprünglichen Antworten auf die Frage sind gut, aber neuere Versionen von MySQL (MySQL 5.7.6 on) unterstützen Geo-Abfragen, sodass Sie jetzt integrierte Funktionen verwenden können, anstatt komplexe Abfragen durchzuführen.

Sie können jetzt so etwas tun wie:

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

Die Ergebnisse werden also zurückgegeben meters, wenn Sie KManstelle von Meilen .0001anstelle von verwenden möchten.000621371192

MySQL-Dokumente sind hier


Wenn möglich, fügen Sie bitte als Antwort die MySQL-Version hinzu.
Parixit

ST_Distance_Sphereexistiert nicht in der install ( mysql Ver 15.1 Distrib 10.2.23-MariaDB) meines Hosts . Ich habe irgendwo gelesen, um zu ersetzen, ST_Distanceaber die Entfernungen sind weit weg.
Ashleedawg

@ashleedawg - Aus der Version denke ich, dass Sie MariaDB verwenden, eine Abzweigung von MySQL. Aus diesem Gespräch geht hervor, dass MariaDB nicht implementiert hatST_Distance_Sphere
Sherman

4

Überprüfen Sie diesen Code anhand des Artikels Geo-Distance-Search-with-MySQL :

Beispiel: Finden Sie die 10 nächstgelegenen Hotels zu meinem aktuellen Standort in einem Umkreis von 10 Meilen:

#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

Es hört sich so an, als ob Sie eine Suche nach dem nächsten Nachbarn durchführen möchten, wobei einige an die Entfernung gebunden sind. Soweit mir bekannt ist, unterstützt SQL so etwas nicht und Sie müssten eine alternative Datenstruktur wie einen R-Baum oder einen kd-Baum verwenden .


2

Finden Sie die nächstgelegenen Benutzer zu meinem:

Entfernung in Metern

Basierend auf Vincentys Formel

Ich habe Benutzertabelle:

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

Radius der Erde: 6371000 (in Metern)


1

MS SQL Edition hier:

        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

Klingt so, als sollten Sie nur PostGIS, SpatialLite, SQLServer2008 oder Oracle Spatial verwenden. Sie alle können diese Frage mit räumlichem SQL für Sie beantworten.


7
Klingt so, als ob Sie NICHT vorschlagen sollten, dass Leute ihre gesamte Datenbankplattform wechseln und irrelevante Ergebnisse in meiner Google-Suche anzeigen, wenn ich explizit nach "Oracle" suche ...
Ich habe einmal mit einem Bären gerungen.

0

In extremen Fällen schlägt dieser Ansatz fehl, aber für die Leistung habe ich die Trigonometrie übersprungen und einfach das Quadrat der Diagonale berechnet.


-13

Dieses Problem ist überhaupt nicht sehr schwer, aber es wird komplizierter, wenn Sie es optimieren müssen.

Was ich meine ist, haben Sie 100 Standorte in Ihrer Datenbank oder 100 Millionen? Es macht einen großen Unterschied.

Wenn die Anzahl der Speicherorte gering ist, entfernen Sie sie aus SQL und in den Code, indem Sie einfach -> ausführen

Select * from Location

Sobald Sie sie in den Code eingegeben haben, berechnen Sie den Abstand zwischen jedem Lat / Lon und Ihrem Original mit der Haversine-Formel und sortieren Sie ihn.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.