Introduction
Bibliothèque maintenant open-source proposée par Uber, la technologie H3 est intégrée de base sur les clusters Databricks depuis l’année dernière. Cette technologie a pour objectif de faciliter les calculs sur les données géospatiales en se basant sur une représentation de la Terre sous la forme d’une mosaïque d’hexagones. Dans cet article nous présenterons cet outil et ses avantages à travers le cas d’usage de détection de pattern à risques.
Databricks H3
La technologie H3 est une représentation de la Terre sous la forme d’une mosaïque d’hexagones et de pentagones de mêmes dimensions. 16 niveaux de résolution existent, formant un découpage de polygone de plus en plus petits. À la résolution 0 il y a 122 hexagones tandis qu’au niveau 16 il y en a 569 707 381 193 162. Les hexagones sont complétés par quelques pentagones à chaque résolution. Ces derniers étant en très grande infériorité numérique, on ne mentionnera dans cet article que les hexagones.
On peut alors représenter des points / géométries dans l’espace, raster ou même lidar en hexagone ou ensemble d’hexagones H3, de plus ou moins grande résolution. On perd donc en précision (on n’a plus un point précis mais une surface qui contient le point) mais l’avantage est de pouvoir maintenant travailler avec de simples index d’hexagones. Le temps de traitement est alors fortement réduit, notamment lors de traitements nécessitant des comparaisons entre plusieurs points :
- 1 point est dans une zone si son index fait partie du tableau d’index de la zone (= vérification si un ‘long’ est présent dans un tableau de ‘long’)
- 2 points sont proches si le retour de la fonction ‘h3_distance’ est petit (calculant le nombre d’hexagone séparant 2 index)
- … Même lorsque des résultats précis sont attendus et où il est nécessaire d’utiliser la position exacte (et non pas des index H3), il peut être utile de d’abord utiliser H3 pour faire un choix d’algorithme. Par exemple dans le calcul d’itinéraire, on peut utiliser H3 pour sélectionner des chemins grossiers et éliminer ceux qui ne sont pas pertinents, avant de ne faire tourner les algorithmes avancés sur le peu de chemins identifiés restants.
La technologie H3 peut tourner sur différents environnements mais il existe des avantages à l’utiliser avec Databricks. L’optimisation de la gestion des données de Databricks avec le z-ordering ou la co-location des données sont pertinents lorsqu’on travaille avec de tels index : si des index sont proches, les données seront stockées physiquement proches, et il est courant que les applications métiers touchent les données proches entre elles. De plus Databricks dispose d’un grand nombre de connecteurs et de partenaires dont il est simple de partager les données sous forme de table Delta, par exemple à des visualisateurs. Dans les cas d’usage suivants, nous utiliserons Kepler Gl pour visualiser les données car cela a l’avantage d’être une simple bibliothèque à installer sur le cluster.
Prérequis
Pour pouvoir utiliser H3 sur Databricks de la même façon que dans l’article, il est nécessaire d’avoir un Workspace Databricks configuré, ainsi qu’un cluster de version 12.0 au minimum et d’activer le Photon. Pour la visualisation, nous utilisons la bibliothèque KeplerGL qu’il faut installer sur le cluster (https://pypi.org/project/keplergl/). Enfin il vous faut des données géospatial. Des données de démonstration sont disponibles directement sur le Workspace (dans Data Explorer / samples)
Contexte
Une flotte d’engins de chantier est équipée de dispositifs IoT et remonte des informations dans un dataframe, comme leur position ou leur vitesse à un instant T. Les engins sont identifiés par la colonne ‘Serial’. Dans l’optique de réduire le nombre d’accidents, on souhaite identifier les zones où les risques sont les plus élevés. On considère dans ce cas d’usage qu’il s’agit des zones où plusieurs engins en mouvement sont proches et où leur vitesse est élevée. À partir de ces informations on peut construire une heatmap, et faire de la prévention de façon assez ciblée. Un autre objectif est de réussir à identifier les zones où les engins font des manœuvres. Dans notre cas on considèrera qu’il s’agit d’engins en mouvement mais à faible distance pendant un laps de temps relativement court.
Démonstration rapide de H3 et Kepler GL
Commençons par une utilisation simple des 2 outils que nous utiliserons tout au long de l’article : H3 et KeplerGL, en affichant sur une carte les positions de notre dataset, avec une échelle de couleur sur le nombre de points présents sur chaque hexagone. La première étape sera presque toujours de convertir la position de nos points en index d’hexagone H3. Pour cela on utilise h3_latlongash. Dans un souci de praticité on utilisera les variantes ‘string’ des fonctions H3 pour avoir directement nos retours sous ce format et utiliser facilement KeplerGL. Il est cependant plus performant de travailler avec des ‘long’.

On crée un dataframe en regroupant nos points sur leur hexagone respectif et les agrégeant avec un ‘count’. On les affiche à l’aide de la fonction ‘display_kepler’, en créant une map et en ajoutant des couches de données (ici notre dataframe agrégé).

On peut configurer l’affichage soit à l’aide d’un json lors de l’instanciation de la map (non décrit dans cet article), soit directement dans l’iFrame dans le panneau latéral. On y retrouve nos couches de données et, en cliquant dessus, choisir la palette de couleur utilisée ainsi que la variable dont la couleur dépend. Dans la suite de l’article, la palette de couleur utilisée est telle que bleu représente les valeurs faibles et marron les valeurs élevées.

Les détails de création et de configuration de la carte ne seront plus explicités dans la suite de l’article, mais les éventuelles différences avec cet exemple seront notifiées. Il en va de même pour la création des index H3 à partir de la latitude et longitude.
Nettoyage des données
On reprend notre dataframe d’origine pour éliminer les lignes incohérentes dû à la dispersion du GPS. En effet la précision des GPS n’est pas infaillible et pour un point qui ne bouge pas, on pourrait obtenir des positions différentes. Pour éliminer ces points, on convertit les positions en index H3 (comme dans l’exemple précédent), à la résolution la plus haute (15) car on ne souhaite pas éliminer des faux positifs (on considère que la dispersion du GPS n’excède pas quelques mètres). On choisit alors d’estimer que pour chaque point, si le point précédent et le point suivant ont le même index, alors le point n’a pas bougé. Donc si le point n’a pas le même index que le point précédent et le point suivant, il est erroné et est à supprimer.

Pour nos cas d’usage il ne nous est pas nécessaire d’avoir l’ensemble des points où les engins ne sont pas en mouvement. Ainsi on ne gardera que le premier et le dernier point de l’inactivité d’un engin. Si un point partage le même index que le point précédent, alors on le flag en ‘is_moving = False’. On filtre ensuite pour ne garder que la première et la dernière occurrence des points qui sont flaggés.

Enfin dans un souci de praticité on va « échantillonner » notre colonne timestamp sur la dizaine de seconde. Ainsi 2 points présents au même endroit, l’un à 13h45 et 01 seconde et l’autre à 13h45 et 08 secondes, seront considérés comme présent au même moment. On termine en réindexant sur une résolution de 14 car c’est la résolution qui nous intéresse. Cela nous permet de supprimer beaucoup de doublons.

Le dataframe en sortie de ce nettoyage sera appelé ‘clean_data_df’.
Détection des zones de danger
On choisit de définir une zone de danger telle qu’au moins 2 engins sont proches l’un de l’autre, à une vitesse élevée. On commence par créer pour chaque point la zone à partir de laquelle on considèrera qu’il croise un autre engin. Cette zone est l’ensemble des hexagones à une distance inférieur ou égale à 2 hexagones de distance du point.

Pour cela on utilise ‘h3_kringdistances’, qui retourne un tableau d’index, et on l’explode pour avoir une ligne par hexagone. Cette fonction retourne également la distance de l’hexagone périphérique de l’hexagone centrale. Ce nouveau dataframe ‘kr2_df’ ajoute donc 2 nouvelles colonnes : ‘kr_ring’ l’index de l’hexagone en périphérie et ‘distance’ la distance entre h3_index et ‘kr_ring’.

Pour connaitre les engins présents à proximité d’un autre (= sur un des hexagones en périphérie d’un autre camion), on fait une jointure entre notre dataframe d’origine et ‘kr2_df’. Nos conditions sont :
- 2 engins différents = ‘Serial’ différent
- À un même moment = ‘datetime10s’ identique
- À proximité d’un de l’autre = h3_index identique au kr_ring d’un autre engin

Ici j’obtiens un nouveau dataframe avec 2000 éléments. Pour plus de lisibilité sur la heatmap, on passe en résolution 11 en prenant les index des hexagones parents (avec ‘h3_toparent’). On va ensuite agréger les données sur cette colonne en normalisant la vitesse et la distance entre les points (pour que toutes les valeurs soient comprises entre 0 et 1), et on crée une valeur unique en multipliant nos 2 valeurs normées.

On affiche ce dernier dataframe en basant la couleur sur la colonne ‘ratio’. On pourrait ajouter un indice de fiabilité à nos résultats en ajoutant à notre agrégation le nombre de points que représentent chacune des tuiles.

Notre dataframe comportait déjà la vitesse à chaque instant, mais il serait possible de la calculer avec la fonction ‘h3_distance’. En effet elle retourne le nombre d’hexagones entre 2 index. Connaissant les timestamp à chacune des positions on peut recalculer la vitesse en chaque point.
Détection des zones de manœuvre
Ici on cherche les zones où les engins sont susceptibles d’être en train de faire des manœuvres, donc possiblement à proximité d’autres engins (une pelleteuse qui chargerait l’engin) ou des piétons (chef de chantier, ouvrier, etc.). Ces zones sont également des zones à risque et il peut être intéressant de les identifier. On considère qu’un engin de chantier manœuvre s’il se déplace dans un rayon proche pendant un laps de temps ni trop court (pour ne pas prendre en compte les phases d’accélération / ralentissement) ni trop long (pour ne pas prendre en compte des informations qui seraient en dehors de la manœuvre comme un aller-retour). On choisit de prendre un rayon de 1 hexagone de distance et un laps de temps de 2 minutes.

Ce dataframe est construit de la même façon que dans le cas d’usage précédent, et on fait une jointure avec le dataframe ‘clean_data_df’ sur les conditions citées au-dessus.

Les points qui nous intéressent sont ceux pour lesquels il y a eu beaucoup de match, ici on place la limite à 40. Une trop faible valeur ferait aussi ressortir les points où il y a eu moins de passage, donc moins de chance qu’il s’agisse d’une manœuvre. Au contraire une trop grande valeur réduirait le nombre de points et on risquerait de ne pas en avoir assez à observer. Comme dans le cas d’usage précédent, on choisit d’afficher les hexagones des index parents pour plus de lisibilité, et on agrège en gardant le nombre de points par hexagone.

En affichant ce dataframe sur la carte avec un gradient de couleur sur le ‘count’, on retrouve nos zones de manœuvre.

Pour aller plus loin
De nombreux cas d’usage peuvent découler de ce genre de dataset. On peut introduire la position de piétons, ou d’autres véhicules, qui n’aurait pas le même rayon de « zone de danger » (= distance utiliser dans ‘h3_kringdistances’), ce qui influerait sur les heatmap. On peut identifier quels engins font le plus d’aller-retour entre 2 points, lesquels passent plus de temps à faire des manœuvres, etc. afin d’orienter des formations ou d’identifier des bonnes ou mauvaises pratiques.
Conclusion
La grande force de H3 outre les performances de calcul est la simplicité d’utilisation. En peu de lignes de code on a réussi à identifier 2 patterns relativement complexes. En revanche cette simplicité fait perdre en précision, nous n’avons jamais utilisé les coordonnées exactes des engins. Notre cas d’usage s’accommode très bien de cette perte de précision mais ce n’est pas le cas de tous. Dans un prochain article nous utiliserons H3 de façon hybride pour ne pas perdre en précision tout en maintenant un haut niveau de performances, et en mettant en perspective avec d’autres solutions plus standard.