Lesson 09

カメラ → 最寄り避難所 — BallTree(haversine) で災害時モニタリング適性を評価

v2-rewrite選択応用基礎空間統計最近傍探索ノンパラ検定
所要 120分 / 想定レベル: 応用基礎 / データ: 2データ横断: #1279 (カメラ351台) + #42 (避難所4,065件)

データ取得手順

⚠️ このスクリプトは自動取得に対応していません。以下のデータセットを DoBoX から手動でダウンロードし、data/extras/ 以下に保存してください。

IDデータセット名
#42避難所情報
#888都市計画区域情報_区域データ_安芸高田市_行政区域
#1279県内のカメラ情報

実行コマンド:

cd "2026 DoBoX 教材"
python -X utf8 lessons/L09_nearest_camera.py

DoBoX のオープンデータは申請不要・商用/非商用とも利用可。 data/extras/.gitignore 対象(約 57 GB のキャッシュ)。 スクリプト実行で自動再生成されます。

学習目標と問い

このレッスンで答えたい問い

「広島県の防災カメラ351台は、災害時に最寄り避難所をどれくらい近くから見守れるか? そして、その近さは災害種別・カメラ管理区分・地理位置でどう変わるか?」

用語の定義(このレッスン独自)

  • 「カメラ」: DoBoX #1279 に登録された 広島県の防災カメラ 351台(道路・河川・ため池・海岸など)。 ライブ映像を配信できる固定地点。緯度経度と「管理区分」を持つ。
  • 「避難所」: DoBoX #42 の 指定避難所 4,065件。各避難所に災害種別フラグ(洪水/土砂/高潮/地震/津波)が付与されており、 その災害時に開設対象になるかが分かる。
  • 「最寄り距離」: 2点間の 地球表面上の直線距離(haversine 距離, km)。 道路距離ではなく球面上の最短距離。
  • 「孤立カメラ」: 最寄り避難所までの距離が 5km を超えるカメラ(本レッスンの定義)。 「映像を撮っても、近くに人々が集まる避難所がない」状態。
  • 「カバレッジ」: あるカメラが半径 d km 以内に 何個の避難所を持っているか。 1個だけでは故障時に代替が効かないため、k=3 や k=5 で「冗長性」を測る。
  • 「BallTree」: 大量の点の中から 「最寄り N 個」を高速に取り出すデータ構造。 ツールとして使う:点群を入れて木を作り、別の点を投げると最寄り N 個が返る。内部の球面三角法は黒箱でOK
  • 「haversine(ハーバサイン)距離」: 緯度経度の2点間の地球表面距離を計算する公式の名前。 道路距離ではなく球面上の直線距離(メートル単位の正確な距離)。本レッスンでは BallTree のオプションとして指定するだけで使える。
  • 「ECDF」: Empirical Cumulative Distribution Function = 累積分布関数。 「距離 d 以内に何 % のカメラが入るか」を d を横軸に取って積み上げたグラフ。 ヒストグラムが「区間ごとの個数」なら、ECDF は「閾値以下の割合」を表す。複数カテゴリの分布を 1枚で重ねて比較できるのが利点。

立てた仮説

  1. H1(最寄り距離は十分に近い): 広島県のカメラ網は密に整備されているはず。 カメラ→最寄り避難所の 中央値は 1km 以下、95%値も数km 以内に収まると予想。 ただし右に長い裾を持ち、山間部・島嶼部に「孤立カメラ」が少数存在するはず。
  2. H2(災害種別で見える避難所が違う): 津波・高潮対応避難所は 沿岸偏在のため、 山間部のカメラから見ると最寄りでも遠くなるはず。 逆に洪水・土砂対応は内陸全域にあるため、すべてのカメラから近いはず。 同じカメラでも「対応災害」のフィルタ次第で実用価値が変わる
  3. H3(冗長性は近距離で急減する): 「最寄り 1個(k=1)」が 1km 以内に入るカメラは多くても、 「3個まとめて 1km 以内(k=3)」に入るカメラは大幅に少ないはず。 1台が故障した瞬間に代替手段がない地域が浮かび上がる。
  4. H4(カメラ起点 vs 避難所起点 は非対称): 351台のカメラと 4,065件の避難所では、 母数が10倍以上違うため、カメラ→避難所(n=351)と避難所→カメラ(n=4065)の最寄り距離分布は形が大きく違うはず。 避難所側はカメラを持たない山地・離島が多く、長距離の裾が厚くなる。
  5. H5(管理区分で距離分布が違う): カメラの管理区分は道路・河川・ため池・海岸ほか で、 それぞれ 立地ロジックが違う。 道路カメラは集落間の長い県道・国道沿いに点在するため避難所まで遠いペアが混じる一方、 河川・海岸ほか は集落隣接の被害想定地点に置かれ、避難所と隣接しやすい。 区分間で距離分布の中央値に有意差があるはず(Kruskal-Wallis 検定で確認)。

