Recherche géospatiale Elasticsearch avec ES|QL

Recherche géospatiale dans le langage de requête Elasticsearch (ES|QL). Elasticsearch possède de puissantes fonctionnalités de recherche géospatiale, qui sont désormais intégrées à ES|QL pour une facilité d'utilisation et une familiarité avec l'OGC considérablement améliorées.

Passez à la pratique avec Elasticsearch : explorez nos notebooks d’exemples, lancez un essai gratuit sur le cloud ou testez Elastic dès maintenant sur votre machine locale.

Elasticsearch dispose depuis de nombreuses années de puissantes fonctionnalités de recherche et d'analyse géospatiales, mais l'API était très différente de celle à laquelle les utilisateurs de SIG étaient habitués. L'année dernière, nous avons ajouté le langage d'interrogation ES|QL, un langage d'interrogation par pipeline aussi facile, voire plus facile, que SQL. Il est particulièrement adapté aux cas d'utilisation de la recherche, de la sécurité et de l'observabilité dans lesquels Elastic excelle. Nous avons également ajouté la prise en charge de la recherche et de l'analyse géospatiales dans ES|QL, ce qui facilite grandement son utilisation, en particulier pour les utilisateurs issus des communautés SQL ou GIS.

Elasticsearch 8.12 et 8.13 ont apporté une prise en charge de base des types géospatiaux à ES|QL. Cette fonction a été considérablement améliorée par l'ajout de capacités de recherche géospatiale dans la version 8.14. Plus important encore, ce support a été conçu pour se conformer étroitement à la norme Simple Feature Access de l'Open Geospatial Consortium (OGC) utilisée par d'autres bases de données spatiales comme PostGIS, ce qui le rend beaucoup plus facile à utiliser pour les experts SIG familiarisés avec ces normes.

Dans ce blog, nous vous montrerons comment utiliser ES|QL pour effectuer des recherches géospatiales, et comment il se compare aux équivalents SQL et Query DSL. Nous vous montrerons également comment utiliser ES|QL pour effectuer des jointures spatiales et comment visualiser les résultats dans Kibana Maps. Notez que toutes les fonctionnalités décrites ici figurent dans l'aperçu technique de "" , et nous serions ravis de recevoir vos commentaires sur la manière dont nous pouvons les améliorer.

Recherche de données géospatiales

Commençons par un exemple de requête :

Cette opération permet de rechercher tous les polygones de limite de ville qui recoupent un polygone de recherche rectangulaire autour de l'aéroport international de Sanya Phoenix (SYX).

Dans un échantillon de données d'aéroports, de villes et de limites de villes, cette recherche trouve le polygone d'intersection et renvoie les champs souhaités à partir du document correspondant :

abbrevaéroportrégionvilleville_location
SYXSanya Phoenix Int'l天涯区SanyaPOINT(109.5036 18.2533)

C'était facile ! Comparez maintenant cela au DSL Elasticsearch Query classique pour la même requête :

Les deux requêtes sont raisonnablement claires dans leur intention, mais la requête ES|QL ressemble beaucoup à SQL. La même requête dans PostGIS se présente comme suit :

Reprenez l'exemple de ES|QL. C'est un peu la même chose, non ?

Nous avons constaté que les utilisateurs existants de l'API Elasticsearch trouvent ES|QL beaucoup plus facile à utiliser. Nous pensons que les utilisateurs de SQL, en particulier ceux de Spatial SQL, trouveront que ES|QL est très proche de ce qu'ils ont l'habitude de voir.

Pourquoi pas SQL ?

Qu'en est-il d'Elasticsearch SQL ? Il existe depuis un certain temps et possède des fonctions géospatiales. Cependant, Elasticsearch SQL a été écrit comme une enveloppe au-dessus de l'API de requête originale, ce qui signifie que seules les requêtes qui pouvaient être transposées vers l'API originale étaient prises en charge. ES|QL n'a pas cette limitation. Le fait qu'il s'agisse d'une pile entièrement nouvelle permet de nombreuses optimisations qui n'étaient pas possibles avec SQL. Nos benchmarks montrent qu'ES|QL est très souvent plus rapide que l'API Query, en particulier avec les agrégations !

Différences avec SQL

Il ressort clairement de l'exemple précédent que ES|QL est quelque peu similaire à SQL, mais qu'il existe des différences importantes. Par exemple, ES|QL est un langage d'interrogation par pipeline, qui commence par une commande source telle que FROM et enchaîne toutes les commandes suivantes à l'aide du caractère de pipeline |. Il est ainsi très facile de comprendre comment chaque commande reçoit un tableau de données et effectue une action sur ce tableau, comme le filtrage avec WHERE, l'ajout de colonnes avec EVAL, ou l'exécution d'agrégations avec STATS. Plutôt que de commencer par SELECT pour définir les colonnes de sortie finales, il peut y avoir une ou plusieurs commandes KEEP, la dernière spécifiant les résultats de sortie finaux. Cette structure simplifie le raisonnement sur la requête.

Si l'on se concentre sur la commande WHERE dans l'exemple ci-dessus, on constate qu'elle ressemble beaucoup à l'exemple PostGIS :

ES|QL

PostGIS

Outre la différence entre les caractères de citation des chaînes, la plus grande différence réside dans la façon dont nous transformons la chaîne de caractères en un type spatial. Dans PostGIS, nous utilisons le suffixe ::geometry, tandis que dans ES|QL, nous utilisons le suffixe ::geo_shape. En effet, ES|QL fonctionne au sein d'Elasticsearch, et l'opérateur de conversion de type :: peut être utilisé pour convertir une chaîne en l'un des types ES|QL pris en charge, dans ce cas, un geo_shape. En outre, les types geo_shape et geo_point dans Elasticsearch impliquent le système de coordonnées spatiales connu sous le nom de WGS84, plus communément désigné par le numéro SRID 4326. Dans PostGIS, cela doit être explicite, d'où l'utilisation du préfixe SRID=4326; pour la chaîne WKT. Si ce préfixe est supprimé, le SRID sera fixé à 0, ce qui est plus proche des types Elasticsearch cartesian_point et cartesian_shape, qui ne sont pas liés à un système de coordonnées spécifique.

ES|QL et PostGIS fournissent également une syntaxe de fonction de conversion de type :

ES|QL

PostGIS

Fonctions de l'OGC

Elasticsearch 8.14 introduit les quatre fonctions de recherche spatiale de l'OGC suivantes :

ES|QLPostGISDescription
ST_INTERSECTSST_IntersectsRetourne true si deux géométries se croisent, et false dans le cas contraire.
ST_DISJOINTST_DisjointRetourne true si deux géométries ne se croisent pas, et false dans le cas contraire. L'inverse de ST_INTERSECTS.
ST_CONTAINSST_ContainsRetourne true si une géométrie en contient une autre, et false sinon.
ST_WITHINST_WithinRetourne true si une géométrie est à l'intérieur d'une autre, et false dans le cas contraire. L'inverse de ST_CONTAINS.

Ces fonctions se comportent de manière similaire à leurs homologues PostGIS et sont utilisées de la même manière. Par exemple, ST_INTERSECTS renvoie un message vrai si deux géométries se croisent et un message faux dans le cas contraire. Si vous suivez les liens de documentation dans le tableau ci-dessus, vous remarquerez que tous les exemples ES|QL se trouvent dans une clause WHERE après une clause FROM, alors que tous les exemples PostGIS utilisent des géométries littérales. En fait, les deux plates-formes permettent d'utiliser les fonctions dans n'importe quelle partie de la requête où elles sont utiles.

Le premier exemple dans la documentation PostGIS pour ST_INTERSECTS est le suivant :

L'équivalent en ES|QL serait le suivant :

Notez que nous n'avons pas spécifié le SRID dans l'exemple de PostGIS. En effet, dans PostGIS, lorsque l'on utilise le type geometry, tous les calculs sont effectués sur un système de coordonnées planaires et, par conséquent, si les deux géométries ont le même SRID, le SRID n'a pas d'importance. Dans Elasticsearch, c'est également le cas pour la plupart des fonctions, mais il existe des exceptions où geo_shape et geo_point utilisent des calculs sphériques, comme nous le verrons dans le prochain blog sur la recherche de distance spatiale.

ES|QL polyvalence

Nous avons donc vu ci-dessus des exemples d'utilisation de fonctions spatiales dans les clauses WHERE et dans les commandes ROW. Où cela aurait-il un sens ? Un endroit très utile est la commande EVAL. Cette commande permet d'évaluer une expression et de renvoyer le résultat. Par exemple, déterminons si les centroïdes de tous les aéroports regroupés par nom de pays se trouvent à l'intérieur d'une frontière délimitant le pays :

Les résultats sont attendus, les centroïdes des aéroports britanniques sont situés à l'intérieur de la frontière britannique, mais pas à l'intérieur de la frontière islandaise, et vice versa :

centroïdeComptein_uken_payswithin_ukà l'intérieur de la patrie
POINT (-21.946634463965893 64.13187285885215)1fauxvraifauxvrai
POINT (-2.597342072712148 54.33551226578214)17vraifauxvraifaux
POINT (0.04453958108176276 23.74658354606057)873fauxfauxfauxfaux

En fait, ces fonctions peuvent être utilisées dans n'importe quelle partie de la requête où leur signature a un sens. Ils prennent tous deux arguments, qui sont soit un objet spatial littéral, soit un champ d'un type spatial, et renvoient tous une valeur booléenne. Il est important de noter que le système de référence des coordonnées (CRS) des géométries doit correspondre, sinon une erreur sera renvoyée. Cela signifie que vous ne pouvez pas mélanger les types geo_shape et cartesian_shape dans le même appel de fonction. Vous pouvez toutefois mélanger les types geo_point et geo_shape, car le type geo_point est un cas particulier du type geo_shape, et tous deux partagent le même système de référence de coordonnées. La documentation relative à chacune des fonctions définies ci-dessus énumère les combinaisons de types prises en charge.

En outre, chaque argument peut être un littéral spatial ou un champ, dans l'un ou l'autre ordre. Vous pouvez même spécifier deux champs, deux littéraux, un champ et un littéral, ou un littéral et un champ. La seule condition est que les types soient compatibles. Par exemple, cette requête compare deux champs dans le même index :

La requête demande essentiellement si la ville est située à l'intérieur des limites de la ville, ce qui devrait généralement être le cas, mais il y a toujours des exceptions :

cardinalitéComptedans_la_ville
peu29faux
nombreux740vrai

Une question bien plus intéressante serait de savoir si l'emplacement de l'aéroport se trouve à l'intérieur des limites de la ville desservie par l'aéroport. Cependant, l'emplacement de l'aéroport se trouve dans un index différent de celui qui contient les limites de la ville. Il faut donc trouver une méthode pour interroger efficacement les données de ces deux index distincts et les mettre en corrélation.

Jointures spatiales

ES|QL ne prend pas JOIN en charge les commandes, mais vous pouvez réaliser un cas spécial de jointure à l'aide de la ENRICH commande, qui se comporte de la même manière qu'une "jointure gauche" en SQL. Cette commande s'apparente à une "jointure gauche" en SQL, vous permettant d'enrichir les résultats d'un index avec des données d'un autre index sur la base d'une relation spatiale entre les deux ensembles de données.

Par exemple, enrichissons les résultats d'un tableau d'aéroports avec des informations supplémentaires sur la ville qu'ils desservent en trouvant la limite de la ville qui contient l'emplacement de l'aéroport, puis effectuons quelques statistiques sur les résultats :

Cette méthode permet d'obtenir les 5 régions qui comptent le plus d'aéroports, ainsi que le centroïde de tous les aéroports qui ont des régions correspondantes et la longueur de la représentation WKT des limites des villes à l'intérieur de ces régions :

centroïdeComptemin_wktmax_wktrégion
POINT (-32.56093470960719 32.598117914802714)90207207nul
POINT (-73.94515332765877 40.70366442203522)9438438Ville de New York
POINT (-83.10398317873478 42.300230911932886)9473473Détroit
POINT (-156.3020245861262 20.176383580081165)5307803Hawaï
POINT (-73.88902732171118 45.57078813901171)4837837Montréal

Alors, que s'est-il vraiment passé ? Où s'est produite la supposée JOIN? L'essentiel de la question réside dans la commande ENRICH:

Cette commande demande à Elasticsearch d'enrichir les résultats extraits de l'index airports et d'effectuer une jointure intersects entre le champ city_location de l'index original et le champ city_boundary de l'index airport_city_boundaries, que nous avons utilisé dans quelques exemples plus tôt. Mais certaines de ces informations ne sont pas clairement visibles dans cette requête. Ce que nous voyons, c'est le nom d'une politique d'enrichissement city_boundaries, et l'information manquante est encapsulée dans cette définition de politique.

