3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonで地図グッズを自作してみた

Last updated at Posted at 2025-12-17

この記事は「FOSS4G Advent Calendar 2025」の17日目の記事です。

1. はじめに

初投稿です。よろしくお願いします。

好きな地域の地図グッズが欲しかったので、OpenStreetMapのデータとOSMnxを使用して自作してみました。

本年7月に開催されたFOSS4G KANSAIでOSMnxを知ったこともあり、FOSS4Gのアドベントカレンダーに参加することとしました。

人生初の個人開発ということもあり、拙い点も多々あるかと思いますが、ご容赦いただけますと幸いです。

2. 作成したもの

1. プログラム

Pythonのライブラリ「OSMnx」を使用したプログラムです。

OpenStreetMapから指定したタグおよび範囲のデータを取得したのち、pngで保存する処理を行います。

なお、データを重ね合わせるために「mapplotlib」を使用しています。

ソースコード
import osmnx as ox
import matplotlib.pyplot as plt
import contextily as ctx
import geopandas as gpd
import matplotlib.patheffects as pe

# 画像設定
fig_height = 3.8 # 82mm=3.22inch
fig_width = 5 # 105mm=4.13inch
dpi = 600

# 色設定
background_color = '#fefcf7'   # ミルキーホワイト(より明るく)
water_color          = '#5dbbe3'  # パステルブルー(鮮やかに)
road_color           = '#64c2a6'  # 明るいグリーン(白背景に映える)
railways_color       = '#2e2e2e'  # 鉄道:印刷で潰れないように濃いグレ
buildings_color      = '#d8a976'  # はっきりしたベージュオレンジ系
national_road_color = '#e6725b'  # 国道:明るめレンガ色

# 地図の設定
north = 44.97 #44.97
south = 44.87 #44.87
east = 141.88 # 141.9
west = 142.07 #142.07

# キャッシュの設定
ox.settings.use_cache = True
ox.settings.log_console = True

# 画像の設定
fig, ax = plt.subplots(figsize=(fig_width, fig_height)) # 図のサイズと背景色
plt.subplots_adjust(left=0, right=1, bottom=0, top=1) # 余白を極力狭くする 
ax.set_facecolor(background_color) # グラフエリア内の背景色

# データの取得と出力
print("川データ")
rivers = ox.features_from_bbox(bbox=[west, south, east, north], tags={'waterway': 'river'})
rivers.plot(ax=ax, color=water_color, linewidth=0.5)

print("水域データ")
water = ox.features_from_bbox(bbox=[west,south,east,north],tags={'natural': 'water'})
ox.plot_footprints(water, ax=ax, color=water_color, show=False, close=False)

print("車両道路データ")
road = ox.graph_from_bbox(bbox=[west,south,east,north],network_type='drive')
ox.plot_graph(road, ax=ax, node_size=0, edge_color=road_color, edge_linewidth=0.5, show=False, close=False)

print('県道データ')
secondary_road = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'secondary'})
secondary_road.plot(ax=ax, color=road_color, linewidth=1)

print('国道データ')
trunk = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'trunk'})
trunk.plot(ax=ax, color=national_road_color, linewidth=1.2, alpha=0.9)

print('鉄道データ')
railways = ox.features_from_bbox(bbox=[west,south,east,north],tags={'railway': 'rail'})
railways.plot(ax=ax, color=railways_color, linewidth=1, alpha=0.9)

print('建物データ')
buildings = ox.features_from_bbox(bbox=[west, south, east, north], tags={'building': True})
ox.plot_footprints(buildings, ax=ax, color=buildings_color, edge_color=buildings_color, edge_linewidth=0.3, show=True, close=True)

# ライセンスを表示
credit_text = "Map data from OpenStreetMap."

ax.text(
    0.03, 0.03,
    credit_text,
    transform=ax.transAxes,
    fontsize=6,
    color="black",
    ha="left", va="bottom",
    alpha=0.7,
    path_effects=[pe.withStroke(linewidth=0.5, foreground="black")]
)

# pngファイルで出力
fig.savefig("output/sample.png", dpi=dpi)
print("地図画像を出力しました: output/sample.png")

2. 地図グッズ

1で作成した画像データを元に、オリジナルグッズの印刷を請け負う業者に発注し、IDホルダーを作成します。

DF4F77E1-F736-4888-AAC8-8E9522525DAF.JPG

余談ですが、今回対象とした地域は北海道幌延町に位置する問寒別周辺です。

問寒別は執筆者が年に何回か訪問する、北海道の北の方に位置する人口200人程度の集落です。

3. OSMnx

3-1. OSMnxとは

OSMnxは、OpenStreetMapから道路網やその他の地理空間フィーチャを簡単にダウンロード、モデル化、分析、視覚化できるPythonパッケージです。歩行者、車、自転車のネットワークを1行のコードでダウンロードしてモデル化し、分析と視覚化が可能です。

公式ドキュメントはこちら

今回は上述のうち、「地理空間地物のダウンロード」と「モデル化」の機能を使用しました