到達点

使用データ

本レッスンは 2データセットを「位置で結合」する空間結合の典型例。 2つのデータには共通IDがないため、テーブル JOIN ではなく 「最寄り点を引く」ことで結合する

ダウンロード(再現用データ・中間データ・図)

本レッスンの全成果物に直リンクを置いた。途中ステップから再現したい学習者向け。

1. 生データ(DoBoX 由来)

ファイル形式サイズ取得元
data/camera_list.csv CSV (緯度・経度・住所・管理区分)約 70 KB / 351 行 DoBoX #1279
data/shelters.json JSON (4,065 items, 災害5フラグ付)約 4 MB DoBoX #42

2. プログラムで生成される中間データ(CSV 直リンク)

ファイル内容使う分析
L09_nn_distances.csv 351カメラ × (最寄り避難所名・市区町村・距離km)分析1 ヒスト / 分析5 マップ
L09_topk_distances.csv 351カメラ × k=1..5 番目に近い避難所までの距離行列分析3 カバレッジ
L09_distances_by_hazard.csv 5災害種別 × 351カメラ の最寄り対応避難所距離(縦持ち)分析2 ECDF
L09_topk_coverage.csv k×半径dの 2D 表(カバレッジ曲線の元データ)分析3 カバレッジ
L09_shelter_to_camera.csv 4,065避難所 × 最寄りカメラ距離(逆方向)分析4 非対称性
L09_category_summary.csv 管理区分4区分の集計(n, 中央値, p95, max, 孤立数)分析5 KW検定

3. 図 PNG / インタラクティブマップ

4. 再現スクリプト

cd "2026 DoBoX 教材"
py -X utf8 lessons/L09_nearest_camera.py

スクリプト本体: lessons/L09_nearest_camera.py

分析1: カメラ→最寄り避難所の距離分布

狙い

「広島県の351台の防災カメラは、最寄り避難所までどれくらい近いか?」 分布の中央値・95%値・最大値を見て、整備密度の全体像を把握する(仮説H1)。

使う道具: BallTree(haversine)

役割(一文で): 大量の点の中から「最寄り N 個」を高速に取り出すツール。 中身は「点を木構造に整理しておくと、新しい点を投げたときに端から全部測らなくて済む」というアイデア。 本レッスンでは 使い方だけを覚える。内部の球面三角法・木の構築アルゴリズムは黒箱でOK。

ステップこのツールの操作入力出力
準備BallTree(点群, metric="haversine") で「木」を作る 避難所4,065件のラジアン座標 (4065×2)木オブジェクト1つ
クエリtree.query(別の点群, k=N) で N 個の最寄りを取る カメラ351台のラジアン座標 (351×2)距離行列 (351×N) と インデックス行列 (351×N)

注意点 3つ:

1台のカメラがどう距離を得るか — 最初から最後まで追う表(カメラ「苗代」の例)

カメラ #1「苗代」(呉市苗代町)が、避難所4,065件の中から最寄り1件を引いて km 距離を得るまでの流れ。 1台のカメラに対する操作を 段階ごとに追う。

