Pour lancer l'aperçu public de ArcGIS GeoAnalytics for Microsoft Fabric, nous publierons une série d'articles de blog explorant les fonctionnalités de GeoAnalytics for Fabric. Dans cet article, nous explorerons les bases du calcul / de la construction de nouvelles géométries telles que les tampons, les bacs spatiaux et les chemins les plus courts. Nous examinerons également le calcul des propriétés telles que la longueur et la surface de ces nouvelles géométries.
Importer GeoAnalytics for Fabric
Nous commencerons par un rappel sur la façon d'importer GeoAnalytics for Fabric et de commencer à l'utiliser. Pour importer GeoAnalytics for Fabric, il vous suffit d'ajouter import geoanalytics_fabric dans une cellule de votre notebook. Pour plus d'informations sur l'activation de la bibliothèque, consultez la documentation de démarrage.
Pour plus de commodité, nous recommandons également d'importer directement les fonctions géospatiales et de leur donner un alias facile à utiliser pour référence. Dans l'exemple ci-dessous, nous importons la bibliothèque et les fonctions avec un alias ST. Comme chacune des fonctions est nommée ST.<nom_fonction>, cela facilite leur référence et identifie qu'il s'agit de fonctions Spatial Type (ST). Cela correspondra également à la structure des exemples dans toute la documentation ArcGIS GeoAnalytics. Votre cellule ressemblera à ceci :
# import ArcGIS GeoAnalytics
import geoanalytics_fabric
import geoanalytics_fabric.sql.functions as ST
Par exemple, après l'importation, vous pourrez référencer la fonction ST_Buffer en utilisant ST.buffer au lieu de geoanalytics_fabric.sql.functions.buffer.
Calcul des nouvelles géométries
GeoAnalytics for Fabric contient un certain nombre d'opérations spatiales pour générer de nouvelles géométries basées sur l'entrée. Par exemple, nous explorerons quelques fonctions courantes dans cet article :
Bien que nous explorions quelques fonctions en détail, il y en a beaucoup d'autres dans la bibliothèque GeoAnalytics for Fabric qui pourraient vous intéresser pour vos projets analytiques. Vous pouvez explorer la gamme des fonctions dans la documentation des fonctions SQL.
Commençons par quelques tampons !
ST_Buffer & ST_GeodesicBuffer
ST_Buffer et ST_GeodesicBuffer créent des polygones représentant la zone inférieure ou égale à la distance spécifiée autour de la géométrie d'entrée (par exemple, la région dans un rayon de 10 miles autour d'un point). Ces fonctions créeront ce tampon autour de chaque entité dans la colonne géométrique.
Pour voir comment cela fonctionne, nous commencerons par créer un nouveau DataFrame avec quelques emplacements d'écoles dans la région de Boston. Nous allons tamponner ceux-ci pour visualiser la zone autour de chaque école. Voici un exemple de création d'un nouveau DataFrame avec quelques emplacements ponctuels :
# créer un DataFrame avec les emplacements de plusieurs écoles (en latitude et longitude)
df_schools = spark.createDataFrame([
(-71.0777, 42.3388, "Hurley K-8"),
(-71.0919,42.3176, "Higginson Elementary School"),
(-71.1016, 42.3164, "Mendell Elementary School")], ["longitude", "latitude", "name"])
# créer une colonne géométrique point ("geometry") à partir des valeurs latitude et longitude
df_schools = df_schools\
.withColumn("geometry", ST.point("longitude", "latitude", sr=4326))
Voici les résultats :

Maintenant, nous pouvons créer des tampons autour de ces écoles.
L'unité de distance pour le tampon est dans les mêmes unités que la géométrie d'entrée. Par exemple, si les données sont dans le système de coordonnées Web Mercator, les unités seront en mètres ; si vos données sont dans un système de coordonnées géographiques, les unités seront en degrés décimaux.
La fonction ST_GeodesicBuffer peut être utilisée pour créer des tampons utilisant une distance géodésique en mètres. Créons quelques tampons et examinons-les - et voyons comment le tampon planaire et le tampon géodésique diffèrent.
# créer des tampons exemples en utilisant ST_Buffer et ST_GeodesicBuffer
# Cet exemple génère un tampon de 0.5 _degrés décimaux_ (les unités pour les données d'entrée sont des valeurs latitude / longitude !)
# Il crée aussi un tampon géodésique de 500 mètres
df_schools = df_schools\
.withColumn("geometry_buffer", ST.buffer("geometry", 0.5))\
.withColumn("geometry_geodesic_buffer", ST.geodesic_buffer("geometry", 500))
Tout d'abord, regardons le tampon planaire - qui a été calculé dans les unités de la géométrie d'entrée (degrés décimaux). C'est un tampon de 0.5 degré décimal autour des trois écoles :

