1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

到達圏を可視化する — OSMnxとNetworkXで徒歩10分圏マップを作る

1
Last updated at Posted at 2025-10-12

はじめに

SaaSや外部APIに頼らず Pythonだけで到達圏(isochrone)分析を行う方法を紹介します。
前回、全体像を整理したので、今回は OSMnxを用いて徒歩5分・10分・15分圏を可視化するハンズオンを行います。

使用する主要ライブラリは以下の通りです。

  • osmnx:OpenStreetMapから道路ネットワークを取得
  • networkx:グラフ探索・最短経路計算(Dijkstra法など)
  • geopandas:地理データの操作
  • folium:インタラクティブ地図描画

OSMnxは内部的にOpenStreetMapのAPIからデータを取得しますが、
一度取得したデータは自動的にキャッシュされるため、次回以降はオフラインで再利用可能です。

補足

ox.distance.nearest_nodes() を使った距離計算には scipy が必要です。
(代わりに scikit-learn を使用することも可能ですが、本記事では scipy を採用します)

.pbf ファイルを直接読み込むためのライブラリ pyrosm も存在しますが、
Windows環境ではC++ビルド環境(Visual Studio Build Tools)が必要です。

今回のハンズオンでは、東京駅を中心に徒歩5分・10分・15分圏を可視化し、
最終的にHTML地図として出力します。

必要ライブラリのインストール

ここでは、Python環境を整えて必要なライブラリを導入します。
すべてローカル環境で動作し、外部APIやクラウドサービスは不要です。

Pythonバージョンに関して

地理系ライブラリ(osmnx / geopandas など)はC拡張を多く含むため、
Pythonのバージョンによっては依存関係のビルドに失敗する場合があります。

特に Windows 環境では次の点に注意してください。

Pythonバージョン 安定度 備考
3.11系 ◎ 全体的に安定。主要GISライブラリが公式対応済み。
3.12系 ○ 概ね問題なし。ビルド時間がやや長くなる場合がある。
3.13系 △ 一部ライブラリ(pyrosm, rasterioなど)でビルドエラーが発生する可能性あり。

本記事では 3.11.9 を使用しています。

# Pythonバージョン確認
% python -V
Python 3.11.9

仮想環境の作成

OSや既存の環境に影響を与えないよう、まず仮想環境を作成します。
任意の作業フォルダで以下を実行してください。

# 任意の作業ディレクトリで実行
python -m venv venv

仮想環境の有効化

作成した仮想環境をアクティブにします。
以降のインストール作業はこの環境内で行います。

# Windows(Git Bash / zsh)の場合:
source venv/Scripts/activate

# Windows(PowerShell / コマンドプロンプト)の場合:
# venv\Scripts\activate

# macOS / Linux の場合:
# source venv/bin/activate

pipの更新とライブラリのインストール

仮想環境が有効になったら、pip を最新化し、必要なライブラリを一括インストールします。

# pipとセットアップツールを更新
python -m pip install -U pip setuptools wheel

# 必須+距離探索に必要なライブラリをインストール
pip install "osmnx>=1.9" "geopandas>=0.14" networkx folium shapely scipy

補足

.pbf ファイルを直接読み込むためのライブラリ pyrosm も存在しますが、
Windows環境ではC++ビルド環境(Visual Studio Build Tools)が必要です。
完全オフライン運用を行いたい場合は、次回の記事で紹介します。

インストール確認

インストールが完了したら、OSMnxのバージョンを確認します。

python -m pip show osmnx

バージョン情報が表示されればインストール成功です。

OSMデータの取得(OSMnx)

道路ネットワークを扱うためには、まずOpenStreetMap(OSM)のデータを取得する必要があります。
OSMnxを使えば、地名を指定するだけで簡単に徒歩ネットワークを取得できます。

OSMnxは初回にオンラインでデータを取得しますが、
取得結果は自動的にローカルキャッシュ(~/.cache/osmnx)へ保存されます。
そのため、同じ場所を再取得する際はオフラインでも利用可能です。

さらに、自分で扱いやすいように
GraphML形式で保存しておくと、プロジェクト単位での再利用が容易になります。