段階このカメラで何が起きるかサイズ
① 緯度経度を取得(34.297405, 132.592275) を CSV から読む1×2 の度数値
② ラジアン変換(0.59866, 2.31416) ラジアンに変換1×2 のラジアン値
③ 木にクエリtree_sh.query([[0.59866, 2.31416]], k=1) を呼ぶクエリ点 1×2 を投げる
④ 木の内部処理4,065件の避難所を全件比較せず、近そうな候補だけ計算(黒箱)
⑤ ラジアン距離が返る0.0000567 ラジアン と インデックス 1234 を返す距離 1個 + index 1個
⑥ km に直す0.0000567 × 6371 = 0.36 km1スカラー
⑦ インデックスから避難所名sh.iloc[1234] で「苗代住吉神社」「呉市」を取得1行
⑧ 結果を保存カメラ「苗代」→ 最寄り避難所「苗代住吉神社」(呉市, 0.36km)表に1行追加

※ 緯度経度は実データ。距離値は概念的な例(実行結果は L09_nn_distances.csv で確認可)。 これを 351 台すべてで一気に行うのが tree.query(cam_rad, k=1) の中身。

このツールの限界

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import json, numpy as np, pandas as pd
from sklearn.neighbors import BallTree

EARTH_R = 6371.0  # km (地球半径)

# (1) 読み込み
cam = pd.read_csv("data/camera_list.csv")
cam["lat"] = pd.to_numeric(cam["緯度"], errors="coerce")
cam["lon"] = pd.to_numeric(cam["経度"], errors="coerce")
cam = cam.dropna(subset=["lat", "lon"]).reset_index(drop=True)

with open("data/shelters.json", encoding="utf-8") as f:
    sh = pd.DataFrame(json.load(f)["items"])
sh["lat"] = pd.to_numeric(sh["latitude"],  errors="coerce")
sh["lon"] = pd.to_numeric(sh["longitude"], errors="coerce")
sh = sh.dropna(subset=["lat","lon"]).reset_index(drop=True)

# (2) ラジアン変換 — haversine の前提
cam_rad = np.radians(cam[["lat","lon"]].values)   # 351 × 2
sh_rad  = np.radians(sh[["lat","lon"]].values)    # 4065 × 2

# (3) 避難所側で BallTree を作り、カメラを query
tree_sh = BallTree(sh_rad, metric="haversine")
d_rad, idx = tree_sh.query(cam_rad, k=1)          # k=1: 最寄り1個だけ

# (4) ラジアン距離 → km
cam["nearest_shl_km"]   = d_rad.flatten() * EARTH_R
cam["nearest_shl_name"] = sh.iloc[idx.flatten()]["name"].values

dists = cam["nearest_shl_km"].values
print(f"中央値 {np.median(dists):.2f} km / 95%値 {np.percentile(dists,95):.2f} km")

結果(図と読み取り)

なぜこの図か: 連続値の偏りを直感的に見たいから ヒストグラム。 中央値(赤)と 95%値(橙)の縦線を加えて、「典型的なカメラ」と「外れ値カメラ」の境界を視覚化する。

図1: 全カメラ (n=351) → 最寄り避難所 距離分布。中央値 0.54 km, 95%値 2.19 km, 最大 5.42 km。
図1: 全カメラ (n=351) → 最寄り避難所 距離分布。中央値 0.54 km, 95%値 2.19 km, 最大 5.42 km。

この図から読み取れること:

結果(表と読み取り)

なぜこの表か: ヒストグラムの「右の裾」に何があるかを具体的なカメラ名で確認する。

