2013-05-09 17 views
8

Ogni utente nel mio database ha la latitudine e la longitudine memorizzato in due campi (lat, lon)ordinamento query MySQL dalla latitudine/longitudine

Il formato di ogni campo è:

lon | -1.403976 
lat | 53.428691 

Se un utente ricerche per altri utenti all'interno, dicono 100 miglia, mi esibisco il seguente al fine di calcolare l'intervallo di lat/lon appropriato ($ lat e lon $ sono i valori degli utenti correnti)

$R = 3960; // earth's mean radius 
$rad = '100'; 
// first-cut bounding box (in degrees) 
$maxLat = $lat + rad2deg($rad/$R); 
$minLat = $lat - rad2deg($rad/$R); 
// compensate for degrees longitude getting smaller with increasing latitude 
$maxLon = $lon + rad2deg($rad/$R/cos(deg2rad($lat))); 
$minLon = $lon - rad2deg($rad/$R/cos(deg2rad($lat))); 

$maxLat=number_format((float)$maxLat, 6, '.', ''); 
$minLat=number_format((float)$minLat, 6, '.', ''); 
$maxLon=number_format((float)$maxLon, 6, '.', ''); 
$minLon=number_format((float)$minLon, 6, '.', ''); 

posso quindi eseguire una quer y quali:

$query = "SELECT * FROM table WHERE lon BETWEEN '$minLon' AND '$maxLon' AND lat BETWEEN '$minLat' AND '$maxLat'"; 

Questo funziona bene, e utilizzare una funzione per Calulate e visualizzare la distanza effettiva tra gli utenti in fase di uscita, ma vorrei poter ordinare i risultati diminuendo o aumentando la distanza alla fase di ricerca.

C'è un modo per farlo?

risposta

10

Ricorda Pitagora?

$sql = "SELECT * FROM table 
    WHERE lon BETWEEN '$minLon' AND '$maxLon' 
     AND lat BETWEEN '$minLat' AND '$maxLat' 
    ORDER BY (POW((lon-$lon),2) + POW((lat-$lat),2))"; 

Tecnicamente questo è il quadrato della distanza, invece della distanza effettiva, ma dal momento che si sta solo usando per l'ordinamento che non importa.

Questo utilizza la formula della distanza planare, che dovrebbe essere buona su piccole distanze.

TUTTAVIA:

Se si vuole essere più precisi o utilizzare le distanze più lunghe, utilizzare this formula for great circle distances in radians:

dist = acos[ sin(lat1)*sin(lat2)+cos(lat1)*cos(lat2)*cos(lng1-lng2) ] 

(Per ottenere la distanza in unità reali anziché in radianti, moltiplicarlo per il raggio della Terra. Questo non è necessario per gli scopi di ordinamento, però.)

Latitudine e longitudine vengono assunte dal motore di calcolo MySQL per essere in radianti, quindi se è memorizzato in d egrees (e probabilmente lo è), si dovrà moltiplicare ogni valore per pi/180, a circa 0,01,745 mila:

$sf = 3.14159/180; // scaling factor 
$sql = "SELECT * FROM table 
    WHERE lon BETWEEN '$minLon' AND '$maxLon' 
     AND lat BETWEEN '$minLat' AND '$maxLat' 
    ORDER BY ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf))"; 

o anche:

$sf = 3.14159/180; // scaling factor 
$er = 6350; // earth radius in miles, approximate 
$mr = 100; // max radius 
$sql = "SELECT * FROM table 
    WHERE $mr >= $er * ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf)) 
    ORDER BY ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf))"; 
+0

Grazie Blaze, sembra funzionare - ce ne sono uno o due fuori posto ma non è un dealbreaker (a meno che non si pensi a come posso aggirarlo). Praticamente a prescindere. Grazie – Dan

+0

Sono davvero fuori posto in base alla distanza al quadrato? O pensi che la distanza al quadrato sia stata calcolata in modo errato? – Blazemonger

+1

Quella query non tiene conto del fatto che la longitudine è misurata est-ovest dal meridiano di Greenwich, quindi restituirà distanze molto lunghe per due luoghi vicini l'uno all'altro e vicino al 180 ° meridiano. – nitro2k01

0

non ti danno i risultati in ordine di distanza planare (non tenendo conto della curvatura della terra) ma per un raggio ridotto "dovrebbe funzionare.

SELECT * from table where lon between '$minLon' and '$maxLon' and lat between '$minLat' and '$maxLat' order by (abs(lon-$lon)/2) + (abs(lat-$lat)/2); 
+0

Orange, grazie e come la risposta di Blaze questo funziona con una manciata di fuori posto - Blaze sembra avere uno o due meno fuori posto però. In aumento a prescindere. La ringrazio per la risposta. – Dan

