再帰クエリを使用して、構築するラインの検出された各端から開始して、各ポイントの最近傍を探索できます。
前提条件:ポイントを含むpostgisレイヤーと、道路を含む1つのMulti-linestringオブジェクトを含むレイヤーを準備します。2つの層は同じCRS上にある必要があります。作成したテストデータセットのコードは次のとおりです。必要に応じて変更してください。(postgres 9.2およびpostgis 2.1でテスト済み)
WITH RECURSIVE
points as (SELECT id, st_transform((st_dump(wkb_geometry)).geom,2154) as geom, my_comment as com FROM mypoints),
roads as (SELECT st_transform(ST_union(wkb_geometry),2154) as geom from highway),
手順は次のとおりです。
各ポイントについて、これら3つの基準を満たすすべての隣人とその距離のリストを生成します。
- 距離はユーザー定義のしきい値を超えてはなりません(これにより、孤立したポイントへのリンクが回避されます)
graph_full as (
SELECT a.id, b.id as link_id, a.com, st_makeline(a.geom,b.geom) as geom, st_distance(a.geom,b.geom) as distance
FROM points a
LEFT JOIN points b ON a.id<>b.id
WHERE st_distance(a.geom,b.geom) <= 15
),
- 直接経路は道路を横断してはいけません
graph as (
SELECt graph_full.*
FROM graph_full RIGHT JOIN
roads ON st_intersects(graph_full.geom,roads.geom) = false
),
距離は、最近傍からの距離のユーザー定義の比率を超えてはなりません(これは、固定距離よりも不規則なデジタル化に対応できるはずです) この部分は実際には実装が困難で、固定検索半径に固執しました
この表を「グラフ」と呼びましょう
グラフに結合し、グラフ内のエントリが1つだけあるポイントのみを保持して、ラインの終点を選択します。
eol as (
SELECT points.* FROM
points JOIN
(SELECT id, count(*) FROM graph
GROUP BY id
HAVING count(*)= 1) sel
ON points.id = sel.id),
このテーブルを "eol"(行末)と
簡単に呼びましょう。優れたグラフを作成することに対する報酬は次のステップで手に負えなくなることになる
各eolから開始してネイバーからネイバーに循環する再帰クエリを設定する
- eolテーブルを使用して再帰クエリを初期化し、深さのカウンター、パスのアグリゲーター、およびラインを構築するためのジオメトリコンストラクターを追加します。
- グラフを使用して最近傍に切り替え、パスを使用して後戻りしないことを確認することにより、次の反復に移動します
- 反復が終了した後、各開始点の最長パスのみを保持します(データセットに予想されるライン間の潜在的な交差が含まれている場合、その部分にはさらに条件が必要になります)
recurse_eol (id, link_id, depth, path, start_id, geom) AS (--initialisation
SELECT id, link_id, depth, path, start_id, geom FROM (
SELECT eol.id, graph.link_id,1 as depth,
ARRAY[eol.id, graph.link_id] as path,
eol.id as start_id,
graph.geom as geom,
(row_number() OVER (PARTITION BY eol.id ORDER BY distance asc))=1 as test
FROM eol JOIn graph ON eol.id = graph.id
) foo
WHERE test = true
UNION ALL ---here start the recursive part
SELECT id, link_id, depth, path, start_id, geom FROM (
SELECT graph.id, graph.link_id, r.depth+1 as depth,
path || graph.link_id as path,
r.start_id,
ST_union(r.geom,graph.geom) as geom,
(row_number() OVER (PARTITION BY r.id ORDER BY distance asc))=1 as test
FROM recurse_eol r JOIN graph ON r.link_id = graph.id AND NOT graph.link_id = ANY(path)) foo
WHERE test = true AND depth < 1000), --this last line is a safe guard to stop recurring after 1000 run adapt it as needed
このテーブルを「recurse_eol」と呼びましょう
各始点で最も長い線のみを保持し、完全に重複するすべてのパスを削除します例:パス1、2、3、5および5、3、2、1は、2つの異なる「線の終わり」によって検出された同じ線です
result as (SELECT start_id, path, depth, geom FROM
(SELECT *,
row_number() OVER (PARTITION BY array(SELECT * FROM unnest(path) ORDER BY 1))=1 as test_duplicate,
(max(depth) OVER (PARTITION BY start_id))=depth as test_depth
FROM recurse_eol) foo
WHERE test_depth = true AND test_duplicate = true)
SELECT * FROM result
残りのエラー(孤立したポイント、重複する線、奇妙な形のストリート)を手動でチェックします
約束どおりに更新されましたが、同じ行の反対のEOLから開始したときに再帰クエリがまったく同じ結果を返さない場合があるので、現時点で結果レイヤーに重複が残っている理由を理解できません。
このコードにはもっとコメントが必要だと私は完全に理解しています。以下は完全なクエリです。
WITH RECURSIVE
points as (SELECT id, st_transform((st_dump(wkb_geometry)).geom,2154) as geom, my_comment as com FROM mypoints),
roads as (SELECT st_transform(ST_union(wkb_geometry),2154) as geom from highway),
graph_full as (
SELECT a.id, b.id as link_id, a.com, st_makeline(a.geom,b.geom) as geom, st_distance(a.geom,b.geom) as distance
FROM points a
LEFT JOIN points b ON a.id<>b.id
WHERE st_distance(a.geom,b.geom) <= 15
),
graph as (
SELECt graph_full.*
FROM graph_full RIGHT JOIN
roads ON st_intersects(graph_full.geom,roads.geom) = false
),
eol as (
SELECT points.* FROM
points JOIN
(SELECT id, count(*) FROM graph
GROUP BY id
HAVING count(*)= 1) sel
ON points.id = sel.id),
recurse_eol (id, link_id, depth, path, start_id, geom) AS (
SELECT id, link_id, depth, path, start_id, geom FROM (
SELECT eol.id, graph.link_id,1 as depth,
ARRAY[eol.id, graph.link_id] as path,
eol.id as start_id,
graph.geom as geom,
(row_number() OVER (PARTITION BY eol.id ORDER BY distance asc))=1 as test
FROM eol JOIn graph ON eol.id = graph.id
) foo
WHERE test = true
UNION ALL
SELECT id, link_id, depth, path, start_id, geom FROM (
SELECT graph.id, graph.link_id, r.depth+1 as depth,
path || graph.link_id as path,
r.start_id,
ST_union(r.geom,graph.geom) as geom,
(row_number() OVER (PARTITION BY r.id ORDER BY distance asc))=1 as test
FROM recurse_eol r JOIN graph ON r.link_id = graph.id AND NOT graph.link_id = ANY(path)) foo
WHERE test = true AND depth < 1000),
result as (SELECT start_id, path, depth, geom FROM
(SELECT *,
row_number() OVER (PARTITION BY array(SELECT * FROM unnest(path) ORDER BY 1))=1 as test_duplicate,
(max(depth) OVER (PARTITION BY start_id))=depth as test_depth
FROM recurse_eol) foo
WHERE test_depth = true AND test_duplicate = true)
SELECT * FROM result