paint-brush
Comment effectuer des calculs SIG sur des bases de données non SIGpar@joellopes
1,658 lectures
1,658 lectures

Comment effectuer des calculs SIG sur des bases de données non SIG

par Joel Lopes9m2024/06/15
Read on Terminal Reader

Trop long; Pour lire

Si votre stockage backend ne prend pas en charge nativement les requêtes spatiales, cet article est conçu pour vous. 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 divise la sphère en cellules, chacune avec un identifiant unique de 64 bits. Des niveaux plus élevés correspondent à des résolutions plus fines et à des zones de cellules plus petites.
featured image - Comment effectuer des calculs SIG sur des bases de données non SIG
Joel Lopes HackerNoon profile picture

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 S2RegionCoverer , 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 GetCovering renvoie le revêtement d'un rectangle autour de la région de Seattle.


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 /tmp/map.html , comme indiqué ci-dessous :


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 CoffeeShops avec 3 champs Id , name et cell_id .


Semblable à l'exemple précédent, nous utilisons S2RegionCoverer 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.


 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: