Prise en charge géospatiale dans ElasticSearch

1. Introduction

Elasticsearch est surtout connu pour ses capacités de recherche en texte intégral, mais il offre également une prise en charge géospatiale complète.

Nous pouvons en savoir plus sur la configuration d'Elasticsearch et la mise en route dans cet article précédent.

Voyons comment nous pouvons enregistrer des données géographiques dans Elasticsearch et comment nous pouvons rechercher ces données à l'aide de requêtes géographiques.

2. Type de données géographiques

Pour activer les géo-requêtes, nous devons créer le mappage de l'index manuellement et définir explicitement le mappage de champ.

Le mappage dynamique ne fonctionnera pas lors de la configuration du mappage pour les types géographiques.

Elasticsearch propose deux façons de représenter les géodonnées:

  1. Paires latitude-longitude utilisant le type de champ de point géographique
  2. Forme complexe définie dans GeoJSON à l' aide du type de champ Geo-shape

Examinons plus en détail chacune des catégories ci-dessus:

2.1. Type de données Geo Point

Le type de champ Geo-point accepte les paires latitude-longitude qui peuvent être utilisées pour:

  • Trouver des points à une certaine distance du point central
  • Rechercher des points dans une boîte ou un polygone
  • Agréger les documents géographiquement ou en fonction de la distance du point central
  • Trier les documents par distance

Vous trouverez ci-dessous un exemple de mappage du champ pour enregistrer les données de point géographique:

PUT /index_name { "mappings": { "TYPE_NAME": { "properties": { "location": { "type": "geo_point" } } } } }

Comme nous pouvons le voir dans l'exemple ci-dessus, le type de champ de localisation est geo_point . Ainsi, nous pouvons maintenant fournir une paire latitude-longitude dans l' emplacement dans le champ emplacement.

2.2. Type de données Geo Shape

À la différence de géo points , forme géo fournit la fonctionnalité pour enregistrer et rechercher des formes complexes comme polygone et rectangle. Le type de données de forme géographique doit être utilisé lorsque nous voulons rechercher des documents contenant des formes autres que des points géographiques.

Jetons un coup d'œil à la cartographie pour le type de données de forme géographique:

PUT /index_name { "mappings": { "TYPE_NAME": { "properties": { "location": { "type": "geo_shape" } } } } }

Les versions récentes d'Elasticsearch décomposent la forme géographique fournie en un maillage triangulaire . Selon la documentation officielle, cela fournit une résolution spatiale presque parfaite.

3. Différentes façons de sauvegarder les données de point géographique

3.1. Objet Latitude Longitude

PUT index_name/index_type/1 { "location": { "lat": 23.02, "lon": 72.57 } }

Ici, l' emplacement du point géographique est enregistré en tant qu'objet avec la latitude et la longitude comme clés.

3.2. Paire Latitude Longitude

{ "location": "23.02,72.57" }

Ici, l' emplacement est exprimé sous la forme d'une paire latitude-longitude dans un format de chaîne simple. Veuillez noter, la séquence de latitude et de longitude au format chaîne.

3.3. Geo Hash

{ "location": "tsj4bys" }

Nous pouvons également fournir des données de géolocalisation sous forme de hachage géographique, comme indiqué dans l'exemple ci-dessus. Nous pouvons utiliser l'outil en ligne pour convertir la latitude-longitude en hachage géographique.

3.4. Tableau de latitude longitude

{ "location": [72.57, 23.02] }

La séquence latitude-longitude est inversée lorsque la latitude et la longitude sont fournies sous forme de tableau. Initialement, la paire latitude-longitude était utilisée à la fois dans une chaîne et dans un tableau, mais plus tard, elle a été inversée afin de correspondre au format utilisé par GeoJSON.

4. Différentes façons d'enregistrer les données de forme géographique

4.1. Point

POST /index/type { "location" : { "type" : "point", "coordinates" : [72.57, 23.02] } }

Ici, le type de forme géographique que nous essayons d'insérer est un point . Jetez un œil au champ de localisation , nous avons un objet imbriqué composé de type de champs et de coordonnées . Ces méta-champs aident Elasticsearch à identifier la forme géographique et ses données réelles.

4.2. LineString

POST /index/type { "location" : { "type" : "linestring", "coordinates" : [[77.57, 23.02], [77.59, 23.05]] } }

Ici, nous insérons une forme géographique de ligne. Les coordonnées de linestring se composent de deux points, à savoir le point de départ et le point final. La forme géographique LineString est très utile pour les cas d'utilisation de la navigation.

4.3. Polygone

POST /index/type { "location" : { "type" : "polygon", "coordinates" : [ [ [10.0, 0.0], [11.0, 0.0], [11.0, 1.0], [10.0, 1.0], [10.0, 0.0] ] ] } }

Here, we're inserting polygon geo shape. Please take a look at the coordinates in above example, first and last coordinates in polygon should always match i.e a closed polygon.

