Elasticsearch: ES|QL を使用した地理空間検索

著者: Elastic Craig Tavernerより

Elasticsearch には長年にわたって強力な地理空間検索および分析機能が備わっていますが、その API は一般的な GIS ユーザーが慣れ親しんでいるものとは大きく異なります。昨年、私たちはES|QL クエリ言語 を追加しました。これは、SQL と同等かそれ以上に単純なパイプライン クエリ言語です。 Elastic が得意とする検索、セキュリティ、可観測性のユースケースに特に適しています。また、ES|QL での地理空間検索と分析のサポートも追加され、特に SQL またはGISコミュニティのユーザーにとってさらに使いやすくなりました

Elasticsearch 8.12 および 8.13 は、ES|QL の地理空間タイプの基本サポートを提供します。この機能は、8.14 で地理空間検索機能が追加されたことで大幅に強化されました。さらに重要なことは、このサポートは、PostGIS などの他の空間データベースで使用されている Open Geospatial Consortium (OGC ) シンプルフィーチャー アクセス(OGC ) 標準と緊密に連携するように設計されており、これらの標準に精通している GIS 専門家にとって使いやすくなっているということです。

このブログ投稿では、ES|QL を使用して地理空間検索を実行する方法と、同等の SQL およびクエリ DSL との比較を説明します。 ES|QL を使用して空間結合を実行し、結果を Kibana Maps で視覚化する方法も説明します。ここで説明するすべての機能は「テクニカル プレビュー」ステータスにあることに注意してください。これらの機能を改善する方法についてのフィードバックをお待ちしています。

データの準備

このチュートリアルで使用されるデータは、次の場所からダウンロードできます。

git clone https://github.com/liu-xiao-guo/esql

使用するドキュメントは、  main · liu-xiao-guo/esql · GitHub にある esql/airport_city_boundaries.csvです。

次に、Kibana を開きます。

上では、インデックス名を Airport_city_boundaries に設定しました。

上記のマッピングを次のように変更します。

      "properties": {
        "abbrev": {
          "type": "keyword"
        },
        "airport": {
          "type": "text"
        },
        "city": {
          "type": "keyword"
        },
        "city_boundary": {
          "type": "geo_shape"
        },
        "city_location": {
          "type": "geo_point"
        },
        "region": {
          "type": "text"
        }
      }
    }

上に示したように、769 件のドキュメントを作成することに成功しました。

地理空間データの検索

クエリの例から始めましょう。

FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location

これにより、三亜鳳凰国際空港 (SYX) を囲む長方形の検索ポリゴンと交差する都市境界ポリゴンが検索されます。

この検索で​​は、空港、都市、および都市の境界のサンプル データセットで、交差するポリゴンが検索され、一致するドキュメントから必要なフィールドが返されます。

略語 空港 地域 都市の場所
SYX 三亜フェニックス国際空港 天雅区 置いてください ポイント(109.5036 18.2533)

簡単です!次に、これを同じクエリの従来の Elasticsearch クエリ DSL と比較します。

GET /airport_city_boundaries/_search
{
  "_source": ["abbrev", "airport", "region", "city", "city_location"],
  "query": {
    "geo_shape": {
      "city_boundary": {
        "shape": {
          "type": "polygon",
          "coordinates" : [[
            [109.4, 18.1],
            [109.6, 18.1],
            [109.6, 18.3],
            [109.4, 18.3],
            [109.4, 18.1]
          ]]
        }
      }
    }
  }
}

どちらのクエリの目的もかなり明確ですが、ES|QL クエリは SQL に非常に似ています。 PostGIS での同じクエリは次のようになります。

SELECT abbrev, airport, region, city, city_location
FROM airport_city_boundaries
WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
);

ES|QL の例を確認してみましょう。かなり似ていますよね?

FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location

Elasticsearch API の既存ユーザーは ES|QL の方が使いやすいと感じていることがわかりました。現在、既存の SQL ユーザー (特に空間 SQL ユーザー) は、ES|QL が使い慣れているものと非常に似ていると感じると予想しています。

なぜ SQL を使用しないのでしょうか?

Elasticsearch SQL についてはどうですか?これはしばらく前から存在しており、いくつかの地理空間機能を備えています。ただし、Elasticsearch SQL は生のクエリ API 上のラッパーとして記述されているため、生の API に変換できるクエリのみがサポートされます。 ES|QL にはこの制限はありません。まったく新しいスタックとして、SQL では不可能な多くの最適化が可能になります。私たちのベンチマークでは、ES|QL は、特に集計において、クエリ API よりも高速であることが多いことが示されています。

SQLとの違い

前の例から明らかに、ES|QL は SQL に似ていますが、いくつかの重要な違いがあります。たとえば、ES|QL は、FROM のようなソース コマンドで始まり、後続のすべてのコマンドをパイプ文字 | でリンクするパイプライン クエリ言語です。これにより、各コマンドがどのようにデータのテーブルを受け取り、そのテーブルに対して WHERE によるフィルタリング、EVAL による列の追加、STATS による集計の実行などの操作を実行するかを理解しやすくなります。SELECT で開始して最終出力列を定義する代わりに、1 つ以上の KEEP コマンドを使用し、その最後のコマンドで最終出力を指定することができます。この構造により、クエリに関する推論が簡素化されます。

上記の例の WHERE コマンドに注目すると、PostGIS の例と非常によく似ていることがわかります。

英語|QL

WHERE ST_INTERSECTS(
    city_boundary,
    "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)

ポストGIS

WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
)

文字列の引用符の違いに加えて、最大の違いは文字列をスペース型にキャストする方法です。 PostGIS では ::geometry サフィックスを使用し、ES|QL では ::geo_shape サフィックスを使用します。これは、ES|QL が Elasticsearch で実行され、型変換演算子:: を使用して文字列をサポートされている ES|QL 型 (この場合は geo_shape) に変換できるためです。さらに、Elasticsearch の geo_shape タイプと geo_point タイプは、通常 SRID 番号 4326 で表される WGS84 と呼ばれる空間座標系を意味します。 PostGIS では、これを明示的に記述する必要があるため、WKT 文字列には SRID=4326; というプレフィックスが付けられます。プレフィックスを削除すると、SRID は 0 に設定されます。これは、特定の座標系に関連付けられていない Elasticsearch タイプの cartesian_point および cartesian_shape に似ています。

ES|QL と PostGIS は両方とも、型変換関数の構文を提供します。

英語|QL

WHERE ST_INTERSECTS(
    city_boundary,
    TO_GEOSHAPE("POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))")
)

ポストGIS

WHERE ST_INTERSECTS(
    city_boundary,
    ST_SetSRID(
      ST_GeomFromText('POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'),
      4326
    )
)

OGC機能

Elasticsearch 8.14 では、次の 4 つの OGC 空間検索機能が導入されています。

英語|QL ポストGIS 説明
ST_INTERSECTS ST_交差 2 つのジオメトリが交差する場合は true を返し、そうでない場合は false を返します。
ST_DISJOINT ST_分離 2 つのジオメトリが交差しない場合は true、交差しない場合は false を返します。 ST_INTERSECTS の逆。
ST_CONTAINS ST_含む 1 つのジオメトリに別のジオメトリが含まれている場合は true を返し、それ以外の場合は false を返します。
ST_WITHIN ST_以内 あるジオメトリが別のジオメトリ内にある場合は true を返し、それ以外の場合は false を返します。 ST_CONTAINS の逆演算。

これらの関数は、PostGIS の対応する関数と同様に動作し、同じ方法で使用できます。たとえば、ST_INTERSECTS は、2 つのジオメトリが交差する場合は true を返し、そうでない場合は false を返します。上の表のドキュメント リンクをクリックすると、すべての ES|QL の例が FROM 句の後の WHERE 句にあるのに対し、すべての PostGIS の例ではリテラル ジオメトリが使用されていることがわかります。実際、どちらのプラットフォームでも、クエリのどの部分でもこれらの関数の使用をサポートしています。

PostGIS ドキュメントの ST_INTERSECTS の最初の例は次のとおりです。

SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);

ES|QL の同等のバージョンは次のとおりです。

ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)

PostGIS の例では SRID を指定していないことに注意してください。これは、PostGIS でジオメトリ タイプを使用する場合、すべての計算が平面座標系で行われるため、2 つのジオメトリが同じ SRID を持つ場合、SRID が何であるかは関係ありません。 Elasticsearch では、これはほとんどの関数にも当てはまりますが、空間距離検索に関する次のブログで説明するように、geo_shape と geo_point が球面計算を使用する例外があります。

ES|QL の多用途性

以上、WHERE 句と ROW コマンドで空間関数を使用する例を見てきました。他にどこで役に立つでしょうか?非常に便利な場所は EVAL コマンドです。このコマンドを使用すると、式を評価して結果を返すことができます。たとえば、国名ごとにグループ化されたすべての空港の重心が国の輪郭を描く境界内にあるかどうかを確認してみましょう。

