ST_Distance、kNNを使用したPostGIS最近傍点


23

1つのテーブルの各要素で、別のテーブルの最も近いポイントを取得する必要があります。最初のテーブルには交通標識が含まれ、2番目のテーブルには町のエントランスホールが含まれます。問題は、ST_ClosestPoint関数を使用できないことと、ST_Distance関数を使用してmin(ST_distance)レコードを取得する必要があることですが、クエリの構築が非常に困難です。

CREATE TABLE traffic_signs
(
  id numeric(8,0) ),
  "GEOMETRY" geometry,
  CONSTRAINT traffic_signs_pkey PRIMARY KEY (id),
  CONSTRAINT traffic_signs_id_key UNIQUE (id)
)
WITH (
  OIDS=TRUE
);

CREATE TABLE entrance_halls
(
  id numeric(8,0) ),
  "GEOMETRY" geometry,
  CONSTRAINT entrance_halls_pkey PRIMARY KEY (id),
  CONSTRAINT entrance_halls_id_key UNIQUE (id)
)
WITH (
  OIDS=TRUE
);

すべてのtraffic_signの最も近いentrnce_hallのIDを取得する必要があります。

これまでの私のクエリ:

SELECT senal.id,port.id,ST_Distance(port."GEOMETRY",senal."GEOMETRY")  as dist
    FROM traffic_signs As senal, entrance_halls As port   
    ORDER BY senal.id,port.id,ST_Distance(port."GEOMETRY",senal."GEOMETRY")

これにより、すべてのtraffic_signからすべてのentrance_hallまでの距離を取得しています。しかし、最小距離のみを取得するにはどうすればよいですか?

よろしく、


PostgreSQLのバージョン
ジャクブカニア

回答:


41

あなたはもうすぐそこにいます。Postgresの異なる演算子を使用する小さなトリックがあります。これは、各組み合わせの最初の一致を返します。ST_Distanceで並べ替えると、各セナルから各ポートに最も近いポイントを効果的に返します。

SELECT 
   DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY")  as dist
FROM traffic_signs As senal, entrance_halls As port   
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

それぞれの場合の最小距離が量x以下であることがわかっている場合(およびテーブルに空間インデックスがある場合)、を置くことでこれを高速化できますWHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", distance)。たとえば、すべての最小距離が10km以内の場合:

SELECT 
   DISTINCT ON (senal.id) senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY")  as dist
FROM traffic_signs As senal, entrance_halls As port  
WHERE ST_DWithin(port."GEOMETRY", senal."GEOMETRY", 10000) 
ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

当然、これは注意して使用する必要があります。最小距離が大きい場合、そのセナルとポートの組み合わせに対して行が取得されないためです。

注:順序ごとの順序は、個別の順序と一致する必要があります。これは、distinctは何らかの順序に基づいて最初の別個のグループを取るためです。

両方のテーブルに空間インデックスがあることを前提としています。

編集1。別のオプションがあります。これは、Postgresの<->および<#>演算子を使用することです(それぞれ中心点と境界ボックスの距離計算)。これは、空間インデックスをより効率的に使用し、nを避けるためにST_DWithinハックを必要としません^ 2比較。それらがどのように機能するかを説明する良いブログ記事があります。一般的な注意事項は、これら2つの演算子がORDER BY句で機能することです。

SELECT senal.id, 
  (SELECT port.id 
   FROM entrance_halls as port 
   ORDER BY senal.geom <#> port.geom LIMIT 1)
FROM  traffic_signs as senal;

編集2。この質問は多くの注目を集めており、GISではk最近傍(kNN)は一般に(アルゴリズムの実行時間に関して)難しい問題であるため、この質問の元の範囲をいくらか拡大する価値があります。

1つのオブジェクトのxの最近傍を見つける標準的な方法は、LATERAL JOINを使用することです(概念的にはfor eachループに似ています)。dbastonの答えから恥知らずに借りると、次のようになります。

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports
      ORDER BY signs.geom <-> ports.geom
     LIMIT 1
   ) AS closest_port