3-2. 使用したモジュール

  • features モジュール
    • OpenStreetMapの地理空間フィーチャからGeoDataFrameをダウンロードして作成します
  • graph モジュール
    • OpenStreetMap データをダウンロードしてグラフを作成します
  • plot モジュール
    • 道路網、ルート、方向、地理空間機能を視覚化します

4. つまづいたところ

4-1. 範囲指定の方法

OSMnxには範囲指定の方法が複数あります。

例えば、features モジュールでは、以下6種類の範囲指定の方法があります。

  • features_from_address()
    • 住所から一定の距離内にある OSM フィーチャをダウンロードします
  • features_from_bbox()
    • 緯度経度境界ボックス内の OSM フィーチャをダウンロードします
  • features_from_place()
    • ある場所の境界内にある OSM フィーチャをダウンロードします
  • features_from_point()
    • 緯度経度ポイントから一定の距離内にある OSM フィーチャをダウンロードします
  • features_from_polygon()
    • (マルチ)ポリゴンの境界内の OSM フィーチャをダウンロードします
  • features_from_xml()
    • OSM XML ファイルのデータから OSM フィーチャの GeoDataFrame を作成します

まずはfeatures_from_point()を使用し、中心となる緯度経度とデータの取得範囲を指定してみました。

結果は以下の通りです。

サンプルコード
# 中心となる緯度経度を指定
latitude = 44.908699
longitude = 141.957398

# データの取得範囲を指定(単位はメートル)
dist = 8000

# 水域データの取得
water = ox.features_from_point(center_point=(latitude,longitude), dist=dist, tags={'natural': 'water'})

# 道路データの取得
road = ox.graph_from_point(center_point=(latitude,longitude), dist=dist,network_type='all')

# FigureとAxesを作成
fig, ax = plt.subplots(figsize=(8, 8))

# 道路を描画
ox.plot_graph(road, ax=ax, show=False, close=False, bgcolor='#ffffff', edge_color='green', node_size=0)

# 水域を描画
ox.plot_footprints(water, ax=ax, show=True, close=True, color='blue', edge_color='none')

# pngファイルで出力
fig.savefig("output/sample.png", dpi=300)
print("地図画像を出力しました: output/sample.png")

image.png

なんということでしょう。国内第4位の長さを誇る天塩川が、上流から下流まで、余すことなく出力されてしまったではありませんか。

続いてfeatures_from_bbox()を使用し、データ取得範囲を緯度軽度で指定してみます。

結果は以下の通りです。

サンプルコード
# データ取得範囲の緯度経度を指定
north = 44.97
south = 44.87
east = 141.9
west = 142.07

# 水域データ・道路データの取得
water = ox.features_from_bbox(bbox=[west,south,east,north],tags={'natural': 'water'})
road = ox.graph_from_bbox(bbox=[west,south,east,north],network_type='all')

# 鉄道データの取得(追加)
railways = ox.graph_from_bbox(bbox=[west, south, east, north], network_type='all', simplify=True, retain_all=True, truncate_by_edge=True, custom_filter='["railway"~"rail"]')

# 建物データの取得(追加)
buildings = ox.features_from_bbox(bbox=[west, south, east, north], tags={'building': True})

# FigureとAxesを作成(縦横比を修正)
fig, ax = plt.subplots(figsize=(10, 8))

# 道路・水域を描画
ox.plot_graph(road, ax=ax, show=False, close=False, bgcolor='#ffffff', edge_color='green', node_size=0)
ox.plot_footprints(water, ax=ax, show=True, close=True, color='blue', edge_color='none')

# 鉄道を描画(追加)
ox.plot_graph(railways, ax=ax, show=True, close=True, edge_color='black', edge_linewidth=0.5, node_size=0)

# 建物を描画(追加)
ox.plot_footprints(buildings, ax=ax, show=True, close=True, color='orange', edge_color='none')

# pngファイルで出力(dpiを修正)
fig.savefig("output/sample.png", dpi=600)
print("地図画像を出力しました: output/sample.png")

image.png

ひとまず、それらしい画像データが出力できました。

4-2.余白を狭くする

先ほど作成した画像は、上下左右の余白があまりにも大きい状態となっています。これらの余白は画像サイズで変わるため、値を調整しながら決めていきます。

執筆者が検証したところ、画像の高さが3.5インチにすると、左右の余白が小さくなり丁度良い塩梅となりました。一方で、高さを4インチにすると、上下にたくさん余白が出てきてしまいました。

また、subplots_adjust()のパラメーターをすべて0にしたり、0.1など1未満の値にした場合はエラーとなったり、余白が大きくなってしまいました。

サンプルコード(画像サイズ設定)
# 画像のサイズを指定(単位はインチ)
fig_height = 3.8
fig_width = 5

# 画像データの作成
fig, ax = plt.subplots(figsize=(fig_width, fig_height))

# 余白の指定
plt.subplots_adjust(left=0, right=1, bottom=0, top=1)

