Si vous ajoutez des champs d'assistance à la table de coordonnées, vous pouvez améliorer le temps de réponse de la requête.
Comme ça:
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`)
)
Si vous utilisez TokuDB, vous obtiendrez des performances encore meilleures si vous ajoutez des index de clustering sur l'un des prédicats, par exemple, comme ceci:
alter table Coordinates add clustering index c_lat(lat);
alter table Coordinates add clustering index c_lon(lon);
Vous aurez besoin des lat et lon de base en degrés ainsi que sin (lat) en radians, cos (lat) * cos (lon) en radians et cos (lat) * sin (lon) en radians pour chaque point. Ensuite, vous créez une fonction mysql, comme ceci:
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
Cela vous donne la distance.
N'oubliez pas d'ajouter un index sur lat / lon pour que la boîte englobante puisse aider la recherche au lieu de la ralentir (l'index est déjà ajouté dans la requête CREATE TABLE ci-dessus).
INDEX `lat_lon_idx` (`lat`, `lon`)
Étant donné une ancienne table avec uniquement des coordonnées lat / lon, vous pouvez configurer un script pour le mettre à jour comme ceci: (php en utilisant 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']
));
}
Ensuite, vous optimisez la requête réelle pour ne faire le calcul de la distance que lorsque cela est vraiment nécessaire, par exemple en délimitant le cercle (enfin, ovale) de l'intérieur et de l'extérieur. Pour cela, vous devrez précalculer plusieurs métriques pour la requête elle-même:
// 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));
Compte tenu de ces préparatifs, la requête ressemble à ceci (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);
EXPLAIN sur la requête ci-dessus peut indiquer qu'il n'utilise pas d'index à moins qu'il n'y ait suffisamment de résultats pour déclencher un tel. L'index sera utilisé lorsqu'il y aura suffisamment de données dans la table de coordonnées. Vous pouvez ajouter FORCE INDEX (lat_lon_idx) au SELECT pour lui faire utiliser l'index sans tenir compte de la taille de la table, afin que vous puissiez vérifier avec EXPLAIN qu'il fonctionne correctement.
Avec les exemples de code ci-dessus, vous devriez avoir une implémentation fonctionnelle et évolutive de la recherche d'objets par distance avec une erreur minimale.