したがって、距離で並べられた最も近い10個のポートを検索する場合は、ラテラルサブクエリのLIMIT句を変更するだけです。これは、LATERAL JOINSを使用せずに行うのがはるかに難しく、ARRAYタイプのロジックを使用する必要があります。このアプローチはうまく機能しますが、特定の距離まで検索するだけでよいことがわかっている場合、非常に高速化できます。このインスタンスでは、サブクエリでST_DWithin(signs.geom、ports.geom、1000)を使用できます。これは、インデックス作成が<->演算子で機能する方法のため、ジオメトリの1つが定数ではなく定数である必要があります列参照-より高速になる場合があります。したがって、たとえば、10 km以内の3つの最も近いポートを取得するには、次のように記述できます。

 SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports
      WHERE ST_DWithin(ports.geom, signs.geom, 10000)
      ORDER BY ST_Distance(ports.geom, signs.geom)
     LIMIT 3
   ) AS closest_port;

いつものように、使用方法はデータの分布とクエリによって異なるため、EXPLAINはあなたの親友です。

最後に、CROSS JOIN LATERALの代わりにLEFTを使用する場合、ラテラルクエリエイリアスの後にON TRUEを追加する必要があるという、ちょっとした落とし穴があります。

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
 FROM traffic_signs
LEFT JOIN LATERAL 
  (SELECT
      id, 
      ST_Distance(ports.geom, signs.geom) as dist
      FROM ports          
      ORDER BY signs.geom <-> ports.geom
      LIMIT 1
   ) AS closest_port
   ON TRUE;

これは、大量のデータではうまく機能しないことに注意してください。
ジャクブカニア

@jakubKania。ST_DWithinを使用できるかどうかによって異なります。しかし、はい、取られたポイント。残念ながら、<-> / <#>演算子による順序では、ジオメトリの1つが定数である必要がありますか?
ジョンパウエル

@JohnPowellakaBarçaそのブログの投稿が現在どこにあるか知っていますか?-または、<->および<#>演算子の同様の説明?ありがとう!!
DPSS17年

@DPSSpatial、それは迷惑です。私はしませんが、このアプローチについて少し話をするこれこれがあります。もう1つは、横方向の結合も使用します。これは、もう1つの興味深い機能強化です。
ジョンパウエル

@DPSSpatial。この<->、<#>および横方向の結合は、すべて少し滑りやすいです。私はこれを非常に大きなデータセットで実行しましたが、ST_DWithinを使用せずにパフォーマンスは恐ろしいものでしたが、これはすべて回避することになっています。最終的に、knnは複雑な問題であるため、使用方法が異なる場合があります。幸運を祈る:
ジョンパウエル

13

これはLATERAL JOIN、PostgreSQL 9.3以降で実行できます。

SELECT
  signs.id,
  closest_port.id,
  closest_port.dist
FROM traffic_signs
CROSS JOIN LATERAL 
  (SELECT
     id, 
     ST_Distance(ports.geom, signs.geom) as dist
     FROM ports
     ORDER BY signs.geom <-> ports.geom
   LIMIT 1) AS closest_port

10

クロス結合を使用したアプローチはインデックスを使用せず、大量のメモリを必要とします。したがって、基本的に2つの選択肢があります。9.3以前では、相関サブクエリを使用していました。9.3+を使用できますLATERAL JOIN

横方向のひねりを加えたKNN GISTお近くのデータベースに近日公開予定

(すぐに続く正確なクエリ)


1
横方向結合のクールな使用。この文脈ではこれまで見たことがない。
ジョンパウエル

1
@JohnBarçaこれは私が見た中で最高のコンテキストの1つです。またST_DISTANCE()、最も近いポリゴンを見つけるために本当に使用する必要があり、クロスジョインがサーバーのメモリ不足を引き起こしている場合にも役立つと思います。最も近いポリゴンクエリは未解決のままです。
ジャクブカニア

2

ジョン・バルサ

ORDER BYが間違っています!

ORDER BY senal.id, port.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY");

senal.id, ST_Distance(port."GEOMETRY", senal."GEOMETRY"),port.id;

それ以外の場合は、最も近いポートIDではなく、小さいポートIDのみを返します


1
正しいものは次のようになります(ポイントとラインを使用しました):SELECT DISTINCT ON (points.id) points.id, lines.id, ST_Distance(lines.geom, points.geom) as dist FROM development.passed_entries As points, development."de_muc_rawSections_cleaned" As lines ORDER BY points.id, ST_Distance(lines.geom, points.geom),lines.id;
blackgis

1
OK @dbastonの回答のように、実際にはLATERAL JOINアプローチを使用する方が良いでしょう。上記のアプローチはもう使用しません。
ジョンパウエル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.