初回:データ取得とGraphMLとして保存

次のスクリプトでは、東京駅を中心とした徒歩ネットワークを取得し、
GraphMLファイルとして保存します。
このファイルを使えば、以降は完全オフラインで再利用できます。

get_osmnx_network.py
import osmnx as ox

# 東京駅を中心に半径2kmの徒歩ネットワークを取得
lat, lon = 35.681236, 139.767125
G = ox.graph_from_point((lat, lon), dist=2000, network_type="walk")

# ローカルに保存して再利用可能に
ox.save_graphml(G, "tokyo_walk_2km.graphml")
print("tokyo_walk_2km.graphml を保存しました。")

2回目以降:保存済みデータの再利用

保存したGraphMLファイルを読み込むことで、
OpenStreetMapへのアクセスなしに、すぐに解析を始められます。

load_osmnx_network.py
import osmnx as ox

# 保存済みGraphMLファイルを読み込み(オフライン実行可能)
G = ox.load_graphml("tokyo_walk_2km.graphml")

print(f"グラフを読み込みました。ノード数: {len(G)}")

今回はOSMnxによるオンライン取得+キャッシュ再利用のみを使用します。

NetworkXで到達距離を計算(Dijkstra法)

OSMnxで取得したネットワークデータは、内部的に NetworkXのグラフ構造 になっています。
したがって、networkx の関数を使って最短経路探索や距離計算を直接行うことができます。

今回は、東京駅を中心に「徒歩○分で到達できる範囲」 を求めます。

isochrone_osmnx.py
import osmnx as ox
import networkx as nx
import geopandas as gpd
from shapely.geometry import Point

# 1. GraphMLを読み込み
G = ox.load_graphml("tokyo_walk_2km.graphml")
G_proj = ox.project_graph(G)  # メートル単位に変換

# 2. 東京駅の座標(WGS84)
lat, lon = 35.681236, 139.767125

# WGS84のポイントを作成
pt_wgs84 = gpd.GeoSeries([Point(lon, lat)], crs=G.graph["crs"])  # EPSG:4326想定
# 投影座標系へ変換
pt_proj = pt_wgs84.to_crs(G_proj.graph["crs"])
x, y = pt_proj.iloc[0].x, pt_proj.iloc[0].y

# 3. 最近傍ノードを取得(scipy使用)
origin = ox.distance.nearest_nodes(G_proj, X=x, Y=y)
print(f"起点ノード: {origin}")

# 4. Dijkstra法による最短距離計算
speed_m_per_min = 80
max_minutes = 15
max_dist_m = speed_m_per_min * max_minutes

lengths = nx.single_source_dijkstra_path_length(
    G_proj, source=origin, cutoff=max_dist_m, weight="length"
)

print(f"計算ノード数: {len(lengths)}")

# 5. 到達圏の距離ごとにノードを抽出
thresh_5m  = 5 * 80
thresh_10m = 10 * 80
thresh_15m = 15 * 80

nodes_5m  = [n for n, d in lengths.items() if d <= thresh_5m]
nodes_10m = [n for n, d in lengths.items() if d <= thresh_10m]
nodes_15m = [n for n, d in lengths.items() if d <= thresh_15m]

print(f"5分={len(nodes_5m)} 10分={len(nodes_10m)} 15分={len(nodes_15m)}")


# ---- 結果 ----
# python isochrone_osmnx.py                                                     
# 起点ノード: 12802523328
# 計算ノード数: 3932
# 5分=778 10分=2009 15分=3932

次章では、これらの到達ノードを GeoPandasでポリゴン化 して地図上に描画します。

GeoPandasで到達圏をポリゴン化して可視化する(Folium編)

前章で計算した「徒歩5分・10分・15分で到達できるノード群」を、
実際の地図上に ポリゴンとして可視化 してみましょう。

OSMnxの内部データは networkx グラフ構造ですが、
GeoPandas を組み合わせることで簡単に 地理的な領域(Polygon) に変換できます。

到達圏の定義について