表1: カメラから最も遠い避難所 TOP10(孤立候補)
カメラ名 住所 管理区分 距離(km) 最寄り避難所 市区町村
智教寺 安芸高田市美土里町生田 道路 5.42 生田集会所 安芸高田市
飯山 廿日市市飯山 道路 4.56 第一集会所 廿日市市
吉和(R434別れ) 廿日市市吉和 道路 4.55 第一集会所 廿日市市
羽出庭 三次市三和町羽出庭 道路 4.13 三次市三和支所 三次市
西城油木 庄原市西城町油木 道路 3.49 前油木老人集会所 庄原市
ため池カメラ(鳥屋ヶ森) 広島市安佐北区可部町大字綾ヶ谷 ため池 3.39 可部運動公園管理事務所 広島市安佐北区
三和 三次市三和町上壱 道路 3.04 三次市立三和中学校 三次市
ため池カメラ(奥桧山) 広島市安佐北区大林町 ため池 2.86 三入東学区集会所 広島市安佐北区
便坂トンネル西 三次市作木町上作木 道路 2.79 布野生涯学習センター 三次市
雲通 三次市吉舎町雲通 道路 2.78 三次市立八幡小学校 三次市

この表から読み取れること: 上位は山間部・島嶼部・海岸線のカメラに偏る。「ため池」「海岸ほか」管理のカメラが多い — 管理区分との関連は分析5で検証する。

分析2: 災害種別ごとの「対応避難所」最寄り距離

狙い

「同じカメラでも、対応災害が違うと最寄り避難所までの距離は変わるか?」 津波・高潮対応避難所は沿岸偏在のため、山間部のカメラから遠いはず(仮説H2)。

使う道具: ECDF(累積分布関数)

役割(一文で): 「距離 d 以下のカメラの割合」を d を横軸に取って積み上げたグラフ。 ヒストグラムが「区間ごとの個数」を縦に積むのに対し、ECDF は「閾値以下の割合」を 0%→100% に向かって右肩上がりにする。

なぜここで ECDF か: 5つの災害種別を同じ図に重ねたい。 ヒストグラムを5つ重ねると棒が混ざって読めないが、ECDF は なので5本重ねても識別できる。 さらに「中央値は何km か」(曲線が y=0.5 を横切る x 値)が一目で分かる。

動作のイメージ:

  1. カメラごとの距離(例: 351個の数値)をソートする
  2. x軸 = 距離、y軸 = 「この距離以下のカメラの割合(1/n, 2/n, ..., n/n)」を階段状にプロット
  3. 曲線が左上にあるほど「短い距離で多くがカバーされる」=その災害には強い整備

処理の流れ(5災害ループ):

ステップ操作サイズ
① 災害フラグでフィルタ避難所4065件 → flag=1 の部分集合例: 洪水避難所 ≒ 3千件
② BallTree 構築その部分集合だけで木を作る木1つ(災害ごと)
③ 351カメラで querytree.query(cam_rad, k=1)距離 351個
④ ECDF 計算距離をソートして 1/n, 2/n, ... の階段関数(351点, 351点)
⑤ 5災害ぶんを重ね描き線色を災害ごとに変えて step plot1枚の図

実装

L09_nearest_camera.py 行 698–741

 1
 2
 3
 4
 5
 6
 7
 8
 9
707
708
709
710
711
712
713
714
715
716
717
HAZARDS = {
    "floodShFlg": "洪水", "sedimentDisasterShFlg": "土砂",
    "stormSurgeShFlg": "高潮", "earthquakeShFlg": "地震",
    "tsunamiShFlg": "津波",
}
hazard_results = {}
for col, label in HAZARDS.items():
    sh_sub = sh[sh[col] == 1].reset_index(drop=True)   # 災害フラグでフィルタ
    sub_rad = np.radians(sh_sub[["lat","lon"]].values)
    tree_sub = BallTree(sub_rad, metric="haversine")    # 災害ごとに木を作り直し
    d, _ = tree_sub.query(cam_rad, k=1)                 # 全カメラ351台でクエリ
    hazard_results[label] = d.flatten() * EARTH_R       # km

# ECDF を重ね描き
fig, ax = plt.subplots(figsize=(9, 5))
for label, ds in hazard_results.items():
    xs = np.sort(ds)                                    # 距離を昇順ソート
    ys = np.arange(1, len(xs) + 1) / len(xs)            # 1/n, 2/n, ..., 1.0
    ax.step(xs, ys, where="post", label=label)
ax.axhline(0.5, color="gray")                           # 中央値の補助線

結果(図と読み取り)

