Quale formula usi per la distanza non ha molta importanza. Ciò che conta di più è il numero di righe che devi leggere, elaborare e ordinare. Nel migliore dei casi è possibile utilizzare un indice per una condizione nella clausola WHERE per limitare il numero di righe elaborate. Puoi provare a classificare le tue posizioni, ma dipende dalla natura dei tuoi dati, se funzionerà bene. Dovresti anche scoprire quale "categoria" usare. Una soluzione più generale sarebbe quella di utilizzare un INDICE SPAZIALE e ST_Within() funzione.
Ora eseguiamo alcuni test..
Nel mio DB (MySQL 5.7.18) ho la seguente tabella:
CREATE TABLE `cities` (
`cityId` MEDIUMINT(9) UNSIGNED NOT NULL AUTO_INCREMENT,
`country` CHAR(2) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`city` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`accentCity` VARCHAR(100) NOT NULL COLLATE 'utf8mb4_unicode_ci',
`region` CHAR(2) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_ci',
`population` INT(10) UNSIGNED NULL DEFAULT NULL,
`latitude` DECIMAL(10,7) NOT NULL,
`longitude` DECIMAL(10,7) NOT NULL,
`geoPoint` POINT NOT NULL,
PRIMARY KEY (`cityId`),
SPATIAL INDEX `geoPoint` (`geoPoint`)
) COLLATE='utf8mb4_unicode_ci' ENGINE=InnoDB
I dati provengono da Database delle città del mondo libero e contiene 3173958 (3.1M) righe.
Nota che geoPoint
è ridondante e uguale a POINT(longitude, latitude)
.
Considera che l'utente si trova da qualche parte a Londra
set @lon = 0.0;
set @lat = 51.5;
e vuoi trovare la posizione più vicina dalle cities
tabella.
Una query "banale" sarebbe
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Il risultato è
988204 Blackwall 1085.8212159861014
Tempo di esecuzione:~ 4.970 sec
Se usi la funzione meno complessa ST_Distance()
, ottieni lo stesso risultato con un tempo di esecuzione di ~ 4.580 sec, il che non fa molta differenza.
Tieni presente che non è necessario memorizzare un punto geografico nella tabella. Puoi anche usare (point(c.longitude, c.latitude)
invece di c.geoPoint
. Con mia sorpresa è ancora più veloce (~3,6 secondi per ST_Distance
e ~4,0 secondi per ST_Distance_Sphere
). Potrebbe essere ancora più veloce se non avessi un geoPoint
colonna affatto. Ma non importa ancora molto, dal momento che non vuoi che l'utente attenda, quindi accedi per una pausa, se puoi fare di meglio.
Ora vediamo come possiamo utilizzare l'INDICE SPAZIALE con ST_Within()
.
Devi definire un poligono che conterrà la posizione più vicina. Un modo semplice è usare ST_Buffer() che genererà un poligono con 32 punti ed è quasi un cerchio*.
set @point = point(@lon, @lat);
set @radius = 0.1;
set @polygon = ST_Buffer(@point, @radius);
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
where st_within(c.geoPoint, @polygon)
order by dist
limit 1
Il risultato è lo stesso. Il tempo di esecuzione è di ~ 0.000 sec (questo è ciò che il mio client (HeidiSQL ) dice).
* Nota che il @radius
è annotato in gradi e quindi il poligono sarà più simile a un'ellisse che a un cerchio. Ma nei miei test ho sempre ottenuto lo stesso risultato della soluzione semplice e lenta. Vorrei però indagare su più casi limite, prima di utilizzarlo nel mio codice di produzione.
Ora devi trovare il raggio ottimale per la tua applicazione/dati. Se è troppo piccolo, potresti non ottenere risultati o perdere il punto più vicino. Se è troppo grande, potresti dover elaborare troppe righe.
Ecco alcuni numeri per il test case indicato:
- @radius =0,001:Nessun risultato
- @radius =0.01:esattamente una posizione (un po' fortunata) - Tempo di esecuzione ~ 0.000 sec
- @radius =0,1:55 posizioni - Tempo di esecuzione ~ 0,000 sec
- @radius =1,0:2183 posizioni - Tempo di esecuzione ~ 0,030 sec