カンケイを可視化する（Relation を PostGIS + QGIS で可視化：OSM）

## 背景
OSM には **データ間の関係を定義する Relation** という概念があります。
この構造を使って様々な位置情報が表現されています。
[参考](https://wiki.openstreetmap.org/wiki/JA:%E3%83%AA%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)
今回、著者の作業において、**特定の Way に関連する Point の構造を可視化** する必要があり、Relation を持つ Point と Way の可視化をトライしましたので、本記事ではその内容を整理しています。

今回は、PostgreSQL + PostGIS を活用し、極力データ処理を SQL で実施し、QGIS はデータの読み込みを中心に担当する形で作業を進めました。  
今回取り上げていませんが、QGIS でも 「Points to Path」や「Join by Lines」などの機能があり、これらでも同様のことが実現できることも確認しました。しかし、データが増えると著者の環境ではQGISでの処理が非常に重くなってしまったため、より様々な環境で利用できるよう、PostGIS で SQL を使って事前に処理し、QGIS に効率的に可視化する方法を記事として取り上げることにしました。

本記事ではこの手法を整理しましたが、他にもより良い方法があるかもしれません！  
もし「こういうアプローチも良いのでは？」というアイデアがあれば、ぜひフィードバックいただけると嬉しいです！


## 必要な環境
### 使用ツール
- **PostgreSQL + PostGIS**
- **osm2pgsql** (OSM データを PostGIS に取り込むツール)
- **QGIS** (PostGIS データを可視化する GIS ソフト)

### OSM データのインポート
まず、OSM データを PostGIS に取り込む必要があります。
こちらのインポートについては以下の記事に詳細解説しておりますので、ご参考ください。
※主にPBFでの取り扱いになります。

[QGISでPostGISでのPBFデータの読み込み方法](https://qiita.com/AshMap/items/b6f7177ea75584619f0f)
[OSMデータをPostgreSQLに取り込む：hstore拡張の有無による違い](https://qiita.com/AshMap/items/602e90d429905a5689d6)
[osm2pgsqlで特定のタグキーを指定してデータを効率的に取り込む(PBF)](https://qiita.com/AshMap/items/ca37decc8bf16f981827)
[バウンディングボックスでOpenStreetMapデータ部分的に抽出する](https://qiita.com/AshMap/items/a2851c79b7ad11bb5044)

インポート後、以下の **PostGIS テーブル** が作成されます：
- `planet_osm_point` → **OSM のノード (Point)**
- `planet_osm_roads` → **OSM の道路 (Way)**
- `planet_osm_rels` → **OSM のリレーション (Relation)**

今回上記のうちQGIS では `planet_osm_point` と `planet_osm_roads` を直接取り込み活用し、SQL で追加で `relation_lines`というテーブルを作成して取り込みます。


## Relation に属する Point と Way のデータを取得
PostGIS を使い、**Relation に含まれる Point (ノード) と、それに対応する Way (道路) を取得し、Point から Way の中心点へ向かう線を作成** します。
目指すのは、あるPointデータからそのPointがリレーションを持っているWayのライン中心に向かって線を引くイメージです。

以下の方法で、この線（Linestring）を抽出します。

```sql
WITH relation_points AS (
    -- Relation に含まれる Point (Node) を取得
    SELECT
        r.id AS relation_id,
        p.osm_id AS point_id,  -- Point の ID
        p.way AS point_geom,  -- Point のジオメトリ
        member->>'role' AS role  -- Relation 内での役割
    FROM planet_osm_rels AS r
    JOIN LATERAL jsonb_array_elements(r.members) AS member ON true
    JOIN planet_osm_point AS p ON p.osm_id::text = member->>'ref'
    WHERE member->>'type' = 'N'  -- Point (Node) のみ取得
),
relation_ways AS (
    -- Relation に含まれる Way (道路) の ID を取得
    SELECT
        r.id AS relation_id,
        member->>'ref' AS way_id  -- Way の ID
    FROM planet_osm_rels AS r
    JOIN LATERAL jsonb_array_elements(r.members) AS member ON true
    WHERE member->>'type' = 'W'  -- Way (道路) のみ取得
)
-- Point と Way を結合し、Way の中心点 & Point→Way の線を取得
SELECT
    rp.relation_id,
    rp.point_id,
    rw.way_id AS related_way_id,  -- Way ID を正しく取得
    rp.point_geom,
    ST_Centroid(w.way) AS way_center,  -- Way の中心点を計算
    ST_MakeLine(rp.point_geom, ST_Centroid(w.way)) AS relation_line,  -- Point から Way の中心への線を作成
    rp.role
INTO relation_lines;  -- 結果を保存
```

### 概要
- Relation に含まれる Point (Node) を取得
- planet_osm_rels.members から type = 'N' の要素を抽出
- planet_osm_point と結合して、Point のジオメトリ (point_geom) を取得
- role も取得して、relation_points として保持
- Relation に含まれる Way (道路) を取得
- planet_osm_rels.members から type = 'W' の要素を抽出
- way_id (Way の OSM ID) を取得して、relation_ways として保持
- Point と Way を relation_id で結合
- relation_points と relation_ways を relation_id で結合
- planet_osm_roads と way_id をキーに結合して、Way のジオメトリ (way_geom) を取得
- Way の中心点を計算
- ST_Centroid(w.way) を使って Way の中心点 (way_center) を計算
- Point から Way の中心点へ線を作成
- ST_MakeLine(rp.point_geom, ST_Centroid(w.way)) を使い Point → Way の中心点を結ぶ線 (relation_line) を作成
- 結果を保存
- テーブルとして保存 (INTO relation_lines;) → relation_lines テーブルを作成
- ビューとして作成 (CREATE VIEW relation_lines AS) → データを保存せず、最新の情報を常に取得

これで以下のようなテーブルを作成できます。

| relation_id | point_id | related_way_id | point_geom | way_center | relation_line | role |
|------------|---------|---------------|------------|------------|---------------|------|
| 12345 | 67890 | 112233 | (POINT) | (POINT) | (LINESTRING) | stop |
| 67890 | 34567 | 445566 | (POINT) | (POINT) | (LINESTRING) | entrance |

>上記のクエリで、PointとWayのOSM_IDも抽出していますが、これらの値を利用すれば、このテーブルをRelationのキーとして扱うことも可能です。

## QGIS でデータを可視化
### QGIS での手順
1. **QGIS の「DBマネージャー」を開く**
2. **PostGIS データベースに接続**
3. **以下のレイヤーを追加**
   - `planet_osm_point` → **Relation に属する Point**
   - `planet_osm_roads` → **OSM の道路 (Way)**
   - `relation_lines` → **Point から Way の中心点へ引かれた線**

下記がイメージです。ここでは赤点戦で関係値を示すラインを、Node（黒）とWay（青）の中心を繋いでいます。

![Screenshot 2025-02-09 at 13.07.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3789281/34ea74c4-3a71-b47f-c0ac-98410f031fe7.png)

>交差点の進行方向を示すrole = via　を表示

見た目については、QGIS上で適宜カスタマイズしてみてください。
例）Point の色を `role` に応じて変更するなどで視認性を上げる等。

## SQL を使った理由
最初にも記載したように、OSM_IDなどを利用して、点線間のリレーションを特定するキーがあれば、QGIS の「Points to Path」や「Join by Lines」など（他にも利用できるツールがあります）でも同様の結果を得ることが可能です。著者の環境で関東のデータないのRelationを利用したのですが、QGISのそれぞれのProccessing toolの作業が非常に時間を要し、効率的に処理するためにさらにデータの絞り込みなどを行う必要がありました。そこで、今回はPostGIS の `ST_MakeLine()` を活用し、QGIS で処理をせずに事前に線を作成する方法に切り替えました。

### **SQL のメリット**
- QGIS の負荷軽減
- 複雑な処理を PostGIS 側で最適化
- データが増えても高速に可視化が可能

## 今後の発展テーマ
### **Relation の Role をさらに分析**
今回の作業を通して、Relation の `role` が 関東エリアだけでも多岐にわたることがわかりました。例えば、今回利用した関東のデータだけでもroleの種類は235あり、数万レコードあるようなroleも存在していました。

様々な情報が定義されていることがわかる一方、仕様としてどれだけ統一してRelationデータを活用できるか？などは今後掘り下げていく必要があると思いました。
いずれにしても、Relationに関しても、より詳細な分析や仕様の確認を進めることで、新たなデータ活用の可能性が広がるように思います。

この記事が位置情報データ活用の参考になれば幸いです！