なぜこの図か: 5本の分布を 1枚で比較したい。線が左にあるほど短距離で多くカバー。 y=0.5 の補助線で中央値、y=0.95 で 95%値が読める。

図2: 災害種別ごとの「カメラ→対応避難所」最寄り距離 ECDF。線が右にずれるほど遠い。
図2: 災害種別ごとの「カメラ→対応避難所」最寄り距離 ECDF。線が右にずれるほど遠い。

この図から読み取れること:

結果(表と読み取り)

表2: 災害種別 サマリ(避難所数 / 中央値 / 95% / 最大)
災害 対応避難所数 中央値(km) 95%値(km) 最大(km)
洪水 3071 0.71 2.81 5.42
土砂 2557 0.69 3.14 6.91
高潮 2015 5.42 35.02 58.54
地震 2337 0.70 2.78 6.91
津波 1340 5.35 38.19 57.78

この表から読み取れること: 津波・高潮の中央値・95%値が他の3災害より明確に大きい。 「対応避難所数」も津波が最少。避難所の絶対数沿岸偏在の二重効果で距離が伸びている。

分析3: top-k カバレッジ曲線(冗長性の評価)

狙い

「最寄り1個だけでなく、3個・5個まとめて何 km 以内にあるか?」 1個だけでは故障時に代替が効かない。災害インフラの「冗長性」を可視化する(仮説H3)。

使う道具: BallTree の k=多 クエリ

1個だけ(k=1)でなく、k=3 や k=5 の最寄りを一気に取るのが BallTree のもう1つの強み。 返ってくるのは 距離行列 (351×k)。j 列目は「j 番目に近い避難所までの距離」。

k=1 (最寄り1個)k=3 (3個目)k=5 (5個目)
「最寄り避難所」までの距離「3つ揃えるのに必要な半径」「5つ揃えるのに必要な半径」
近いほど通常運用◎近いほど1台壊れても安全近いほど多重冗長

カバレッジ曲線の作り方:

  1. 半径 d を 0.5km から 10km まで 40 点でスキャン
  2. 各 d について「k 番目に近い避難所までの距離が d 以下のカメラ」の割合を計算
  3. x=d, y=割合(%) でプロット。k=1, 3, 5 を別線色で重ね描き

実装

L09_nearest_camera.py 行 760–791

 1
 2
 3
 4
 5
 6
 7
 8
 9
769
770
771
772
773
# k=5 まで一気に取る (351カメラ × 5列の距離行列)
d_topk_rad, _ = tree_sh.query(cam_rad, k=5)
d_topk_km = d_topk_rad * EARTH_R                # 351 × 5

# 半径 0.5..10km を 40点でスキャン
ds_grid = np.linspace(0.5, 10.0, 40)
cov_curves = {}
for k in (1, 3, 5):
    d_k = d_topk_km[:, k - 1]                   # k番目の距離 (1-indexed)
    cov_curves[k] = np.array([(d_k <= d).mean() for d in ds_grid])

# プロット
for k in (1, 3, 5):
    ax.plot(ds_grid, cov_curves[k] * 100, label=f"k≥{k}")

結果(図と読み取り)

なぜこの図か: 「半径」を変えながら「カバー率」を見る 感度分析。 3線を重ねることで「k を増やすと曲線がどれだけ右にずれるか」=冗長性のコストが分かる。

図3: top-k カバレッジ曲線。k=1 (青) → k=3 (橙) → k=5 (赤) と曲線が右にずれる。
図3: top-k カバレッジ曲線。k=1 (青) → k=3 (橙) → k=5 (赤) と曲線が右にずれる。

この図から読み取れること:

結果(表と読み取り)

表3: 主要半径での top-k カバレッジ(カメラの何 % が条件を満たすか)
k d=1km d=3km d=5km
1 72.6% 98.0% 99.7%
3 47.6% 83.2% 94.9%
5 33.0% 76.6% 90.9%

