Геопространственная поддержка в ElasticSearch

Геопространственная поддержка в ElasticSearch

1. Вступление

Elasticsearch наиболее известен своими возможностями полнотекстового поиска, но также имеет полную геопространственную поддержку.

Мы можем узнать больше о настройке Elasticsearch и начале работы в этомprevious article.

Давайте посмотрим, как мы можем сохранять геоданные в Elasticsearch и как мы можем искать эти данные с помощью географических запросов.

2. Тип геоданных

Чтобы включить гео-запросы, нам нужно создать отображение индекса вручную и явно установить отображение поля.

Динамическое сопоставление не будет работать при настройке сопоставления для географических типов.

Elasticsearch предлагает два способа представления геоданных:

  1. Пары широта-долгота с использованием типа поля географической точки

  2. Сложная форма, определенная вGeoJSON с использованием типа поля гео-формы

Давайте более подробно рассмотрим каждую из перечисленных категорий:

2.1. Тип данных географической точки

Тип поля Geo-point принимает пары широта-долгота, которые можно использовать для:

  • Найти точки на определенном расстоянии от центральной точки

  • Найти точки внутри поля или многоугольника

  • Агрегировать документы географически или по расстоянию от центральной точки

  • Сортировать документы по расстоянию

Ниже приведен пример сопоставления поля для сохранения данных географической точки:

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

Как видно из примера выше,type для поляlocation равноgeo_point. Таким образом, теперь мы можем указать пару широта-долгота вlocation в поле местоположения.

2.2. Тип данных Geo Shape

В отличие отgeo-point,geo shape предоставляет функциональные возможности для сохранения и поиска сложных форм, таких как многоугольник и прямоугольник. Тип данныхGeo shape должен использоваться, когда мы хотим искать документы, содержащие фигуры, отличные от географических точек.

Давайте посмотрим на отображение для типа данных гео-формы:

PUT /index_name
{
    "mappings": {
        "TYPE_NAME": {
            "properties": {
                "location": {
                    "type": "geo_shape",
                    "tree": "quadtree",
                    "precision": "1m"
                }
            }
        }
    }
}

Приведенное выше отображение будет индексировать поле местоположения с реализациейquadtree с точностью до одного метра.

Elasticsearch breaks down the provided geo shape into series of geo hashes consisting of small grid-like squares called raster.

В зависимости от нашего требования мы можем контролировать индексацию полейgeo shape. Например, когда мы ищем документы для навигации, точность до одного метра становится очень важной, так как это может привести к неверному пути.

А если мы ищем достопримечательности, то точность до 10-50 метров может быть приемлемой.

При индексировании данныхgeo shape мы должны помнить одну вещь: мы всегда ставим под угрозу производительность в ущерб точности. С более высокой точностью Elasticsearch генерирует больше терминов, что приводит к увеличению использования памяти. Следовательно, мы должны быть очень осторожны при выборе отображения для гео-формы.

Мы можем найти больше вариантов сопоставления для типа данныхgeo-shape в официальномES site.

3. Различные способы сохранения данных географических точек

3.1. Широта Долгота Объект

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

Здесь географическая точкаlocation сохраняется как объект с ключамиlatitude иlongitude.

3.2. Пара широты и долготы

{
    "location": "23.02,72.57"
}

Здесьlocation выражается как пара широта-долгота в формате простой строки. Обратите внимание, последовательность широты и долготы в строковом формате.

3.3. Гео Хеш

{
    "location": "tsj4bys"
}

Мы также можем предоставить данные географической точки в форме гео-хеша, как показано в примере выше. Мы можем использоватьonline tool для преобразования долготы и широты в гео-хэш.

3.4. Массив долготы и широты

{
    "location": [72.57, 23.02]
}

Последовательность широта-долгота меняется на противоположную, когда широта и долгота предоставляются в виде массива. Первоначально пара широта-долгота использовалась как в строке, так и в массиве, но позже она была перевернута, чтобы соответствовать формату, используемомуGeoJSON.

4. Различные способы сохранения данных геометрии

4.1. Pointс

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

Здесь тип геометрической фигуры, которую мы пытаемся вставить, -point. Обратите внимание на полеlocation, у нас есть вложенный объект, состоящий из полейtype иcoordinates. Эти мета-поля помогают Elasticsearch идентифицировать гео-форму и ее фактические данные.

4.2. LineStringс

POST /index/type
{
    "location" : {
        "type" : "linestring",
        "coordinates" : [[Here, we're inserting _linestring_ geo shape. The coordinates for _linestring_ consists of two points i.e. start and endpoint. _LineString_ geo shape is very helpful for navigation use case.

=== *4.3. _Polygon_*

[source,java,gutter:,true]

POST / index / type {"location": {"type": "polygon", "координаты": [[[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 https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html#input-structure[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:

[source,java,gutter:,true]

org.locationtech.spatial4j Space4j 0.7 com.vividsolutions < artifactId> jts 1.13 xerces xercesImpl

We can search for above dependencies in https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.locationtech.spatial4j%22%20AND%20a%3A%22spatial4j%22[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 sample query to fetch all documents that fall _within_ given top-left and bottom-right coordinates:

[source,java,gutter:,true]

{"query": {"bool": {"must": {"match_all": {}}, "filter": {"geo_shape": {"region": {"shape": {"type": "envelope "," координаты ": [[отношение": "в пределах"}}}}}}

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:

[source,java,gutter:,true]

QueryBuilders .geoShapeQuery ("регион", ShapeBuilders.newEnvelope (новая координата (75.00, 25.0), новая координата (80.1, 30.2))) .relation (ShapeRelation.WITHIN);

=== *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:

[source,java,gutter:,true]

{"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:

[source,java,gutter:,true]

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

Geo Bounding Box query supports similar formats like we have in _geo_point_ data type. Sample queries for supported formats can be found on the https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-bounding-box-query.html#_accepted_formats[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:

[source,java,gutter:,true]

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

And here's the Java code for above query:

[source,java,gutter:,true]

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 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-query.html#_accepted_formats_2[official site].

=== *5.4. Geo _Polygon_ Query*

A query to filter all records that have points that fall within the given polygon of points.

Let's have a quick look at a sample query:

[source,java,gutter:,true]

{"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}]}}}}}}}

And at the Java code for this query:

[source,java,gutter:,true]

Список allPoints = новый ArrayList (); allPoints.add (новый GeoPoint (22.733, 68.859)); allPoints.add (новый GeoPoint (24.733, 68.859)); allPoints.add (новый GeoPoint (23, 70.859));

QueryBuilders.geoPolygonQuery("location", allPoints);

Geo Polygon Query also supports formats mentioned below:

* lat-long as an array: [lon, lat]
* lat-long as a string: “lat, lon”
* geo hash

_geo_point_ data type is mandatory in order to use this query.

== *6. Conclusion*

In this article, we discussed different mapping options for indexing geo data i.e _geo_point_ and _geo_shape_.

We also went through different ways to store _geo-data_ and finally, we observed geo-queries and Java API to filter results using geo queries.

As always, the code is available https://github.com/eugenp/tutorials/tree/master/persistence-modules/spring-data-elasticsearch[in this GitHub project].
Related