Ici, nous pouvons voir qu'il effectuera une requête geo_match (intersects est la valeur par défaut), que le champ à comparer est city_boundary et que les champs enrich_fields sont les champs que nous voulons ajouter au document d'origine. L'un de ces champs, le region, a été utilisé comme clé de regroupement pour la commande STATS, ce que nous n'aurions pas pu faire sans cette capacité de "jointure à gauche". Pour plus d'informations sur les politiques d'enrichissement, voir la documentation d'enrichissement. En lisant ces documents, vous remarquerez qu'ils décrivent l'utilisation des index d'enrichissement pour enrichir les données au moment de l'indexation, en configurant les pipelines d'ingestion. Cela n'est pas nécessaire pour ES|QL, car la commande ENRICH fonctionne au moment de la requête. Il suffit de préparer l'index d'enrichissement avec les données et la politique d'enrichissement nécessaires, puis d'utiliser la commande ENRICH dans vos requêtes ES|QL.

Vous pouvez également remarquer que la région la plus fréquemment trouvée est null. Qu'est-ce que cela peut signifier ? Rappelez-vous que j'ai comparé cette commande à une "jointure gauche" en SQL, ce qui signifie que si aucune limite de ville correspondante n'est trouvée pour un aéroport, l'aéroport est toujours renvoyé, mais avec les valeurs null pour les champs de l'index airport_city_boundaries. Il s'avère que 89 aéroports n'ont pas trouvé de correspondance avec city_boundary, et un aéroport avec une correspondance où le champ region était null. Cela a conduit à un décompte de 90 aéroports sans region dans les résultats. Un autre détail intéressant est la nécessité de la commande MV_EXPAND. Cela est nécessaire car la commande ENRICH peut renvoyer plusieurs résultats pour chaque ligne d'entrée, et MV_EXPAND permet de séparer ces résultats en plusieurs lignes, une pour chaque résultat. Cela explique également pourquoi "Hawaii" affiche des résultats différents de min_wkt et max_wkt: il y avait plusieurs régions portant le même nom mais ayant des limites différentes.

Cartes Kibana

Kibana a ajouté la prise en charge de Spatial ES|QL dans l'application Maps. Cela signifie que vous pouvez désormais utiliser ES|QL pour rechercher des données géospatiales dans Elasticsearch et visualiser les résultats sur une carte.

Il existe une nouvelle option de couche dans le menu d'ajout de couches, appelée "ES|QL". Comme toutes les fonctions géospatiales décrites jusqu'à présent, il s'agit d'un aperçu technique "" . Cette option permet d'ajouter une couche à la carte en fonction des résultats d'une requête ES|QL. Par exemple, vous pouvez ajouter une couche à la carte qui montre tous les aéroports du monde.

Vous pouvez également ajouter une couche qui montre les polygones de l'index airport_city_boundaries ou, mieux encore, la requête complexe ENRICH ci-dessus qui génère des statistiques sur le nombre d'aéroports dans chaque région.

Et ensuite ?

Vous avez peut-être remarqué que dans deux des exemples ci-dessus, nous avons inséré une autre fonction spatiale ST_CENTROID_AGG. Il s'agit d'une fonction d'agrégation utilisée dans la commande STATS et de la première des nombreuses fonctions d'analyse spatiale que nous prévoyons d'ajouter à ES|QL. Nous en parlerons sur notre blog lorsque nous en aurons plus à montrer !

Avant cela, nous souhaitons vous parler d'une fonctionnalité particulièrement intéressante sur laquelle nous avons travaillé : la possibilité d'effectuer des recherches spatiales par distance, l'une des fonctionnalités de recherche spatiale les plus utilisées d'Elasticsearch. Pouvez-vous imaginer à quoi pourrait ressembler la syntaxe des recherches à distance ? Peut-être similaire à une fonction de l'OGC ? Restez à l'écoute du prochain blog de cette série pour le découvrir !

Alerte au spoiler : Elasticsearch 8.15 vient d'être publié, et la recherche de distance spatiale avec ES|QL est incluse !

Pour aller plus loin

Prêt à créer des expériences de recherche d'exception ?

Une recherche suffisamment avancée ne se fait pas avec les efforts d'une seule personne. Elasticsearch est alimenté par des data scientists, des ML ops, des ingénieurs et bien d'autres qui sont tout aussi passionnés par la recherche que vous. Mettons-nous en relation et travaillons ensemble pour construire l'expérience de recherche magique qui vous permettra d'obtenir les résultats que vous souhaitez.

Jugez-en par vous-même