Notez que j'ai laissé les étiquettes des axes sur la carte afin que vous puissiez voir que cela s'affiche sur une carte de base en degrés décimaux.
Et maintenant, regardons les tampons géodésiques autour des écoles. Notez que cela s'affiche également sur une carte de base en degrés décimaux. Nous pouvons voir que les tampons sont beaucoup plus petits (500 m autour de chaque école), et qu'ils ne sont pas circulaires. Explorons pourquoi...

Il y a quelques points clés à noter lors du tracé de ces tampons :
- Les tampons sur le premier graphique semblent énormes, tandis que ceux du second sont beaucoup plus petits. Le "geometry_buffer" correspond à un demi-degré décimal, tandis que le "geometry_geodesic_buffer" fait seulement 500 mètres. Un degré décimal est significativement plus long que 500 mètres. À l'équateur, un degré de latitude ou longitude correspond à environ 111 miles !
- Les tampons sur le premier graphique ("geometry_buffer") ont une forme circulaire tandis que ceux du second graphique ("geometry_geodesic_buffer") ont une forme ovale. Pourquoi cela ? Ces deux cartes sont générées sur une carte en fonction des coordonnées de latitude et de longitude. Dans ces tracés, un degré de latitude est égal à un degré de longitude en longueur. Sur la Terre, la longueur d'un degré de latitude et de longitude diffère, un degré de longitude devenant de plus en plus court à mesure que l'on se rapproche des pôles. L'entrée Wikipédia sur <\/SPAN>Longitude<\/A> <\/SPAN>explique cela en détail si vous souhaitez plus d'informations. Lorsque les tampons géodésiques sont tracés sur cette projection cartographique particulière, ils apparaissent étirés en ovales, alors que sur la surface terrestre ils seraient circulaires.<\/LI><\/UL>Si nous devions tracer le tampon géodésique dans notre système de coordonnées local de Boston (SRID 2249), les tampons ressemblent aux cercles que nous attendrions.<\/P>Nous pouvons mettre à jour la carte lors du tracé en ajoutant un paramètre <\/SPAN>sr<\/FONT> <\/SPAN>(référence spatiale). Le code d'exemple ci-dessous le démontre. Plus d'informations sur les paramètres pour <\/SPAN>st.plot<\/FONT> <\/SPAN>sont disponibles dans la <\/SPAN>référence API<\/A>.<\/P>
<\/P>
# tracé du geometry_geodesic_buffer sur une carte avec référence spatiale 2249
df_schools\\
.st.plot(basemap="streets", geometry="geometry_geodesic_buffer", alpha=0.5, edgecolor="black", sr=2249);<\/code><\/pre><P> <\/P><P>En général, il est plus rapide d'exécuter<SPAN> <\/SPAN><A href="https:\/\/developers.arcgis.com\/geoanalytics-fabric\/sql-functions\/st_buffer\/\ target="_blank" rel="noopener nofollow noreferrer">ST_H3Bin</A><SPAN> </SPAN>fonction pour identifier le<SPAN> </SPAN><A href="https://h3geo.org/" target="_blank" rel="noopener nofollow noreferrer">H3 bin</A><SPAN> </SPAN>dans lequel chacun de nos points de demande de service se trouve. Nous utiliserons ensuite une fonction PySPark<SPAN> </SPAN><A href="https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.groupBy.html" target="_blank" rel="noopener nofollow noreferrer">GroupBy</A><SPAN> </SPAN>pour créer un DataFrame simplifié qui liste le nombre de points dans chaque bin.</P><P>La taille des bins H3 est basée sur une valeur de résolution. Pour cet exemple, nous utiliserons une résolution de 8, ce qui équivaut à une taille moyenne de bin hexagonal de 0,73 kilomètres carrés. Plus d'informations sur<SPAN> </SPAN><A href="https://h3geo.org/docs/core-library/restable" target="_blank" rel="noopener nofollow noreferrer">les résolutions des bins H3 peuvent être trouvées ici</A>.</P><P>Ajouter l'ID pour un bin est aussi simple que de créer une nouvelle colonne pour les données et d'utiliser la fonction bin pour le type de géométrie que vous souhaitez (hexagonal, carré ou H3) :</P><P> </P><pre class="lia-code-sample language-python"><code># ajouter une colonne avec l'_id_ du bin H3 dans lequel chaque point se trouve
df = df\
.withColumn("h3_bin", ST.h3_bin("geometry", 8))
La
fonction ST_H3Bin ajoute un id de bin à chaque enregistrement. Elle n'ajoute pas spécifiquement la géométrie, car il y aurait beaucoup de duplicatas de la géométrie (un pour chaque point à l'intérieur du bin) et il est plus efficace de travailler avec l'ID jusqu'à ce que nous ayons besoin des géométries pour l'analyse ou la visualisation. Nous pouvons voir ces nouveaux IDs de bin dans le tableau ci-dessous :
En utilisant le champ d'ID h3_bin, nous pouvons ensuite regrouper les valeurs dans notre tableau pour compter les enregistrements dans chaque bin. # grouper le DataFrame des demandes de service en utilisant l'ID du bin h3 et compter tous les enregistrements
df_by_bin = df.groupBy("h3_bin").count() Après regroupement, nous pouvons voir que chaque ID de bin a un compte du nombre de points qu'il contient. Après cela, nous pouvons convertir les IDs des bins en géométries et visualiser les résultats.
Voici comment nous ajoutons la géométrie du bin dans la table en utilisant la fonction ST_BinGeometry : # ajouter la géométrie du bin basée sur l'ID du bin en utilisant ST_BinGeometry
df_by_bin = df_by_bin\
.withColumn("geometry_bin", ST.bin_geometry("h3_bin")) Tracer les résultats montre maintenant où se trouve la plus grande concentration de demandes de service :
Passons maintenant au calcul des lignes et distances entre emplacements !ST_ShortestLine et ST_GeodesicLineST_ShortestLine renvoie une colonne linestring représentant la ligne la plus courte qui touche deux géométries, en utilisant le calcul de distance planaire. Cette fonction renvoie une seule ligne la plus courte s'il y en a plusieurs qui peuvent être calculées entre les emplacements.Pour créer une ligne la plus courte en utilisant des calculs de distance géodésique, utilisez ST_GeodesicShortestLine.À titre d'exemple, nous examinerons la ligne la plus courte entre le DataFrame des écoles que nous avons créé précédemment et les demandes de service à proximité. Shortest line utilisera deux colonnes de géométrie comme entrée, elles doivent donc être dans le même DataFrame.Dans les exemples de buffer ci-dessus, nous avons utilisé ST_Contains pour trouver tous les points à l'intérieur du polygone buffer.Dans cet exemple, nous utiliserons un autre calcul spatial appelé ST_DWithin (distance within) pour trouver tous les points dans une distance spécifiée des points d'école. Effectivement, cela peut donner le même résultat, cependant DWithin est souvent plus performant que la création d'un buffer et l'exécution d'une jointure spatiale. DWithin utilise par défaut des calculs planaires, donc nous devrions soit transformer la géométrie dans un système de coordonnées local, soit définir le paramètre pour utiliser des calculs géodésiques.Dans ce cas, nous utiliserons ST_Transform pour mettre à jour le système de coordonnées pour ce calcul. Nous ferons cela dans un seul calcul où nous joignons les écoles et les demandes de service et créons un nouveau DataFrame avec les résultats : # trouver tous les points dans un rayon de 500 pieds autour de chaque école
# utiliser une jointure spatiale pour associer tout point qui est dans la distance spécifiée autour de l'école
# puisque les jeux de données sont initialement en coordonnées latitude/longitude, nous transformons d'abord vers un système local
df_schools_requests = df.join(df_schools.select("name", F.col("geometry").alias("geometry_school")),
ST.dwithin(ST.transform("geometry", 2249), ST.transform("geometry_school", 2249), 500)) Maintenant que chaque école a la géométrie pour l'école et les points à proximité, nous pouvons utiliser ces deux géométries pour calculer la ligne la plus courte entre chaque point de demande de service et l'école. # calculer la ligne la plus courte entre chaque point et chaque école
df_schools_requests = df_schools_requests\
.withColumn("geometry_line", ST.shortest_line("geometry", "geometry_school")) Jetons un coup d'œil au résultat pour une école : # tracer les lignes connectrices entre une école et tous les points dans un rayon de 500 pieds autour de cet emplacement
df_schools_requests.filter(F.col("name") == "Hurley K-8").st.plot(basemap="streets", geometry="geometry_line", linewidth=0.5, alpha=0.7);
Pour les courtes distances, ST_ShortestLine est un bon choix, mais pour des distances plus longues, vous pourriez envisager d'utiliser ST_GeodesicShortestLine afin que la ligne créée représente le chemin le plus court sur la sphère. À titre d'exemple des différences entre la "ligne la plus courte" (calcul planaire) et la ligne géodésique, nous pouvons explorer la différence entre une ligne connectant Los Angeles et Londres. Dans cet exemple, la ligne géodésique (orange) apparaît courbée et plus longue dans cette projection, mais c'est en réalité la ligne la plus courte !
# ligne géodésique (geodesic) (en orange) vs. "ligne la plus courte" (planar) (en bleu)
# la ligne géodésique apparaît courbée et plus longue dans cette projection, mais c'est en réalité la ligne la plus courte !
# créer des points pour deux emplacements
data = [
("POINT (-118.2426 34.0549 )", "Los Angeles",
"POINT (0.1276 51.5072 )", "London")
]
# créer un DataFrame avec les deux emplacements
# convertir les emplacements du well-known text en géométrie
df_lines = spark.createDataFrame(data, ["wkt-origin", "city-origin", "wkt-dest", "city-dest"])\
.select("city-origin",
"city-dest",
ST.geom_from_text("wkt-origin", 4326).alias("geometry-origin"),
ST.geom_from_text("wkt-dest", 4326).alias("geometry-dest"))
# tracer les résultats pour comparer les deux lignes générées (géodésique vs. la plus courte)
myplt = df_lines.select(ST.geodesic_shortest_line("geometry-origin", "geometry-dest")).st.plot(basemap="light", figsize=(10,10), color="orange")
df_lines.select(ST.shortest_line("geometry-origin", "geometry-dest")).st.plot(ax=myplt);
Calculs de distance et de longueurRegardons maintenant la distance et la longueur pour ces lignes...ST_Distance et ST_GeodesicDistance calculeront la distance entre deux emplacements d'entrée. L'unité pour ST_Distance est la même que celle des géométries d'entrée. Pour ST_GeodesicDistance, la distance est calculée en mètres. - ST_Length et ST_GeodesicLength calculeront la longueur d'une entité d'entrée. L'unité pour ST_Length est la même que celle de la géométrie d'entrée. Pour ST_GeodesicLength, la longueur est calculée en mètres.
Nous allons examiner ces calculs en utilisant les lignes que nous avons générées précédemment reliant les emplacements des écoles aux demandes de service à proximité.
Avec le calcul de distance, nous pouvons également regarder une fonctionnalité particulièrement intéressante de la bibliothèque GeoAnalytics - la projection à la volée. Si vous souhaitez calculer une propriété ou une relation en utilisant deux géométries qui sont dans différents systèmes de coordonnées, la bibliothèque peut souvent réconcilier cette différence pour vous afin que vous n'ayez pas besoin de transformer les données.
- La colonne geometry (demandes de service) est dans le système de coordonnées Massachusetts State Plane (SRID 2249)
- La colonne geometry_school (emplacements des écoles) est en latitude et longitude WGS84 (SRID 4326)
Utilisons ces deux géométries et calculons une variété de distances et voyons comment GeoAnalytics peut travailler avec des géométries dans différents systèmes de coordonnées ! Dans la cellule ci-dessous, nous calculons :
- Distance entre les deux géométries sans transformer les coordonnées
- Distance entre les deux géométries avec une transformation pour convertir la geometry_school en Massachusetts State Plane (2249)
- Distance géodésique entre les deux géométries sans transformation
Nous allons également calculer la longueur de la ligne geometry_line que nous avons créée plus tôt pour connecter les deux emplacements ponctuels. Nous effectuerons ces calculs et afficherons les résultats comme exemple :
display(
df_schools_requests.select("geometry",
"geometry_school",
F.round(ST.distance("geometry", "geometry_school"), 2).alias("Distance (sans transformation)"),
F.round(ST.distance("geometry", ST.transform("geometry_school", 2249)),2).alias("Distance (avec transformation)"),
F.round(ST.geodesic_distance("geometry", "geometry_school"),2).alias("DistanceGéodésique (mètres)"),
"geometry_line",
F.round(ST.length("geometry_line"),2).alias("Longueur (pieds)"),
F.round(ST.geodesic_length("geometry_line"),2).alias("LongueurGéodésique (mètres)")
)
)

