ElasticsearchのGEO系検索のうち、ポリゴンをからめた検索について試してみました。
なお、試したのはElasticsearch 6.8ですが、7系に橋渡ししやすいクエリやMapping設定の記述としたつもりです。ただし、紙面の都合・その他の理由によりでver6系とver7系の違いそのものについては割愛しています。
GEO系クエリ
まず、Elasticsearchの最近のバージョンでのGEO系クエリDSLの四天王は次のもののようです。
- geo_bounding_box query
- geo_distance query
- geo_polygon query
- geo_shape query
文章で説明するのも手間なので、おおよそざっくりした図に示します。
検索できる field typeと 検索条件の指定方法は次のようになります。
私自身、個人的にかかわったプロダクトで関わったものは、取り回しが比較的容易な上の2つなのですが、この記事では勉強をかねて下2つの方をフォーカスします。
なお、上2つの試してみた系の記事を自分なりにまとめてみたものがありますので、上2つの方がお求めのものであれば、こちらもご参照ください。
検索してみる
最初にならし運転として、公式のリファレンスの例を抜粋〜ちょいと改変しててなぞってみます。
geo_polygon検索
Geo-polygon query | Elasticsearch Reference [7.6] | Elastic
実のところ、geo_polygonでの検索は、次項のgeo_shapeの特化した例と言えます。geohashを用いた検索方法バリエーションなど、geo_polygonならではのものもあるようですが、geo_shapeとまるごと説明する本記事では、次のgeo_shapeを説明で代用することにします。
なお、geo_polygonは、検索条件の方は、GeoJSON相当ですが、geo_pointタイプのフィールドが検索対象です。
Geo-point datatype | Elasticsearch Reference [7.6] | Elastic
geo_shape検索( 以下fieldtypeのgeo_shapeと紛らわしいと考えた場合は、Geo-Shape検索などと記載しているところがあります)
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-geo-shape-query.html
前準備
◆mapping PUT /example_x?include_type_name=false { "mappings": { "properties": { "basho": { "type": "geo_shape" } } } } ※ bashoというフィールドを「geo_shape」typeとして定義しています。 ◆検索対象ドキュメントを1件登録 POST /example_x/_doc?refresh { "name": "Wind & Wetter, Berlin, Germany", "basho": { "type": "point", "coordinates": [13.4,52.5] } } ※bashoについて、GeoJSON形式(相当)のcoordinates(座標が13.4、52.5)、および該当のtypeとして登録します。 GeoJSONについては、後ほど、詳しく... はありませんが、もう少し補足します。
検索の例
◆検索クエリの例(ここでは、GeoJSONではなく、envelope(雑に言うと、長方形エリアの対角座標を指定の検索用シンタックスシュガー)を指定して検索) GET /example_x/_search?filter_path=*.hits.total,**.hits._source { "query":{ "bool": { "must": { "match_all": {} }, "filter": { "geo_shape": { "basho": { "shape": { "type": "polygon", "coordinates" : [[ [13,52], [14,51], [14,53], [13,54],[13,52] ]] }, "relation": "within" } } } } } } ※ geo_shape.shape部分がこのDSLの様式です。 shapeの内側が、GeoJSON形式相当での検索範囲の矩形の指定になっています。 また、relationプロパティで、withinを設定し、この検索範囲に収まっているような、ドキュメントを検索します。 relationについては、within以外に、intersects、contains、disjointが指定できます。 ↓ 網にかかった先ほどの登録ドキュメントがヒット { "hits" : { "total" : 1, "hits" : [ { "_source" : { "name" : "Wind & Wetter, Berlin, Germany", "basho" : { "type" : "point", "coordinates" : [ 13.4, 52.5 ] } } } ] } }
GeoJSON
ではGeoJSONですが、ガチのGISerな人はともかく、私のようなヒトは、ひとまずWikipediaの図解を見てから、もっと気になることがあれば公式をなぞってみる...ぐらいが良いと思いました。
もったいつけた割にリンクをはるだけかよというところがありますので、以下、少しだけElasticsearchでGeo-Shape検索をつまみ食い的に試してみたいヒト向けに、私なりのGeoJSONの勘所をまとめてみます。
GeoJSONをElasticsearch検索で使うにあたり理解のしどころポイントなど
- これはおそらくもととなったシェープファイル(Shapefile)からの踏襲だと思うのですが、GeoJSONでは、「Feature」「features」とか「FeatureXXX」のようなプロパティで、1レコードの塊を示すようです。
- 一方、Elasticsearchでの検索やGeo-Shape検索対象のフィールドのdatatypeであるgeo_shapeのフォーマットとしては、これら"Feature"系の名前のプロパティはさほど深く考える必要がなく、"geometry"という名前のプロパティに注目すると良いでしょう。
- geometryというプロパティの中で、「type」で図形の種類およびその図形の種類(Polygon、Pointなど)と種類の形状を表す座標の配列をcordinatesプロパティで指定する形になります。ここでは割愛しますが、MultiPolygonなどMulti系の形状でも(ヒットの仕方は細かく確認していませんが)エラーにはならず検索できるようでした。
- なおGeoJSONの規格ではありませんが、envelopeという、長方形の対角の座標を指定する形式でより手軽に、Geo-Shape検索が可能です。→下記参照。
- また冒頭に述べたとおり、"relations"で、intersectsかwithinか、containsかのように検索方法を指定できますが、Polygonであれば、within(範囲内)、intersects(交差あり)を検索できますが、LineStringでは、intersects検索は可能ですが、withinはエラーになります。トポロジー的には当たり前といえばそうだと思いますが、確かにそうだねという挙動ですね。
◆GeoJSON以外にも「envelope」で検索できる。
GET /example_x/_search?filter_path=hits.total,**.hits._source { "query":{ "bool": { "must": { "match_all": {} }, "filter": { "geo_shape": { "basho": { "shape": { "type": "envelope", "coordinates" : [[13.0, 53.0], [14.0, 52.0]] }, "relation": "within" } } } } } }
geo_shape データフィールドタイプ
ここまでは、「検索する側のGeoJSON形式」に重心を置いていましたが、インデックスのドキュメントデータ側のGeoJSONです。
Geo-Shape検索のターゲットとなるフィールドは、geo_shapeデータフィールドタイプである必要があります。
これはこちら↓で述べられています。
じっくり取り組む方はページの先頭から読んでいただくべきかと思いますが、ひとまずつまみ食いしたいと言う方は
PUT /example { "mappings": { "properties": { "location": { "type": "geo_shape" } } } }
でlocationという名前のフィールドをgeo_shape型として宣言しておき、
このセクションの部分のデータを登録して、GeoJSONのデータ形式に慣れ親しむというのも良いかもしれません。 https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html#input-structure
なお、上記のリンクはまさにほぼGeoJSONのあるあるなデータ型の名前とも一致するのですが、いくつかElasticsearchのgeo_shape型オリジナルの型もあるようです。
個人的に便利だなと思ったのは、「circle」という型です。
次のような、中心点と距離で円形の形を定義できるようです。
POST /example/_doc { "location" : { "type" : "circle", "coordinates" : [101.0, 1.0], "radius" : "100m" } }
geo_shapeデータにしてもそれを検索するGeo-ShapeクエリDSLにしても、このような要件の場合は実際はどこかで仕入れてきた地形などの複雑な形状に対応した座標の配列情報を使うことになり、実際は独自に設定するということはあまりないのではないかと思います。
しかし、独自にキメのサイズの「商圏」などを定義する場合には、複雑な形状を定義してもしかたなく/やろうとしても難しいので、中心点と「だいたいこれぐらいの半径かな〜」というやり方になると思われますので、このcircleという形状の定義ができることはありがたいですね。
この話の続きなど
以下の記事で本記事の内容をもう少し掘り下げています。
直接の続編は後者の記事ですが、前者でGeoPandasを使って、GeoJSONの生々しいデータをごにょごにょすることをお試ししています。