また、合わせて色の設定とライセンス表示の処理を追加しました。

執筆者は色のセンスに自信がないため、色コードはChatGPTに出力してもらいました。

結果は以下の通りです。

サンプルコード(色設定)
# 色設定
background_color = '#fefcf7'   # ミルキーホワイト(より明るく)
water_color          = '#5dbbe3'  # パステルブルー(鮮やかに)
road_color           = '#64c2a6'  # 明るいグリーン(白背景に映える)
railways_color       = '#2e2e2e'  # 鉄道:印刷で潰れないように濃いグレ
buildings_color      = '#d8a976'  # はっきりしたベージュオレンジ系
national_road_color = '#e6725b'  # 国道:明るめレンガ色
サンプルコード(ライセンス表示)
# ライセンスを表示
credit_text = "Map data from OpenStreetMap."

ax.text(
    0.03, 0.03,
    credit_text,
    transform=ax.transAxes,
    fontsize=6,
    color="black",
    ha="left", va="bottom",
    alpha=0.7,
    path_effects=[pe.withStroke(linewidth=0.5, foreground="black")]
)

image.png

グッズらしい画像データになったのではないでしょうか。

4-3. 道路データのデザイン化

先ほど作成した画像は、国道を赤・県道(北海道のため道道)を太い緑・その他道路を細い緑に設定しています。

続いて、こちらの設定方法をまとめます。

OSMnxで道路データをダウンロードするには、featuresモジュールを使う場合とgraphモジュールを使用する場合の2通りが挙げられます。

1. feature モジュールを使用する場合

featuresモジュールは、OpenStreetMapのデータそのものに付けられた「タグ」を指定することで、データをダウンロードします。

今回は範囲指定の方法を緯度経度のボックスとしているため、 features_from_bbox()を使用します。

OpenStreetMapのデータに何のタグが付けられているかは、OpenStreetMapホームページから、データをクリックすることで確認できます。また、タグ一覧はOpenStreetMap 編集用タグ一覧表に公開されています。

国道と県道を出力した結果は以下の通りです。

サンプルコード(国道・県道の表示)
print('県道データ')
secondary_road = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'secondary'})
secondary_road.plot(ax=ax, color=road_color, linewidth=1) # プロット時に色・幅を指定

print('国道データ')
trunk = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'trunk'})
trunk.plot(ax=ax, color=national_road_color, linewidth=1.2, alpha=0.9) # プロット時に色・幅・透明度を指定

image.png

上記の画像では、高速道路や市道など、他のタグに割り振られたデータは出力できておりません。

OpenStreetMapのタグは細かく分かれており、道路関係だけで19種類。小道や交差点、坂などを含めると57種類になります。

これらを1つずつ指定して表示するのも大変なので、その他の道路はgraphモジュールを使用してまとめて出力することにしました。

2. graphモジュールを使用する場合

graphモジュールはOpenStreetMapの道路データを、PythonのパッケージであるNetworkXの構造でグラフ化したものです。今回使用した関数は、graph_from_bbox()です。

本来の使用用途はここでダウンロードしたデータから、経路探索やネットワーク解析を行うものですが、今回は道路データをまとめて出力するために使用します。

結果は以下の通りです。

サンプルコード(全道路データの表示)
road = ox.graph_from_bbox(bbox=[west,south,east,north],network_type='all')
ox.plot_graph(road, ax=ax, node_size=0, edge_color=road_color, edge_linewidth=0.5, show=False, close=False)

print('県道データ')
secondary_road = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'secondary'})
secondary_road.plot(ax=ax, color=road_color, linewidth=1)

print('国道データ')
trunk = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'trunk'})
trunk.plot(ax=ax, color=national_road_color, linewidth=1.2, alpha=0.9)

image.png

「network_type='all'」としたことで、林道や私道を含む、すべての道路が出力されました。

network_typeはall・all_public・bike・drive・drive_service・walkから指定できます。今回は車両が通行できる道路を指定するため、「network_type='drive'」とすることにしました。

最終形は以下の通りです。

サンプルコード
print("車両道路データ")
road = ox.graph_from_bbox(bbox=[west,south,east,north],network_type='drive')
ox.plot_graph(road, ax=ax, node_size=0, edge_color=road_color, edge_linewidth=0.5, show=False, close=False)

print('県道データ')
secondary_road = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'secondary'})
secondary_road.plot(ax=ax, color=road_color, linewidth=1)

print('国道データ')
trunk = ox.features_from_bbox(bbox=[west,south,east,north],tags={'highway': 'trunk'})
trunk.plot(ax=ax, color=national_road_color, linewidth=1.2, alpha=0.9)

image.png

5. 終わりに

小規模ではありますが、人生初の個人開発が形になったことがとても面白かったです。

次はこれをブラウザ上で出力できるwebサイトを構築してみたいです。

参考リンク

Matplotlib plt.subplots()の使い方|FigureとAxesを同時生成!
【python】matplotlibで図の余白を調整する

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?