+1

Il '/ 2' è superfluo perché stai solo confrontando la grandezza, e le parentesi extra sono superflue perché l'aggiunta ha già una precedenza più alta della divisione. – nitro2k01

4

Utilizzare solo SELECT * FROM Table WHERE lat between $minlat and $maxlat non sarà sufficientemente preciso.

Il modo corretto per interrogare la distanza è utilizzare le coordinate in radianti.

<?php 
    $sql = "SELECT * FROM Table WHERE acos(sin(1.3963) * sin(Lat) + cos(1.3963) * cos(Lat) * cos(Lon - (-0.6981))) * 6371 <= 1000"; 

Ecco un pratico di riferimento - http://janmatuschek.de/LatitudeLongitudeBoundingCoordinates

Ad esempio:

<?php 
    $distance = 100; 
    $current_lat = 1.3963; 
    $current_lon = -0.6981; 
    $earths_radius = 6371; 

    $sql = "SELECT * FROM Table T WHERE acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius <= $distance"; 

E se si vuole fare l'ordine e mostrare la distanza:

<?php 
    $distance = 100; 
    $current_lat = 1.3963; 
    $current_lon = -0.6981; 
    $earths_radius = 6371; 

    $sql = "SELECT *, (acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius) as distance FROM Table T WHERE acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius <= $distance ORDER BY acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius DESC"; 

Edited per @Blazemonger e per evitare dubbi :) Se vuoi lavorare in gradi invece di radianti:

<?php 
    $current_lat_deg = 80.00209691585; 
    $current_lon_deg = -39.99818366895; 
    $radians_to_degs = 57.2957795; 

    $distance = 100; 
    $current_lat = $current_lat_deg/$radians_to_degs; 
    $current_lon = $current_lon_deg/$radians_to_degs; 
    $earths_radius = 6371; 

    $sql = "SELECT *, (acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius) as distance FROM Table T WHERE acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius <= $distance ORDER BY acos(sin($current_lat) * sin(T.Lat) + cos($current_lat) * cos(T.Lat) * cos(T.Lon - ($current_lon))) * $earths_radius DESC"; 

Si potrebbe facilmente concludere questo in una classe che ha accettato Radianti o Gradi dalle informazioni fornite sopra.

+1

È più che altro. – nitro2k01

+0

Ho la mia giusta quota di app di geolocalizzazione utilizzando questo codice esatto. Nessuna lamentela finora! :) –

+0

Luke, apprezzo la risposta, ma mi confonde un po '- puoi modificarlo usando le mie variabili $ max e $ min nei posti giusti? Odio chiedere ma, beh, lo sai :) – Dan

0

Questa è la formula che mi ha dato i risultati corretti (al contrario delle soluzioni di cui sopra). Confermato utilizzando la funzione "Misura distanza" di Google Maps (distanza diretta, non la distanza di trasporto).

SELECT 
    *, 
    (3959 * acos(cos(radians(:latitude)) * cos(radians(latitude)) * cos(radians(longitude) - radians(:longitude)) + sin(radians(:latitude)) * sin(radians(latitude)))) AS `distance` 
FROM `locations` 
ORDER BY `distance` ASC 

:latitude e :longitude sono i segnaposto per le funzioni DOP. Puoi sostituirli con i valori effettivi, se lo desideri. latitude e longitude sono i nomi delle colonne.

3959 è il raggio della Terra in miglia; l'uscita distance sarà anch'essa in miglia. Per cambiarlo in chilometri, sostituire 3959 con 6371.

+0

È una risposta identica alla risposta mia e di @ Blazemonger. L'ordine del forum è diverso, ma altrimenti hai usato PDO (quando l'OP non funziona) e hai forzato i radianti usando la funzione radianti in MYSQL. Per non parlare, rivendicare altre risposte è fuorviante e downvoting, non è nello spirito di SO. –

+0

Grazie per il tuo commento. Ancora una volta, ho copiato le domande dalle risposte sopra (compresa la tua) e quelle mi hanno dato i valori * fuorvianti * - bene che li ho ricontrollati. Così ho passato i miei vecchi archivi di codice e ho trovato la query che funzionava per me allora. Quindi questa è solo un'altra risposta (di lavoro), e sì ho downvoted perché le altre query non hanno funzionato per me. Non vedo cosa c'è di sbagliato in questo, mi dispiace. –

+0

È strano. Usando i miei dati le domande danno la stessa risposta. Mi piacerebbe vedere i tuoi dati per confrontare i risultati! Deve essere qualcosa di molto strano in corso. –