本記事で描く「到達圏」は、
到達可能な道路リンクを太く結合して得られる歩行可能範囲(道路沿いの帯域) を示しています。
そのため、公園や広場など内部に道路データが存在しない場所は、
実際には通行できても、可視化された到達範囲上では“穴”として描かれます。

一方で、OSMデータには「歩行可能なポリゴン(例:公園・広場・歩道エリア)」も含まれています。
これらを osmnx.features_from_place()pyrosm.get_data_by_custom_criteria() などで取得・統合することで、
実際に踏み入れ可能なエリア(場所=space) をより正確に表現することが可能です。

また、OSMのポリゴンデータには建物(building=*)も含まれますが、
それらは通常「通行不可」として扱われます。
ただし、駅構内や商業施設など、実際には通行可能な建物も存在します。
そのため、精緻な歩行可能エリアを構築する場合は、
用途タグ(area:highway=*, leisure=* など)を基にフィルタリングする
のが望ましいです。

到達圏ポリゴンの考え方

単に「到達ノードの点」を繋ぐと円形に近い形になります。
より“道路に沿った”現実的な形状を描くために、
到達ノード両端を結ぶ道路エッジ(LineString)を太くし結合(buffer + union) しています。
これにより、「実際に歩ける道路」をベースにした徒歩圏マップを作ることができます。