この表から読み取れること: 同じ d=1km でも k=1 と k=3 ではカバー率が大きく違う。 1km 圏内に 3個の避難所を持てるカメラは限定的で、「冗長な見守り体制」は地理的に均等ではないことが定量化できた。

分析4: 避難所→最寄りカメラ(逆方向の比較)

狙い

「カメラ起点(n=351)と避難所起点(n=4065)では、最寄り距離分布はどう違うか?」 「クエリ側を入れ替える」だけで問題定義が変わることを実演する(仮説H4)。

使う道具: BallTree の役割を入れ替える

分析1〜3 では 避難所側の木にカメラを投げた(カメラ視点)。 分析4 では カメラ側の木に避難所を投げる(避難所視点)。木の構築先が入れ替わるだけ。

分析の方向木に入れる点群クエリする点群意味
カメラ→避難所 (分析1)避難所 4,065件カメラ 351台「各カメラが何をモニタできるか」
避難所→カメラ (分析4)カメラ 351台避難所 4,065件「各避難所は映像で見守られているか」

n が10倍以上違うので結果も大きく非対称になる。 カメラ351台に対して4,065件の避難所すべてに「最寄り1台のカメラ」を割り当てると、必然的に 遠いペアが大量発生する。

実装

L09_nearest_camera.py 行 813–832

813
814
815
816
817
818
819
820
# 木の構築先を逆にするだけ
tree_cam = BallTree(cam_rad, metric="haversine")   # カメラ351台で木を作る
d_inv_rad, _ = tree_cam.query(sh_rad, k=1)         # 避難所4065件をクエリ
d_inv_km = d_inv_rad.flatten() * EARTH_R

# カメラ起点 (dists) と避難所起点 (d_inv_km) を 1図で比較
ax.hist(dists,    bins=bins, alpha=0.65, label="カメラ→避難所 (n=351)")
ax.hist(d_inv_km, bins=bins, alpha=0.55, label="避難所→カメラ (n=4065)")

結果(図と読み取り)

なぜこの図か: 同じ「最寄り距離」でも母数が違う2分布を 1枚で重ねると非対称性が一目で分かる。 ビン幅を揃えてある(同じ x 軸)ので、形状の違いが面積比較できる。

図4: カメラ起点 (青, n=351) と避難所起点 (赤, n=4065) の最寄り距離ヒスト比較。
図4: カメラ起点 (青, n=351) と避難所起点 (赤, n=4065) の最寄り距離ヒスト比較。

この図から読み取れること:

分析5: 孤立カメラ地理マップ + 管理区分別 Kruskal-Wallis 検定

狙い

(A) 「孤立カメラはどこに集中するか?」 — 地理プロットで可視化。
(B) 「管理区分(道路/河川/ため池/海岸ほか)で距離分布は違うか?」 — 仮説H5を Kruskal-Wallis 検定で判定。

使う道具: 孤立判定 + Kruskal-Wallis 検定

(A) 孤立判定: 分析1で計算済みの最寄り距離に対し、5km 超を「孤立」、2km 超を「準孤立」(95%値ライン)と定義。 散布図を経度・緯度で描き、3カテゴリを色・形・サイズで分けて重ねる。背景に避難所を薄灰色で。

(B) Kruskal-Wallis 検定: 「複数グループの中央値が同じか?」を判定する ノンパラメトリック検定

実装

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from scipy import stats

# (A) 孤立判定
ISOLATED_TH  = 5.0   # km: 政策的閾値
ISOLATED_TH2 = 2.0   # km: 95%値ライン
cam["isolated"]  = cam["nearest_shl_km"] > ISOLATED_TH
cam["isolated2"] = cam["nearest_shl_km"] > ISOLATED_TH2

# 散布図で3カテゴリを色分け(経度×緯度, cosφ補正で縦横比)
ax.scatter(sh["lon"], sh["lat"], s=2, color="gray", alpha=0.35)   # 背景
ax.scatter(ok["lon"], ok["lat"], s=18, color="blue")              # 通常
ax.scatter(mid["lon"], mid["lat"], s=42, color="orange")          # 準孤立
ax.scatter(iso["lon"], iso["lat"], s=160, color="red", marker="X")# 孤立
ax.set_aspect(1.21)   # cosφ補正 (φ≈34.5°)

