Se si aggiungono campi helper alla tabella delle coordinate, è possibile migliorare i tempi di risposta della query.
Ti piace questa:
CREATE TABLE `Coordinates` (
`id` INT(10) UNSIGNED NOT NULL COMMENT 'id for the object',
`type` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT 'type',
`sin_lat` FLOAT NOT NULL COMMENT 'sin(lat) in radians',
`cos_cos` FLOAT NOT NULL COMMENT 'cos(lat)*cos(lon) in radians',
`cos_sin` FLOAT NOT NULL COMMENT 'cos(lat)*sin(lon) in radians',
`lat` FLOAT NOT NULL COMMENT 'latitude in degrees',
`lon` FLOAT NOT NULL COMMENT 'longitude in degrees',
INDEX `lat_lon_idx` (`lat`, `lon`)
)
Se stai usando TokuDB, si otterrà prestazioni ancora migliori se si aggiunge il clustering indici su uno dei predicati, per esempio, in questo modo:
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
Avrai bisogno del lat lat e lon in gradi e del sin (lat) in radianti, cos (lat) * cos (lon) in radianti e cos (lat) * sin (lon) in radianti per ogni punto . Poi si crea una funzione mysql, smth come questo:
CREATE FUNCTION `geodistance`(`sin_lat1` FLOAT,
`cos_cos1` FLOAT, `cos_sin1` FLOAT,
`sin_lat2` FLOAT,
`cos_cos2` FLOAT, `cos_sin2` FLOAT)
RETURNS float
LANGUAGE SQL
DETERMINISTIC
CONTAINS SQL
SQL SECURITY INVOKER
BEGIN
RETURN acos(sin_lat1*sin_lat2 + cos_cos1*cos_cos2 + cos_sin1*cos_sin2);
END
Questo vi dà la distanza.
Non dimenticare di aggiungere un indice su lat/lon in modo che il boxing di delimitazione possa aiutare la ricerca anziché rallentarlo (l'indice è già stato aggiunto nella query CREATE TABLE sopra).
INDEX `lat_lon_idx` (`lat`, `lon`)
Dato un vecchio tavolo con solo le coordinate Lat/Lon, è possibile impostare uno script per aggiornare in questo modo: (php usando meekrodb)
$users = DB::query('SELECT id,lat,lon FROM Old_Coordinates');
foreach ($users as $user)
{
$lat_rad = deg2rad($user['lat']);
$lon_rad = deg2rad($user['lon']);
DB::replace('Coordinates', array(
'object_id' => $user['id'],
'object_type' => 0,
'sin_lat' => sin($lat_rad),
'cos_cos' => cos($lat_rad)*cos($lon_rad),
'cos_sin' => cos($lat_rad)*sin($lon_rad),
'lat' => $user['lat'],
'lon' => $user['lon']
));
}
Poi si ottimizzare la query effettiva solo eseguire il calcolo della distanza quando è veramente necessario, ad esempio vincolando il cerchio (bene, ovale) dall'interno e dall'esterno. Per questo, avrete bisogno di precalculate diverse metriche per la query stessa:
// assuming the search center coordinates are $lat and $lon in degrees
// and radius in km is given in $distance
$lat_rad = deg2rad($lat);
$lon_rad = deg2rad($lon);
$R = 6371; // earth's radius, km
$distance_rad = $distance/$R;
$distance_rad_plus = $distance_rad * 1.06; // ovality error for outer bounding box
$dist_deg_lat = rad2deg($distance_rad_plus); //outer bounding box
$dist_deg_lon = rad2deg($distance_rad_plus/cos(deg2rad($lat)));
$dist_deg_lat_small = rad2deg($distance_rad/sqrt(2)); //inner bounding box
$dist_deg_lon_small = rad2deg($distance_rad/cos(deg2rad($lat))/sqrt(2));
A fronte di tali preparati, la query più o meno così (PHP):
$neighbors = DB::query("SELECT id, type, lat, lon,
geodistance(sin_lat,cos_cos,cos_sin,%d,%d,%d) as distance
FROM Coordinates WHERE
lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d
HAVING (lat BETWEEN %d AND %d AND lon BETWEEN %d AND %d) OR distance <= %d",
// center radian values: sin_lat, cos_cos, cos_sin
sin($lat_rad),cos($lat_rad)*cos($lon_rad),cos($lat_rad)*sin($lon_rad),
// min_lat, max_lat, min_lon, max_lon for the outside box
$lat-$dist_deg_lat,$lat+$dist_deg_lat,
$lon-$dist_deg_lon,$lon+$dist_deg_lon,
// min_lat, max_lat, min_lon, max_lon for the inside box
$lat-$dist_deg_lat_small,$lat+$dist_deg_lat_small,
$lon-$dist_deg_lon_small,$lon+$dist_deg_lon_small,
// distance in radians
$distance_rad);
contare sulla query precedente potrebbe dire che non sta usando l'indice a meno che non ci siano risultati sufficienti per far scattare tale. L'indice sarà usato quando ci sono abbastanza dati nella tabella delle coordinate. È possibile aggiungere FORCE INDEX (lat_lon_idx) a SELECT per fare in modo che utilizzi l'indice indipendentemente dalle dimensioni della tabella, in modo da poter verificare con EXPLAIN che funzioni correttamente.
Con gli esempi di codice sopra riportato si dovrebbe avere un'implementazione funzionante e scalabile della ricerca di oggetti per distanza con errore minimo.
Sulla base di tutte le ottime risposte qui sotto, [campione qui sta lavorando della formula Haversine in azione] (http://sqlfiddle.com/#!2/abba1/2/0) –
Grazie per aver condiviso Michael.M –
http://stackoverflow.com/a/40272394/1281385 Ecco un esempio di come assicurarsi che l'indice sia stato colpito – exussum