FROM airports
| EVAL in_uk = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL in_iceland = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| EVAL within_uk = ST_WITHIN(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL within_iceland = ST_WITHIN(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| STATS centroid = ST_CENTROID_AGG(location), count=COUNT() BY in_uk, in_iceland, within_uk, within_iceland
| SORT count ASC

結果は予想どおりで、英国の空港の重心はアイスランド国境ではなく英国国境内にあり、またその逆も同様です。

重心 カウント イギリス アイスランド 英国内 アイスランド内
ポイント (-21.946634463965893 64.13187285885215) 1 間違い 真実 間違い 真実
ポイント (-2.597342072712148 54.33551226578214) 17 真実 間違い 真実 間違い
ポイント (0.04453958108176276 23.74658354606057) 873 間違い 間違い 間違い 間違い

実際、これらの関数は、その署名が意味をなす限り、クエリのどの部分でも使用できます。どちらも 2 つの引数 (リテラル空間オブジェクトまたは空間タイプのフィールド) を受け入れ、どちらもブール値を返します。重要な考慮事項は、ジオメトリの座標参照系 (CRS) が一致する必要があることです。一致しない場合はエラーが返されます。これは、同じ関数呼び出しで geo_shape タイプと cartesian_shape タイプを混在させることはできないことを意味します。ただし、 geo_point タイプは geo_shape タイプの特殊なケースであり、両方とも同じ座標参照系を共有するため、 geo_point タイプと geo_shape タイプを混合することができます。上記で定義された各関数のドキュメントには、サポートされる型の組み合わせがリストされています。

さらに、どちらのパラメータも、任意の順序でスペース リテラルまたはフィールドにすることができます。 2 つのフィールド、2 つのテキスト、フィールドとテキスト、またはテキストとフィールドを指定することもできます。唯一の要件は型の互換性です。たとえば、次のクエリは同じインデックス内の 2 つのフィールドを比較します。

FROM airport_city_boundaries
| EVAL in_city = ST_INTERSECTS(city_location, city_boundary)
| STATS count=COUNT(*) BY in_city
| SORT count ASC
| EVAL cardinality = CASE(count < 10, "very few", count < 100, "few", "many")
| KEEP cardinality, count, in_city

クエリは基本的に、都市の位置が都市の境界内にあるかどうかを尋ねます。これは通常は正しいはずですが、常に例外があります。

基数 カウント 市内
少し 29 間違い
多くの 740 真実

さらに興味深い質問は、空港の場所が空港が管轄する都市の境界内にあるかどうかです。ただし、空港の位置は、市の境界を含むインデックスとは異なるインデックスにあります。これには、これら 2 つの独立したインデックス内のデータを効率的にクエリして相関付ける方法が必要です。

空間結合

ES|QL は JOIN コマンドをサポートしていませんが、ENRICH コマンドを使用して、SQL の「左結合」のように動作する特殊な場合の結合を実装できます。このコマンドは SQL の「左結合」のように動作し、2 つのデータ セット間の空間的関係に基づいて、あるインデックスの結果を別のインデックスのデータで強化することができます。

たとえば、空港がサービスを提供する都市に関する追加情報を含む空港の場所を含む都市の境界を検索して、空港テーブルの結果を充実させ、結果に対していくつかの統計を実行してみましょう。

FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| MV_EXPAND city_boundary
| EVAL boundary_wkt_length = LENGTH(TO_STRING(city_boundary))
| STATS centroid = ST_CENTROID_AGG(location), count = COUNT(city_location), min_wkt = MIN(boundary_wkt_length), max_wkt = MAX(boundary_wkt_length) BY region
| SORT count DESC
| LIMIT 5

これにより、最も多くの空港を含む上位 5 つの地域、一致する地域を持つすべての空港の重心、およびそれらの地域内の都市境界の WKT 表現の長さの範囲が返されます。

重心 カウント my_wkt 最大幅 地域
ポイント (-32.56093470960719 32.598117914802714) 90 207 207 ヌル
ポイント (-73.94515332765877 40.70366442203522) 9 438 438 ニューヨーク市
ポイント (-83.10398317873478 42.300230911932886) 9 473 473 デトロイト
ポイント (-156.3020245861262 20.176383580081165) 5 307 803 ハワイ
ポイント (-73.88902732171118 45.57078813901171) 4 837 837 モントリオール

では、ここで一体何が起こっているのでしょうか?いわゆる JOIN はどこで行われるのでしょうか?クエリの鍵は ENRICH コマンドにあります。

FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary

このコマンドは、Elasticsearch に対して、空港インデックスから取得した結果を強化し、元のインデックスの city_location フィールドと、以前のいくつかの例で使用した Airport_city_boundaries インデックスの city_boundary フィールドの間の交差結合を実行するように指示します。ただし、この情報の一部は、このクエリでは明確に表示されません。表示されるのは、強化されたポリシー city_boundaries の名前であり、欠落している情報はポリシー定義にカプセル化されています。

{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}

ここでは、 geo_match クエリ (デフォルトで交差) が実行され、一致するフィールドは city_boundary で、enrich_fields は元のドキュメントに追加するフィールドであることがわかります。フィールドの 1 つであるリージョンは、実際には STATS コマンドのグループ化キーとして使用されます。これは、この「左結合」機能がなければ実行できません。エンリッチメント戦略の詳細については、 エンリッチメントのドキュメントを参照してください。これらのドキュメントを読むと、取り込みパイプラインを構成することにより、インデックス作成時にリッチ インデックスを使用してデータを強化する方法が説明されていることがわかります。 ENRICH コマンドはクエリ時に機能するため、ES|QL ではこれは必要ありません。必要なデータと強化戦略を備えたリッチ インデックスを準備し、ES|QL クエリで ENRICH コマンドを使用するだけで十分です。

最も一般的なフィールドが null であることにも気づくかもしれません。それはどういう意味ですか?このコマンドを SQL の「左結合」と比較したことを思い出してください。つまり、空港に一致する都市の境界が見つからない場合でも、空港は返されますが、airport_city_boundaries インデックスのフィールド値は null になります。 89 の空港には一致する市の境界がなく、1 つの空港には一致する地域フィールドがヌルであることが判明しました。その結果、90 の空港の結果に地域が含まれませんでした。もう 1 つの興味深い詳細は、MV_EXPAND コマンドの必要性です。これが必要なのは、ENRICH コマンドが入力行ごとに複数の結果を返す可能性があり、MV_EXPAND がこれらの結果を結果ごとに 1 つずつ複数の行に分割するのに役立つためです。これは、「ハワイ」で異なる min_wkt と max_wkt の結果が表示される理由も説明しています。同じ名前で境界が異なる複数の地域が存在します。

キバナのマップ

Kibana は、マップ アプリケーションに Spatial ES|QL のサポートを追加します。これは、ES|QL を使用して Elasticsearch で地理空間データを検索し、結果を地図上で視覚化できることを意味します。

[レイヤーの追加] メニューに、「ES|QL」という新しいレイヤー オプションがあります。これまで説明したすべての地理空間機能と同様、このオプションは「テクニカル プレビュー」ステータスです。このオプションを選択すると、ES|QL クエリの結果に基づいてマップにレイヤーを追加できます。たとえば、世界中のすべての空港を表示するレイヤーをマップに追加できます。

あるいは、airport_city_boundaries インデックスにポリゴンを表示するレイヤーを追加することもできます。あるいは、さらに良い方法として、上記の複雑な ENRICH クエリは、各地域にある空港の数に関する統計をどのように生成するのでしょうか。

次は何でしょうか?

上の 2 つの例では、別の空間関数 ST_CENTROID_AGG を押し込んでいることに気づいたかもしれません。これは STATS コマンドで使用される集計関数であり、ES|QL に追加する予定の多くの空間分析機能の最初のものです。他にも紹介したいことがあれば、ブログで紹介します。

その前に、私たちが開発した特にエキサイティングな機能についてもう少し詳しく説明したいと思います。それは、Elasticsearch で最も一般的に使用される空間検索機能の 1 つである空間距離検索を実行する機能です。距離検索の構文がどのようなものになるか想像できますか? OGC機能のようなものでしょうか?詳細については、このシリーズの次回のブログをお楽しみに!

ネタバレ注意: Elasticsearch 8.15 がリリースされました。ES|QL を使用した空間距離検索が含まれています。

 

自分で試してみる準備はできましたか?無料トライアルを開始してください
Elastic 認定を取得したいですか?次回のElasticsearch エンジニア トレーニングがいつ開始されるかを確認してください。

 

翻訳:Elasticsearch ES|QL による地理空間検索 — Search Labs

Google: Rust への移行により、Android の脆弱性が大幅に軽減されました。Huawei 、オープンな UBMC (古いクラシック音楽プレーヤー Winamp) を2024.2.3 に正式にリリースする と発表しました。 オラクルの商標になったのですか?Open Source Daily | PostgreSQL 17; 中国の AI 企業は米国のチップ禁止を回避する方法; AI 開発者の渇望を潤せるのは誰か?スタートアップ企業「Zhihuijun」は、最新のロボット工学分野向けのランタイム開発フレームワークである AimRT、Tcl/Tk 9.0 をオープンソース化し、Meta をリリースし、Llama 3.2 マルチモーダル AI モデルをリリースしました
{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/3343882/blog/16010045