# (B) 管理区分を 4 グループに集約
def grp4(x): return x if x in ("道路","河川","ため池") else "海岸ほか"
cam["区分4"] = cam["管理区分"].apply(grp4)
groups = [cam.loc[cam["区分4"]==g, "nearest_shl_km"].values
          for g in ["道路","河川","ため池","海岸ほか"]]

# Kruskal-Wallis: 4グループの中央値が同じか検定
H, p = stats.kruskal(*groups)
print(f"H={H:.3f}, p={p:.4g}")

結果A(図と読み取り)— 孤立カメラの地理分布

なぜこの図か: 「孤立カメラがどこに偏在するか」は 地図を見れば一発。 3カテゴリを色・サイズ・形で同時にエンコードし、避難所を背景に薄く描くことで「カメラと避難所の 密度の地理的ミスマッチ」が見える。

図5A: 孤立カメラ (赤X, >5.0km, n=1) と 準孤立 (橙○, 2.0–5.0km, n=25) の地理分布。
図5A: 孤立カメラ (赤X, >5.0km, n=1) と 準孤立 (橙○, 2.0–5.0km, n=25) の地理分布。

この図から読み取れること:

結果B(図と読み取り)— 管理区分別 箱ひげ + KW 検定

なぜこの図か: 4グループの分布の重なりと中央値差を一目で見たい → 箱ひげ。 Kruskal-Wallis の H 値・p 値をタイトルに併記して、定性的な印象(中央値の差)を統計的根拠で裏付ける。

図5B: 管理区分4区分別 最寄り避難所距離 — Kruskal-Wallis H=46.95, p=3.56e-10。
図5B: 管理区分4区分別 最寄り避難所距離 — Kruskal-Wallis H=46.95, p=3.56e-10。

この図から読み取れること:

結果(表と読み取り)

表4: 管理区分別 距離サマリ
区分 n_台 中央値_km 平均_km p95_km 最大_km 孤立_5km超
道路 131 0.74 1.05 2.78 5.42 1
河川 120 0.41 0.56 1.37 1.98 0
ため池 70 0.74 0.88 2.11 3.39 0
海岸ほか 30 0.18 0.24 0.63 1.25 0

この表から読み取れること: 中央値で見ると 海岸ほか(0.18km) < 河川(0.41km) < 道路(0.74km) ≈ ため池(0.74km) の順で集落距離が伸びる。 最大値・95%値は道路・ため池が他より大きく、区分の管理目的そのもの(道路は集落間の主要道監視、ため池は山間貯水池監視)が距離分布に直接表れている。

インタラクティブマップ(folium)

各カメラ・避難所をクリックすると詳細ポップアップ(カメラ名・住所・最寄り避難所など)が表示される。 レイヤ切替で「通常/準孤立/孤立/避難所」を個別表示可。

仮説検証と考察

仮説と結果の照合

#仮説判定根拠
H1カメラの最寄り避難所距離は中央値1km以下、95%値も数km、ただし右の長尾あり支持 図1 で中央値 0.54km、95%値 2.19km、最大 5.42km。 分布は強い右非対称で、長尾の正体は山間部・島嶼部の少数カメラ(表1)。
H2津波・高潮対応避難所はカメラから遠い(沿岸偏在)支持 図2 のECDFで津波・高潮の線が右にずれる。表2 で中央値・95%値が他3災害より明確に大きい。 対応避難所数の絶対差と地理的偏在の二重効果。
H3k=1 と k=3 でカバレッジは大きく違う(冗長性は近距離で急減)支持 図3 のカバレッジ曲線で k=3 の線は k=1 から大きく右シフト。 表3 で同じd=1km での k=1 vs k=3 のカバー率差を定量確認。
H4カメラ起点 vs 避難所起点 の最寄り距離分布は非対称支持 図4 で避難所起点(赤, n=4065)の中央値 1.73km、p95 5.73km。 カメラ起点(青, n=351, 中央値 0.54km)と分布形が大きく違う。 「クエリ側を入れ替えると問題定義が変わる」体験。
H5管理区分別で距離分布に有意差あり(区分間で立地ロジックが違う)支持 図5B で Kruskal-Wallis H=46.95, p=3.56e-10(高度に有意)。 表4 で中央値順位は 海岸ほか(0.18km) < 河川(0.41km) < 道路 ≈ ため池(0.74km)。 ただし方向は当初予想と一部異なり、海岸ほか は最も避難所に近い(港湾集落隣接)。 道路・ため池は山間部の長距離区間に置かれるため遠いペアが混じる。

