J'ai une base de données MySQL. Je stocke les maisons dans la base de données et effectue littéralement une seule requête contre la base de données,, mais cette requête doit être exécutée très rapidement, et toutes les maisons doivent être renvoyées dans un carré et une latitude.
SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???
Quelle est la meilleure façon pour moi de stocker mes données géographiques afin que je puisse effectuer cette requête d'affichage de tous les foyers dans la zone de géolocalisation le plus rapide?
Fondamentalement:
Si cela peut aider, j'ai inclus mon schéma de table de base de données ci-dessous:
CREATE TABLE IF NOT EXISTS `homes` (
`home_id` int(10) unsigned NOT NULL auto_increment,
`address` varchar(128) collate utf8_unicode_ci NOT NULL,
`city` varchar(64) collate utf8_unicode_ci NOT NULL,
`state` varchar(2) collate utf8_unicode_ci NOT NULL,
`Zip` mediumint(8) unsigned NOT NULL,
`price` mediumint(8) unsigned NOT NULL,
`sqft` smallint(5) unsigned NOT NULL,
`year_built` smallint(5) unsigned NOT NULL,
`geolat` decimal(10,6) default NULL,
`geolng` decimal(10,6) default NULL,
PRIMARY KEY (`home_id`),
KEY `geolat` (`geolat`),
KEY `geolng` (`geolng`),
) ENGINE=InnoDB ;
METTRE À JOUR
Je comprends que l’espace prendra en compte la courbure de la Terre, mais je souhaite avant tout restituer les données géographiques au PLUS RAPIDE. À moins que ces packages de base de données spatiales ne renvoient les données plus rapidement, veuillez ne pas recommander les extensions spatiales. Merci
MISE À JOUR 2
Veuillez noter que personne ci-dessous n'a vraiment répondu à la question. Je suis vraiment impatient de recevoir de l'aide. Merci d'avance.
Il existe un bon article sur les performances de géolocalisation de MySQL ici .
EDIT Je suis presque certain qu'il s'agit d'un rayon fixe. De plus, je ne suis pas sûr à 100% que l’algorithme de calcul de la distance soit le plus avancé (c’est-à-dire qu’il va "percer" sur Terre).
Ce qui est important, c’est que l’algorithme n’est pas cher et vous donne une limite au nombre de lignes pour effectuer une recherche de distance appropriée.
L'algorithme pré-filtre en prenant les candidats dans un carré autour du point source, puis en calculant la distance en miles.
Pré-calculez ceci ou utilisez une procédure stockée comme le suggère la source:
# Pseudo code
# user_lon and user_lat are the source longitude and latitude
# radius is the radius where you want to search
lon_distance = radius / abs(cos(radians(user_lat))*69);
min_lon = user_lon - lon_distance;
max_lon = user_lon + lon_distance;
min_lat = user_lat - (radius / 69);
max_lat = user_lat + (radius / 69);
SELECT dest.*,
3956 * 2 * ASIN(
SQRT(
POWER(
SIN(
(user_lat - dest.lat) * pi() / 180 / 2
), 2
) + COS(
user_lat * pi() / 180
) * COS(
dest.lat * pi() / 180
) * POWER(
SIN(
(user_lon - dest.lon) * pi() / 180 / 2
), 2
)
)
) as distance
FROM dest
WHERE
dest.lon between min_lon and max_lon AND
dest.lat between min_lat and max_lat
HAVING distance < radius
ORDER BY distance
LIMIT 10
J'ai eu le même problème et j'ai écrit un article de blog en 3 parties. C'était plus rapide que l'index géographique.
Si vous avez vraiment besoin de performances, vous pouvez définir des cadres de délimitation pour vos données et les mapper aux objets de vos calculs lors de l'insertion, puis les utiliser ultérieurement pour les requêtes.
Si les ensembles de résultats sont raisonnablement faibles, vous pouvez toujours effectuer des corrections d’exactitude dans la logique de l’application (plus facile à mettre à l’échelle horizontale qu’une base de données) tout en permettant de fournir des résultats précis.
Jetez un coup d'oeil à Bret Slatkin's geobox.py qui contient une excellente documentation pour l'approche.
Je recommanderais quand même de vérifier PostgreSQL et PostGIS par rapport à MySQL si vous avez l’intention de faire des requêtes plus complexes dans un avenir prévisible.
Voici un truc que j'ai utilisé avec un certain succès est de créer des régions arrondies. En d’autres termes, si vous avez un emplacement compris entre 36.12345 et -120.54321 et que vous souhaitez le regrouper avec d’autres emplacements situés dans une zone de grille de moins d’un demi-mile (approximativement), vous pouvez appeler sa région au tous les autres endroits avec la même région arrondie tomberont dans la même case.
Évidemment, cela ne vous donne pas un rayon propre, c’est-à-dire si l’emplacement que vous regardez est plus proche d’un bord que d’un autre. Cependant, avec ce type de configuration, il est assez facile de calculer les huit cases qui entourent la case de votre emplacement principal. En être témoin:
[36.13x-120.55][36.13x-120.54][36.13x-120.53]
[36.12x-120.55][36.12x-120.54][36.12x-120.53]
[36.11x-120.55][36.11x-120.54][36.11x-120.53]
Tirez sur tous les emplacements avec les étiquettes d'arrondi correspondantes, puis, une fois que vous les aurez retirés de la base de données, vous pourrez faire vos calculs de distance pour déterminer ceux à utiliser.
Les index que vous utilisez sont bien des index B-tree et prennent en charge le mot clé BETWEEN
dans votre requête. Cela signifie que l'optimiseur est capable d'utiliser vos index pour trouver les maisons dans votre "boîte". Cela ne signifie toutefois pas qu'il utilisera toujours les indices. Si vous spécifiez une plage contenant trop de "résultats", les index ne seront pas utilisés.
Depuis MySQL 5.7, mysql peut utiliser geoindex comme ST_Distance_Sphere () et ST_Contains () pour améliorer les performances.
Une très bonne alternative est MongoDB avec son Indexation géospatiale .
Maisons? Vous n'en aurez probablement même pas dix mille. Utilisez simplement un index en mémoire tel que STRTree .
Cela semble assez rapide. Mon seul souci serait d'utiliser un index pour obtenir toutes les valeurs dans les 3 miles de la latitude, puis de les filtrer pour les valeurs dans les 3 miles de la longitude. Si je comprends le fonctionnement du système sous-jacent, vous ne pouvez utiliser qu'un seul index (INDEX) par table. Par conséquent, l'index sur lat ou long est sans valeur.
Si vous disposiez d'une grande quantité de données, pourrait accélérer les choses pour donner à chaque 1x1 mile un ID logique unique, puis créer une restriction supplémentaire sur le SELECT qui (area = "23234/34234" OR area = "23235/34234" OR ...) pour tous les carrés autour de votre point, puis forcez la base de données à utiliser cet index plutôt que les valeurs lat et long. Vous filtrerez alors beaucoup moins de kilomètres carrés de données.
Si vous vous en tenez à votre approche actuelle, vous devriez apporter un changement: Plutôt que d’indexer séparément géolat et géolong, vous devriez avoir un index composite:
KEY `geolat_geolng` (`geolat`, `geolng`),
Actuellement, votre requête ne tire parti que de l'un des deux index.
Vous pouvez envisager de créer une table séparée 'GeoLocations' qui a une clé primaire de 'geolat', 'geolng') et une colonne qui contient le home_id si cette géolocalisation particulière a une maison. Cela devrait permettre à l'optimiseur de rechercher une plage de géolocalisations qui seront triées sur le disque pour obtenir une liste de home_ids. Vous pouvez ensuite effectuer une jointure avec votre table 'homes' pour trouver des informations sur ces home_ids.
CREATE TABLE IF NOT EXISTS `GeoLocations` (
`geolat` decimal(10,6) NOT NULL,
`geolng` decimal(10,6) NOT NULL,
`home_id` int(10) NULL
PRIMARY KEY (`geolat`,`geolng`)
);
SELECT GL.home_id
FROM GeoLocations GL
INNER JOIN Homes H
ON GL.home_id = H.home_id
WHERE GL.geolat between X and Y
and GL.geolng between X and Y