Cela montre comment vous pouvez effectuer ces calculs, même avec des géométries dans différents systèmes de coordonnées ! GeoAnalytics for Fabric est excellent pour aider à atténuer les différences dans les systèmes de coordonnées autant que possible.
Regardons maintenant une dernière fois certaines des propriétés que nous pouvons calculer et explorons l'aire.
ST_Area et ST_GeodesicArea
ST_Area - calcule une aire plane pour chaque géométrie. L'unité de l'aire est la même que celle des géométries d'entrée. Si vous calculez l'aire sur un jeu de données dans un système de coordonnées géographique, les unités seront en degrés décimaux carrés. Ce n'est pas une unité recommandée. Pour utiliser la fonction ST_Area, il est préférable d'utiliser un système de coordonnées projeté. Plus d'informations sont disponibles dans le document conceptuel sur les systèmes de coordonnées et transformations.
ST_GeodesicArea calcule l'aire pour chaque géométrie en mètres carrés, et nécessite qu'une référence spatiale soit définie sur la colonne de géométrie d'entrée.
Pour cet exemple, nous allons calculer les aires des états américains que nous avons lus depuis un service d'entités ArcGIS.
# Service d'entités des états américains depuis Esri Living Atlas of the World
# Pour plus d'informations sur ce jeu de données : https://www.arcgis.com/home/item.html?id=774019f31f8549c39b5c72f149bbe74e
fs_url = "https://services.arcgis.com/P3ePLMYs2RVChkJx/arcgis/rest/services/USA_Census_States/FeatureServer/0"
df_states = spark.read.format('feature-service').load(fs_url)
Avant de calculer l'aire, nous devrions vérifier le système de coordonnées des données afin que nous sachions plus sur les unités utilisées dans le système de coordonnées et décider si nous allons utiliser ST_Area ou ST_GeodesicArea. Nous pouvons faire cela avec la fonction get_spatial_reference()
df_states.st.get_spatial_reference()
Ce qui retourne des informations montrant que les données ont un SRID de 4326 (système de coordonnées WGS84)
SpatialReference(srid=4326, is_projected=False, unit='Degree', wkt='GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]')
Pour ce jeu de données, regardons les valeurs calculées par ST_Area et ST_GeodesicArea.
Dans l'exemple ci-dessous, nous affichons les données originales pour le nom de l'état et les miles carrés, ainsi que trois nouveaux champs calculés, tous arrondis à deux décimales pour une meilleure lisibilité en utilisant PySpark Round()
- aire utilisant ST_Area - puisque la référence spatiale est 4326 avec une unité en Degré, les résultats seront en degrés décimaux carrés, ce qui n'a pas vraiment de valeur applicable
- aire utilisant ST_Area sur une géométrie transformée utilisant la projection USA Contiguous Albers Equal Area (102003). Ce calcul retourne des mètres carrés, donc ils sont convertis en miles carrés dans l'exemple ci-dessous
- aire utilisant ST_GeodesicArea sur la géométrie originale. Ce calcul retourne des mètres carrés, donc ils sont convertis en miles carrés dans exemple ci-dessous
Les valeurs retournées sont toutes très proches les unes des autres en valeur, mais pas exactement les mêmes. Ces écarts sont dus à la distorsion dans le calcul de la surface pour les grandes régions (le document conceptuel principal sur les systèmes de coordonnées et transformations fournit plus d'informations sur la distorsion dans les projections), ainsi qu'à de légères variations de précision qui peuvent être introduites par le processus de projection et de transformation.
# Lors de la lecture des services de fonctionnalités, la géométrie se trouve généralement dans une colonne nommée "Shape." Cela peut être confirmé en regardant le tableau affiché ci-dessus
display(
df_states.select("STATE_NAME",
"SQMI",
F.round(ST.area("Shape"), 2).alias("Surface (degrés décimaux carrés"),
F.round(ST.area(ST.transform("Shape", 102003))/2.59e+6, 2).alias("Surface (sqmi)"),
F.round(ST.geodesic_area("Shape")/2.59e+6, 2).alias("Surface géodésique (sqmi)")
)
)

Conclusion
Nous espérons que cette introduction rapide à la création de nouvelles géométries et au calcul de leurs propriétés vous a été utile. Nous publierons du contenu supplémentaire pour vous aider à démarrer, alors revenez sur la Communauté pour en savoir plus ! Et s'il vous plaît, faites-nous savoir quel type de sujets vous aimeriez approfondir !