スクリプト全体(isochrone_map.py

isochrone_map.py
import osmnx as ox
import geopandas as gpd
import networkx as nx
from shapely.geometry import Point
import folium

# --- 1) グラフ読み込み & 投影 ---
G = ox.load_graphml("tokyo_walk_2km.graphml")
G_proj = ox.project_graph(G)  # メートル系

# 起点(東京駅, WGS84)
lat, lon = 35.681236, 139.767125
pt_wgs84 = gpd.GeoSeries([Point(lon, lat)], crs=G.graph.get("crs", "EPSG:4326"))
pt_proj  = pt_wgs84.to_crs(G_proj.graph["crs"])
x, y = pt_proj.iloc[0].x, pt_proj.iloc[0].y
origin = ox.distance.nearest_nodes(G_proj, X=x, Y=y)

# --- 2) Dijkstra(距離: m) ---
speed_m_per_min = 80
thresh_5m  = 5  * speed_m_per_min  # 400
thresh_10m = 10 * speed_m_per_min  # 800
thresh_15m = 15 * speed_m_per_min  # 1200
lengths = nx.single_source_dijkstra_path_length(
    G_proj, source=origin, cutoff=thresh_15m, weight="length"
)

# 到達ノード集合
S5  = {n for n, d in lengths.items() if d <= thresh_5m}
S10 = {n for n, d in lengths.items() if d <= thresh_10m}
S15 = {n for n, d in lengths.items() if d <= thresh_15m}

# --- 3) エッジ由来でポリゴン化 ---
# 投影座標のGDF(m)
edges_gdf_proj = ox.graph_to_gdfs(G_proj, nodes=False, edges=True)

def iso_polygon_from_edges(reachable_nodes: set, buffer_m=10, simplify_m=3):
    """到達ノードに両端が含まれる道路エッジを抽出→buffer→union_all→Polygon(WGS84)"""
    if not reachable_nodes:
        return None

    # (u,v,key) MultiIndex の u,v が到達集合に含まれるエッジのみ採用
    def both_endpoints_reached(idx):
        try:
            u, v = idx[0], idx[1]
        except Exception:
            return False
        return (u in reachable_nodes) and (v in reachable_nodes)

    sub = edges_gdf_proj.loc[edges_gdf_proj.index.map(both_endpoints_reached)]
    if sub.empty:
        return None

    # 道路形状を太くし結合
    merged = sub.buffer(buffer_m).union_all().buffer(0).simplify(simplify_m)

    # WGS84へ変換して shapely geometry を返す
    return gpd.GeoDataFrame(geometry=[merged], crs=edges_gdf_proj.crs).to_crs(4326).iloc[0].geometry

# 細め設定
poly_5m  = iso_polygon_from_edges(S5,  buffer_m=8,  simplify_m=3)
poly_10m = iso_polygon_from_edges(S10, buffer_m=10, simplify_m=3)
poly_15m = iso_polygon_from_edges(S15, buffer_m=12, simplify_m=4)

# --- 4) Folium 可視化 ---
m = folium.Map(location=[lat, lon], zoom_start=15, tiles="cartodbpositron")

def add_poly(geom, name, color, fill_opacity=0.28):
    if geom is None:
        return
    folium.GeoJson(
        data=gpd.GeoSeries([geom], crs="EPSG:4326").to_json(),
        name=name,
        style_function=lambda x, col=color: {
            "color": col, "weight": 2, "fillOpacity": fill_opacity, "fillColor": col
        },
        tooltip=name,
    ).add_to(m)

# レイヤ順:15 → 10 → 5(下から順に)
add_poly(poly_15m, "徒歩15分圏(~1200m)", "#377eb8")
add_poly(poly_10m, "徒歩10分圏(~800m)",  "#4daf4a")
add_poly(poly_5m,  "徒歩5分圏(~400m)",   "#e41a1c")

# 起点マーカー
folium.Marker([lat, lon], icon=folium.Icon(color="red"), tooltip="起点(東京駅)").add_to(m)
folium.LayerControl().add_to(m)

m.save("isochrone_map.html")
print("isochrone_map.html を出力しました。")

補足
thresh_5m / thresh_10m / thresh_15m の閾値定義や、
poly_5m / poly_10m / poly_15m の生成、add_poly() の呼び出し部分は、
実際には辞書やループ処理にまとめるのが望ましいです
この記事では 各到達圏の距離・色・処理の対応関係を明確に示すため
明示的に展開したコードとしています

処理の流れ

  1. GraphMLの読み込みと投影変換
  2. 起点ノードの特定(scipy使用)
  3. Dijkstra法による最短距離計算
  4. 到達圏ノード抽出と道路エッジのポリゴン化
  5. Foliumで色分け表示(徒歩5・10・15分圏)

パラメータ調整

設定 内容 推奨値
buffer_m 道路の太さ 都市部:8〜12m/郊外:12〜18m
simplify_m 輪郭の滑らかさ 3〜5
fill_opacity 塗りつぶしの透明度 0.25〜0.35

出力結果(例)

  • 赤:徒歩5分圏(約400m)
  • 緑:徒歩10分圏(約800m)
  • 青:徒歩15分圏(約1200m)

1c97aba92153e6cbccc4.png

まとめ:ローカル環境での到達圏分析の意義と次の展開

本記事では、OSMnxとNetworkXを活用して、Pythonのみで到達圏(isochrone)を可視化する手法を紹介しました。
オンラインAPIや外部SaaSを利用せず、完全にローカル環境で徒歩圏マップを生成できる点が最大の特徴です。

今回実現したこと

  • OpenStreetMap(OSM)から徒歩ネットワークを取得(OSMnx)
  • Dijkstra法による最短経路探索(NetworkX)
  • 徒歩5分・10分・15分圏のポリゴン化(GeoPandas)
  • Foliumによる地図描画とHTML出力(完全ローカル可視化)

これらの処理をすべてPythonスクリプトのみで完結させることで、
地理情報分析のプロトタイピングをクラウド依存なく行う環境を構築できました。

ローカル実行の利点

観点 メリット
コスト API利用料が不要
再現性 データとコードをセットでバージョン管理できる
セキュリティ 社内データなど外部送信せずに分析可能
オフライン対応 ネットワークが不安定な環境でも再現可能

特に、機密性の高いデータを扱う業務システムや社内ツールでは、
ローカル実行のメリットが非常に大きくなります。


今後の拡張アイデア

  • GraphHopperやpgRouting を用いた高精度なルートベースの到達圏分析
  • 自転車・自動車モード対応network_type='bike' / 'drive'
  • 等時間帯比較(例:朝と夜で到達範囲を比較)
  • 地点群(複数拠点)の到達圏統合分析
    └ 複数地点の徒歩圏を重ね合わせ、共通到達エリアや未到達エリアを抽出
    店舗配置の最適化や防災計画など、地理的カバレッジ評価に応用可能
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?