La formule que vous utilisez pour la distance n'a pas beaucoup d'importance. Ce qui importe beaucoup plus, c'est le nombre de lignes que vous devez lire, traiter et trier. Dans le meilleur des cas, vous pouvez utiliser un index pour une condition dans la clause WHERE afin de limiter le nombre de lignes traitées. Vous pouvez essayer de catégoriser vos emplacements - Mais cela dépend de la nature de vos données, si cela fonctionne bien. Vous auriez également besoin de savoir quelle "catégorie" utiliser. Une solution plus générale serait d'utiliser un INDICE SPATIAL et le ST_Within() fonction.
Faisons maintenant quelques tests..
Dans ma BD (MySQL 5.7.18) j'ai le tableau suivant :
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
Les données proviennent de Free World Cities Database et contient 3173958 (3.1M) lignes.
Notez que geoPoint
est redondant et égal à POINT(longitude, latitude)
.
Considérez que l'utilisateur se trouve quelque part à Londres
set @lon = 0.0;
set @lat = 51.5;
et vous voulez trouver l'emplacement le plus proche des cities
tableau.
Une requête "triviale" serait
select c.cityId, c.accentCity, st_distance_sphere(c.geoPoint, point(@lon, @lat)) as dist
from cities c
order by dist
limit 1
Le résultat est
988204 Blackwall 1085.8212159861014
Temps d'exécution :~ 4.970 sec
Si vous utilisez la fonction moins complexe ST_Distance()
, vous obtenez le même résultat avec un temps d'exécution de ~ 4.580 sec - ce qui n'est pas tellement différent.
Notez que vous n'avez pas besoin de stocker un point géographique dans le tableau. Vous pouvez aussi bien utiliser (point(c.longitude, c.latitude)
au lieu de c.geoPoint
. A ma grande surprise c'est encore plus rapide (~3.6 sec pour ST_Distance
et ~4,0 s pour ST_Distance_Sphere
). Cela pourrait être encore plus rapide si je n'avais pas de geoPoint
colonne du tout. Mais cela n'a pas beaucoup d'importance, puisque vous ne voulez pas que l'utilisateur attende, alors connectez-vous pour une réponse, si vous pouvez faire mieux.
Voyons maintenant comment nous pouvons utiliser l'INDICE SPATIAL avec ST_Within()
.
Vous devez définir un polygone qui contiendra l'emplacement le plus proche. Un moyen simple consiste à utiliser ST_Buffer() qui va générer un polygone avec 32 points et est presque un cercle*.
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
Le résultat est le même. Le temps d'exécution est de ~ 0.000 sec (c'est ce que mon client (HeidiSQL ) dit).
* Notez que le @radius
est noté en degrés et ainsi le polygone ressemblera plus à une ellipse qu'à un cercle. Mais dans mes tests j'ai toujours obtenu le même résultat qu'avec la solution simple et lente. J'étudierais cependant plus de cas extrêmes avant de l'utiliser dans mon code de production.
Vous devez maintenant trouver le rayon optimal pour votre application/vos données. S'il est trop petit, vous risquez de ne pas obtenir de résultats ou de manquer le point le plus proche. S'il est trop volumineux, vous devrez peut-être traiter trop de lignes.
Voici quelques chiffres pour le cas de test donné :
- @radius =0.001 :Aucun résultat
- @radius =0,01 :exactement un emplacement (un peu de chance) - Temps d'exécution ~ 0,000 s
- @radius =0.1 :55 emplacements - Temps d'exécution ~ 0.000 sec
- @radius =1.0 : 2 183 emplacements - Temps d'exécution ~ 0,030 s