Elasticsearch also supports other GeoJSON structures as well. A complete list of other supported formats is as below:

  • MultiPoint
  • MultiLineString
  • MultiPolygon
  • GeometryCollection
  • Envelope
  • Circle

We can find examples of above-supported formats on the official ES site.

For all structures, the inner type and coordinates are mandatory fields. Also, sorting and retrieving geo shape fields are currently not possible in Elasticsearch due to their complex structure. Thus, the only way to retrieve geo fields is from the source field.

5. ElasticSearch Geo Query

Now, that we know how to insert documents containing geo shapes, let's dive into fetching those records using geo shape queries. But before we start using Geo Queries, we'll need following maven dependencies to support Java API for Geo Queries:

 org.locationtech.spatial4j spatial4j 0.7   com.vividsolutions jts 1.13   xerces xercesImpl   

We can search for the above dependencies in Maven Central repository as well.

Elasticsearch supports different types of geo queries and they are as follow:

5.1. Geo Shape Query

This requires the geo_shape mapping.

Similar to geo_shape type, geo_shape uses GeoJSON structure to query documents.

Below is a sample query to fetch all documents that fall within given top-left and bottom-right coordinates:

{ "query":{ "bool": { "must": { "match_all": {} }, "filter": { "geo_shape": { "region": { "shape": { "type": "envelope", "coordinates" : [[75.00, 25.0], [80.1, 30.2]] }, "relation": "within" } } } } } }

Here, relation determines spatial relation operators used at search time.

Below is the list of supported operators:

  • INTERSECTS – (default) returns all documents whose geo_shape field intersects the query geometry
  • DISJOINT – retrieves all documents whose geo_shape field has nothing in common with the query geometry
  • WITHIN – gets all documents whose geo_shape field is within the query geometry
  • CONTAINS – returns all documents whose geo_shape field contains the query geometry

Similarly, we can query using different GeoJSON shapes.

Java code for above query is as below:

Coordinate topLeft = new Coordinate(74, 31.2); Coordinate bottomRight = new Coordinate(81.1, 24); GeoShapeQueryBuilder qb = QueryBuilders.geoShapeQuery("region", new EnvelopeBuilder(topLeft, bottomRight).buildGeometry()); qb.relation(ShapeRelation.INTERSECTS);

5.2. Geo Bounding Box Query

Geo Bounding Box query is used to fetch all the documents based on point location. Below is a sample bounding box query:

{ "query": { "bool" : { "must" : { "match_all" : {} }, "filter" : { "geo_bounding_box" : { "location" : { "bottom_left" : [28.3, 30.5], "top_right" : [31.8, 32.12] } } } } } }

Java code for above bounding box query is as below:

QueryBuilders .geoBoundingBoxQuery("location").setCorners(31.8, 30.5, 28.3, 32.12);

Geo Bounding Box query supports similar formats as we have in geo_point data type. Sample queries for supported formats can be found on the official site.

5.3. Geo Distance Query

Geo distance query is used to filter all documents that come with the specified range of the point.

Here's a sample geo_distance query:

{ "query": { "bool" : { "must" : { "match_all" : {} }, "filter" : { "geo_distance" : { "distance" : "10miles", "location" : [31.131,29.976] } } } } }

And here's the Java code for above query:

QueryBuilders .geoDistanceQuery("location") .point(29.976, 31.131) .distance(10, DistanceUnit.MILES);

Similar to geo_point, geo distance query also supports multiple formats for passing location coordinates. More details on supported formats can be found at the official site.

5.4. Geo Polygon Query

Une requête pour filtrer tous les enregistrements contenant des points compris dans le polygone de points donné.

Jetons un coup d'œil à un exemple de requête:

{ "query": { "bool" : { "must" : { "match_all" : {} }, "filter" : { "geo_polygon" : { "location" : { "points" : [ {"lat" : 22.733, "lon" : 68.859}, {"lat" : 24.733, "lon" : 68.859}, {"lat" : 23, "lon" : 70.859} ] } } } } } }

Et au code Java pour cette requête:

List allPoints = new ArrayList(); allPoints.add(new GeoPoint(22.733, 68.859)); allPoints.add(new GeoPoint(24.733, 68.859)); allPoints.add(new GeoPoint(23, 70.859)); QueryBuilders.geoPolygonQuery("location", allPoints);

Geo Polygon Query prend également en charge les formats mentionnés ci-dessous:

  • lat-long comme tableau: [lon, lat]
  • lat-long comme une chaîne: "lat, lon"
  • hachage géographique

Le type de données geo_point est obligatoire pour utiliser cette requête.

6. Conclusion

Dans cet article, nous avons discuté des différentes options de cartographie pour l'indexation des données géographiques, c'est-à-dire geo_point et geo_shape .

Nous sommes également passés par différentes manières de stocker des géo-données et enfin, nous avons observé des géo-requêtes et une API Java pour filtrer les résultats à l'aide de requêtes géographiques.

Comme toujours, le code est disponible dans ce projet GitHub.