ElasticSearchでの地理空間のサポート

ElasticSearchの地理空間サポート

1. 前書き

Elasticsearchは、全文検索機能で最もよく知られていますが、完全な地理空間サポートも備えています。

Elasticsearchの設定と開始について詳しくは、このprevious articleをご覧ください。

Elasticsearchで地理データを保存する方法と、地理クエリを使用してそれらのデータを検索する方法を見てみましょう。

2. 地理データタイプ

ジオクエリを有効にするには、インデックスのマッピングを手動で作成し、フィールドマッピングを明示的に設定する必要があります。

地理タイプのマッピングを設定している間は、動的マッピングは機能しません。

Elasticsearchは、ジオデータを表す2つの方法を提供します。

  1. ジオポイントフィールドタイプを使用した緯度と経度のペア

  2. ジオシェイプフィールドタイプを使用してGeoJSONで定義された複雑なシェイプ

上記の各カテゴリをさらに詳しく見てみましょう。

2.1. ジオポイントデータタイプ

ジオポイントフィールドタイプは、以下に使用できる緯度経度ペアを受け入れます。

  • 中心点から一定の距離内にある点を見つける

  • ボックスまたはポリゴン内のポイントを見つける

  • 地理的に、または中心点からの距離によってドキュメントを集約します

  • 距離でドキュメントを並べ替える

以下は、ジオポイントデータを保存するフィールドのサンプルマッピングです。

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

上記の例からわかるように、locationフィールドのtypegeo_pointです。 したがって、ロケーションフィールドのlocationに緯度と経度のペアを指定できるようになりました。

2.2. 地理形状データ型

geo-pointとは異なり、geo shapeは、ポリゴンや長方形などの複雑な形状を保存および検索する機能を提供します。 ジオポイント以外の形状を含むドキュメントを検索する場合は、Geo shapeデータ型を使用する必要があります。

ジオシェイプデータタイプのマッピングを見てみましょう。

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

上記のマッピングは、1メートルの精度でquadtree実装を使用してロケーションフィールドにインデックスを付けます。

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

要件に応じて、geo shapeフィールドのインデックス付けを制御できます。 たとえば、ナビゲーション用のドキュメントを検索する場合、パスが正しくない可能性があるため、最大1メートルの精度が非常に重要になります。

一方、いくつかの観光地を探している場合は、最大10〜50メートルの精度を許容できます。

geo shapeデータのインデックスを作成する際に留意する必要があることのひとつは、常に正確にパフォーマンスを低下させることです。 Elasticsearchはより高い精度で、より多くの用語を生成します。これにより、メモリ使用量が増加します。 したがって、ジオシェイプのマッピングを選択するときには、非常に注意する必要があります。

公式のES siteで、geo-shapeデータ型のその他のマッピングオプションを見つけることができます。

3. ジオポイントデータを保存するさまざまな方法

3.1. 緯度経度オブジェクト

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

ここで、ジオポイントlocationは、latitudelongitudeをキーとしてオブジェクトとして保存されます。

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フィールドを見てください。フィールドtypecoordinatesで構成されるネストされたオブジェクトがあります。 これらのメタフィールドは、Elasticsaerchが地理形状とその実際のデータを識別するのに役立ちます。

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"、 "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 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 Spatial4j 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]

{"クエリ":{"bool":{"must":{"match_all":{}}、 "filter":{"geo_shape":{"region":{"shape":{"type": "envelope "、" coordinates ":[[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:

[source,java,gutter:,true]

QueryBuilders .geoShapeQuery( "region"、ShapeBuilders.newEnvelope(new Coordinate(75.00、25.0)、new Coordinate(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]

{"クエリ":{"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]

{"クエリ":{"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]

{"クエリ":{"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]

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 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].