考察

発展課題(結果から導かれる新たな問い)

各課題は、上の結果新たな仮説に裏打ちされている。 「結果X → 新仮説Y → 課題Z」の3段で記述する。

  1. 道路ネットワーク距離での再評価
    • 結果X: haversine 距離は球面上の直線で、山岳部の実走行距離より 短く出る。本レッスンの「最寄り 0.36km」は実際は徒歩 1km の可能性がある
    • 新仮説Y: 山間部では 直線距離と道路距離の比率(歪度)が平地の数倍に達する。本レッスンの「孤立カメラ {n_iso}台」は実は2倍以上の実距離を持つ可能性
    • 課題Z: OSRM や OpenStreetMap routing API でカメラ-避難所間の 実走行距離を取得し、本レッスンの直線距離との散布図を描く。山間部で直線距離が短くても道路距離が大きいペアを抽出
  2. 夜間人口で重み付けした「実需要」評価
    • 結果X: 図4 で避難所起点 (n=4065) の最寄りカメラは長尾だが、避難所4065件には 収容人数(capacity)が大きく違う。1人収容の小さな避難所と1000人収容の大きな避難所を同列に扱っている
    • 新仮説Y: capacity や夜間人口で重み付ければ、「実際に多くの人を見守るべき避難所」のうち何%がカメラから遠いかが正しく評価できる
    • 課題Z: 国勢調査メッシュ(500mメッシュ)で各避難所周辺の夜間人口を集計し、距離の累積分布を「人重み」「件数重み」で2本描いて比較
  3. クラスタ数 k のスイープと最適配置
    • 結果X: 図3 で k=3 の曲線は k=1 から大きく右シフト。「3個の冗長確保」が困難な地域がある
    • 新仮説Y: 「孤立カメラ {n_iso}台 と 準孤立 {n_iso2-n_iso}台 を 0 にするには、何台を どこに追加すれば足りるか?」が定量化できる
    • 課題Z: 整数計画 (set cover) または貪欲 set cover で、孤立カメラ周辺に追加カメラを配置するシミュレーション。 地点候補 = 主要道路の交差点に絞ることで現実的な最適化に
  4. 標高差込みの「届く映像」評価
    • 結果X: 本レッスンは 水平距離だけを測っている。実際にカメラ映像が避難所まで「届く」かは標高差・遮蔽物に依存する
    • 新仮説Y: 5mDEM でカメラ-避難所間の標高差を引き、下り坂で見通しがあるペアと 山稜越しのペアを区別すれば、「実用的な見守り距離」分布は本レッスンの結果よりさらに長尾になる
    • 課題Z: DoBoX の 5mDEM (標高ラスタ) を読み込み、カメラ-避難所の line of sight (LoS) を ray casting で判定。LoS が通るペアだけで再分析
  5. 避難所→カメラ ランキングで「映像のない市町村」を抽出
    • 結果X: 図4・分析4 で避難所起点の長尾を確認したが、市町村別にどう偏在しているかは未解析
    • 新仮説Y: 市町村別に「最寄りカメラまで5km超の避難所の割合」を集計すると、特定の山間市町村が上位に並ぶ。これらは 映像情報による災害支援が届きにくい地域である
    • 課題Z: L09_shelter_to_camera.csvmuni でグルーピングし、5km超避難所率を市町村ランキングに。 上位市町村に対して整備優先度を提案するレポートを作成