Se aggiungi campi helper alla tabella delle coordinate, puoi migliorare i tempi di risposta della query.
Come questo:
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, otterrai prestazioni ancora migliori se aggiungi indici di cluster su uno dei predicati, ad esempio in questo modo:
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
Avrai bisogno di lat e lon di base in gradi e di sin (lat) in radianti, cos (lat) * cos (lon) in radianti e cos (lat) * sin (lon) in radianti per ogni punto. Quindi crei una funzione mysql, come questa:
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 ti dà la distanza.
Non dimenticare di aggiungere un indice su lat / lon in modo che il riquadro di delimitazione possa aiutare la ricerca anziché rallentarla (l'indice è già stato aggiunto nella query CREATE TABLE sopra).
INDEX `lat_lon_idx` (`lat`, `lon`)
Data una vecchia tabella con solo coordinate lat / lon, puoi impostare uno script per aggiornarlo 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']
));
}
Quindi si ottimizza la query effettiva per eseguire il calcolo della distanza solo quando realmente necessario, ad esempio delimitando il cerchio (bene, ovale) dall'interno e dall'esterno. Per questo, dovrai precalcolare 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));
Dati questi preparativi, la query va in questo modo (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);
SPIEGARE sulla query sopra potrebbe dire che non sta usando l'indice a meno che non ci siano abbastanza risultati per innescare tale. L'indice verrà utilizzato quando ci sono abbastanza dati nella tabella delle coordinate. È possibile aggiungere FORCE INDEX (lat_lon_idx) a SELECT per farlo utilizzare l'indice indipendentemente dalle dimensioni della tabella, in modo da poter verificare con EXPLAIN che funzioni correttamente.
Con gli esempi di codice sopra riportati dovresti avere un'implementazione funzionante e scalabile della ricerca di oggetti a distanza con un errore minimo.