Introduction: Parfois, vous devrez peut-être exécuter des fonctions géospatiales au sein de votre application, telles que la cartographie des emplacements des utilisateurs ou l'analyse de données géographiques. Il existe de nombreuses bibliothèques spécifiques au langage disponibles pour ces tâches, telles que GDAL, Shapely et Geopandas for Python. Alternativement, la fonctionnalité géospatiale peut être mise en œuvre via des bases de données ; par exemple, vous pouvez utiliser l'extension PostGIS avec une base de données relationnelle comme PostgreSQL, ou tirer parti de la prise en charge native des types de données spatiales dans une base de données distribuée comme Azure CosmosDB. Toutefois, si votre stockage back-end, tel que Redis ou Google Spanner, ne prend pas en charge de manière native les requêtes spatiales et que vous devez gérer des requêtes géospatiales à grande échelle, cet article est conçu pour vous. Quelles sont mes options ? Vous pouvez toujours créer un autre microservice pour gérer les données spatiales, mais cette option implique souvent la surcharge liée à la maintenance d'une application supplémentaire. Une autre approche consiste à utiliser des bibliothèques de géo-indexation comme S2 et H3. S2, développé par Google, est basé sur la courbe de Hilbert, tandis que H3, développé par Uber, est basé sur un système de grille globale géodésique discrète. S2 et H3 partagent de nombreuses similitudes : tous deux divisent une région donnée en cellules et utilisent des entiers de 64 bits pour indexer ces cellules. Cependant, la principale différence réside dans la forme des cellules ; S2 utilise des cellules de forme carrée, tandis que H3 utilise des cellules de forme hexagonale. Pour certaines applications, H3 peut offrir de meilleures performances. Cependant, dans l’ensemble, l’une ou l’autre bibliothèque devrait suffire. Dans cet article, nous utiliserons S2, mais vous pouvez exécuter des fonctions similaires en utilisant H3. Concepts de base de la bibliothèque Google S2 Cellules : S2 divise la sphère en cellules, chacune avec un identifiant unique de 64 bits. Niveaux de cellules : la hiérarchie permet différents niveaux de détail, des grandes régions aux petites zones précises. Chaque niveau représente une résolution différente : Niveau 0 : Les plus grandes cellules, couvrant une partie importante de la surface terrestre. Niveaux supérieurs : les cellules sont progressivement subdivisées en quadrants plus petits. Par exemple, les cellules de niveau 1 sont chacune divisées en quatre cellules de niveau 2, et ainsi de suite. Résolution et zone : des niveaux plus élevés correspondent à des résolutions plus fines et à des zones de cellules plus petites. Cette hiérarchie permet une indexation et des requêtes précises à différents niveaux de détail. Le tableau ci-dessous montre différents niveaux de cellules ainsi que leurs zones correspondantes. niveau superficie minimale superficie maximale superficie moyenne unités Nombre de cellules 00 85011012.19 85011012.19 85011012.19 km2 6 01 21252753.05 21252753.05 21252753.05 km2 24 02 4919708.23 6026521.16 5313188.26 km2 96 03 1055377.48 1646455.50 1328297.07 km2 384 04 231564.06 413918.15 332074.27 km2 1536 05 53798.67 104297.91 83018.57 km2 6K 06 12948.81 26113.30 20754.64 km2 24K 07 3175.44 6529.09 5188.66 km2 98 Ko 08 786.20 1632.45 1297.17 km2 393 Ko 09 195,59 408.12 324.29 km2 1573K dix 48,78 102.03 81.07 km2 6M 11 12h18 25.51 20.27 km2 25M 12 3.04 6.38 5.07 km2 100M 13 0,76 1,59 1.27 km2 402M 14 0,19 0,40 0,32 km2 1610M 15 47520.30 99638.93 79172.67 m2 6B 16 11880.08 24909.73 19793.17 m2 25B 17 2970.02 6227.43 4948.29 m2 103B 18 742,50 1556.86 1237.07 m2 412B 19 185,63 389.21 309.27 m2 1649B 20 46.41 97h30 77.32 m2 7T 21 11h60 24h33 19h33 m2 26T 22 2,90 6.08 4,83 m2 105T 23 0,73 1,52 1.21 m2 422T 24 0,18 0,38 0,30 m2 1689T 25 453.19 950.23 755.05 cm2 7e15 26 113h30 237,56 188,76 cm2 27e15 27 28h32 59.39 47.19 cm2 108e15 28 7.08 14h85 11h80 cm2 432e15 29 1,77 3,71 2,95 cm2 1729e15 30 0,44 0,93 0,74 cm2 7e18 D'après le tableau fourni, il est évident que vous pouvez obtenir une précision de cartographie allant jusqu'à 0,44 cm^2 en utilisant S2. Dans chaque carré d'une cellule S2, il existe une cellule enfant qui partage le même parent, indiquant une structure hiérarchique. Le niveau de la cellule peut être une valeur statique (même niveau appliqué à toutes les cellules) ou peut être dynamique où S2 décide quelle résolution fonctionne le mieux. Calcul des voisins les plus proches Commençons par un exemple. Considérez que nous écrivons une application qui fournit des fonctionnalités de type service de proximité pour la région de Seattle. Nous souhaitons renvoyer une liste de cafés dans le voisinage donné. Pour réaliser ces opérations, nous diviserons cette tâche en 4 sous-tâches : Chargement de la carte de Seattle Visualisez les cellules S2 sur la carte de Seattle Stockez quelques emplacements de cafés dans la base de données Rechercher les cafés les plus proches Chargement de la carte de Seattle Pour charger une carte Google, nous utiliserions la bibliothèque gmplot. Cette bibliothèque nécessite une clé API Google Maps pour être chargée. Pour générer la clé API, suivez les instructions . ici import gmplot import const # plot seattle with zoom level 13 gmap = gmplot.GoogleMapPlotter(47.6061, -122.3328, 13, apikey=const.API_KEY) # Draw the map to an HTML file: gmap.draw('map.html') Le code ci-dessus génère un fichier map.html comme indiqué ci-dessous : Visualisez les cellules S2 sur la carte de Seattle Maintenant que nous avons la carte, dessinons quelques cellules S2 pour les cartes : from s2 import * import gmplot # plot seattle with zoom level 13 gmap = gmplot.GoogleMapPlotter(47.6061, -122.3328, 13, apikey=const.API_KEY) areatobeplotted = [ (47.64395531736767,-122.43597221319135), (47.51369277846956,-122.43597221319135), (47.51369277846956,-122.24156866779164), (47.64395531736767,-122.24156866779164), (47.64395531736767,-122.43597221319135) ] region_rect = S2LatLngRect( S2LatLng.FromDegrees(47.51369277846956,-122.43597221319135), S2LatLng.FromDegrees(47.64395531736767, -122.24156866779164)) coverer = S2RegionCoverer() coverer.set_min_level(8) coverer.set_max_level(15) covering = coverer.GetCovering(region_rect) geoms = 0 for cellid in covering: new_cell = S2Cell(cellid) vertices = [] for i in range(0, 4): vertex = new_cell.GetVertex(i) latlng = S2LatLng(vertex) vertices.append((latlng.lat().degrees(), latlng.lng().degrees())) gmap.polygon(*zip(*vertices), face_color='pink', edge_color='cornflowerblue', edge_width=5) geoms+=1 gmap.polygon(*zip(*areatobeplotted), face_color='red', edge_color='green', edge_width=5) print(f"Total Geometries: {geoms}") gmap.draw('/tmp/map.html') Output: Total Geometries: 273 Dans le code ci-dessus, nous centrons d'abord le traceur Google Map autour de la région de Seattle. Dans , nous initialisons le couvreur de région pour avoir des niveaux dynamiques compris entre un niveau minimum de 8 et un niveau maximum de 15. Cela permet à S2 d'ajuster dynamiquement toutes les cellules dans des tailles de cellules spécifiques pour un ajustement optimal. La méthode renvoie le revêtement d'un rectangle autour de la région de Seattle. S2RegionCoverer GetCovering Ensuite, nous parcourons chaque cellule, calculons les sommets des cellules et les traçons sur la carte. Nous maintenons le nombre de cellules générées à environ 273. Enfin, nous traçons le rectangle d'entrée en rouge. Ce code tracera les cellules S2 sur la carte de Seattle dans , comme indiqué ci-dessous : /tmp/map.html Stocker quelques emplacements de cafés dans la base de données Générons une base de données de cafés avec leurs identifiants de cellules S2. Vous pouvez stocker ces cellules dans n'importe quelle base de données de votre choix. Pour ce tutoriel, nous utiliserons une base de données SQLite. Dans l'exemple de code ci-dessous, nous nous connectons à la base de données SQLite pour créer une table avec 3 champs , et . CoffeeShops Id name cell_id Semblable à l'exemple précédent, nous utilisons pour calculer les cellules mais cette fois, nous utilisons un niveau fixe pour tracer les points. Enfin, l'ID calculé est converti en chaîne et stocké dans la base de données. S2RegionCoverer import sqlite3 from s2 import S2CellId,S2LatLng,S2RegionCoverer # Connect to SQLite database conn = sqlite3.connect('/tmp/sqlite_cells.db') cursor = conn.cursor() # Create a table to store cell IDs cursor.execute('''CREATE TABLE IF NOT EXISTS CoffeeShops ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, cell_id TEXT )''') coverer = S2RegionCoverer() # Function to generate S2 cell ID for a given latitude and longitude def generate_cell_id(latitude, longitude, level=16): cell=S2CellId(S2LatLng.FromDegrees(latitude, longitude)) return str(cell.parent(level)) # Function to insert cell IDs into the database def insert_cell_ids(name,lat,lng): cell_id = generate_cell_id(lat, lng) cursor.execute("INSERT INTO CoffeeShops (name, cell_id) VALUES (?, ?)", (name, cell_id)) conn.commit() # Insert cell IDs into the database insert_cell_ids("Overcast Coffee", 47.616656277302155, -122.31156460382837) insert_cell_ids("Seattle Sunshine", 47.67366852914391, -122.29051997415843) insert_cell_ids("Sip House", 47.6682364706238, -122.31328618043693) insert_cell_ids("Victoria Coffee",47.624408595334536, -122.3117362652041) # Close connection conn.close() À ce stade, nous disposons d'une base de données qui stocke les cafés ainsi que leurs identifiants de cellule, déterminés par la résolution sélectionnée pour le niveau de cellule. Requête des cafés les plus proches Enfin, recherchons des cafés dans la région du district universitaire. import sqlite3 from s2 import S2RegionCoverer,S2LatLngRect, S2LatLng # Connect to SQLite database conn = sqlite3.connect('/tmp/sqlite_cells.db') cursor = conn.cursor() # Function to query database for cells intersecting with the given polygon def query_intersecting_cells(start_x,start_y,end_x,end_y): # Create S2RegionCoverer region_rect = S2LatLngRect( S2LatLng.FromDegrees(start_x,start_y), S2LatLng.FromDegrees(end_x,end_y)) coverer = S2RegionCoverer() coverer.set_min_level(8) coverer.set_max_level(15) covering = coverer.GetCovering(region_rect) # Query for intersecting cells intersecting_cells = set() for cell_id in covering: cursor.execute("SELECT name FROM CoffeeShops WHERE cell_id >= ? and cell_id<=?", (str(cell_id.range_min()),str(cell_id.range_max()),)) intersecting_cells.update(cursor.fetchall()) return intersecting_cells # Query for intersecting cells intersecting_cells = query_intersecting_cells(47.6527847,-122.3286438,47.6782181, -122.2797203) # Print intersecting cells print("Intersecting cells:") for cell_id in intersecting_cells: print(cell_id[0]) # Close connection conn.close() Output: Intersecting cells: Sip House Seattle Sunshine Vous trouverez ci-dessous une représentation visuelle des cellules. Par souci de brièveté, le code de visualisation ci-dessous n’est pas ajouté. Étant donné que toutes les cellules enfants et parents partagent un préfixe, nous pouvons rechercher des plages de cellules comprises entre min et max pour obtenir toutes les cellules comprises entre ces deux valeurs. Dans notre exemple, nous utilisons le même principe pour interroger un café Conclusion: Dans cet article, nous avons montré comment utiliser la géo-indexation pour stocker et interroger des données géospatiales dans des bases de données qui ne prennent pas en charge les requêtes géospatiales. Cela peut être étendu à plusieurs cas d'utilisation tels que le calcul du routage entre 2 points ou l'obtention des voisins les plus proches. En règle générale, pour les requêtes de bases de données géo-indexées, vous devrez effectuer un post-traitement supplémentaire sur les données. Une attention particulière à la logique d'interrogation et de post-traitement est nécessaire pour garantir que nous ne submergeons pas le nœud. Les références: Traceur de cartes Google - https://github.com/gmplot/gmplot/wiki/GoogleMapPlotter Guide du développeur S2 - http://s2geometry.io/devguide/ SQLite - https://docs.python.org/3/library/sqlite3.html Azure Cosmos DB géospatial - https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/query/geospatial?tabs=javascript GDAL - https://gdal.org/index.html Shapley- https://shapely.readthedocs.io/en/stable/manual.html Géopandas - https://geopandas.org Posgis - https://postgis.net/ Clé Google - https://cloud.google.com/spanner?hl=en Redis - https://redis.io/