osmar(R言語)を用いた鉄道路線図の描画例

【概要】本記事では、R言語のosmarパッケージでOpenStreetMapデータから鉄道路線図を描画するプログラムの例を示す。osmarを使うと、Rの解析機能やグラフ描画機能と組み合わせて地理データを扱える。例として、福岡市内の西日本鉄道と名古屋市内の名古屋鉄道の路線図を描画する。

【目次】

  1. osmarについて
  2. 福岡市内の西日本鉄道経路図の描画例
  3. 名古屋市内の名古屋鉄道経路図の描画例

osmarについて

osmarは、OpenStreetMap(誰でも自由に利用・編集できる地図プロジェクト)のデータをR言語で扱うためのパッケージである。OSM-XMLファイルの読み込み、データの操作、地図の描画の機能を提供する。

注: osmarパッケージは2022年6月20日にCRANからアーカイブされ、2026年5月時点でもinstall.packages("osmar")では入手できない。Windows環境では、次のいずれかの方法でインストールする。
(1) CRANアーカイブからソースで導入する場合(事前に「Rtools」をインストールしておく):
install.packages("https://cran.r-project.org/src/contrib/Archive/osmar/osmar_1.1-7.tar.gz", repos = NULL, type = "source")
(2) R-Forge(R関連プロジェクトのホスティングサービス)から導入する場合:
install.packages("osmar", repos = "http://R-Forge.R-project.org")
いずれの場合も、依存パッケージであるXMLgeosphereが必要となる。install.packages(c("XML", "geosphere"))で先に導入しておく。

前準備: OSM-XMLファイルを準備する。OpenStreetMapのウェブサイト(openstreetmap.org)の「エクスポート」機能で対象範囲を指定して取得できる。本記事では、取得したファイルをWindows上の作業フォルダ(例: C:/Users/hoge/Documents/osm/)に保存しているものとする。

本資料の前提とデータについて

本資料のプログラムは、入力するOSM-XMLファイルに次のタグが付与されていることを前提とする。第一に、各路線を構成する要素に路線名のタグ(OpenStreetMapではキーと値の組で表され、路線名は name=貝塚線 のようにキー name に入る)が付いていること。第二に、駅を表すノードに駅のタグ railway=station(キー railway、値 station)が付いていること。OpenStreetMapから対象範囲をエクスポートしただけのファイルでは、これらのタグが揃っていない場合がある。タグが揃っていないと、後述のサブセット(条件で抽出した部分データ)が空になり、駅・路線が描画されない。

本資料で使う主要関数の役割は次のとおりである。find()は指定したタグ条件に合う要素のIDを取得する。find_down()はウェイ(路線)から、それを構成するノード(地点)へ階層を辿ってIDを集める。subset()は指定したIDの要素だけを取り出した部分データ(osmarオブジェクト)を作成する。plot_nodes()はノードを点として、plot_ways()はウェイを線として描画する。

OpenStreetMapのデータ構造のうち、本資料ではnode(ノード: 駅などの地点を表す座標情報)とway(ウェイ: 路線などの経路を表すノードの集合)を使い、駅の位置と路線を描画する。

福岡市内の西日本鉄道経路図の描画例

福岡市内における西日本鉄道の経路図

上図は西日本鉄道の経路図で、貝塚線と天神大牟田線の駅と路線を表示している。プログラムを以下に示す。

#ライブラリの読み込み
library("osmar")
library("XML")   #xmlParse()を使用するため明示的に読み込む

#地図データの読み込み(Windowsのパスを指定。区切りは / または \\ を使う)
#readLinesでファイルを文字列として読み込み、xmlParseでXMLとして解析する。
#大きいファイルではメモリを多く消費する点に注意。
xml <- readLines('C:/Users/hoge/Documents/osm/fukuoka.xml')
map <- as_osmar(xmlParse(xml))

##貝塚線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
kai.id <- unique(find(map, node(tags(v == "貝塚線"))))
kai.set <- subset(map, node_ids = kai.id)
kai_n.id <- unique(find(kai.set, node(tags(k == "railway" & v == "station"))))
kai_n.set <- subset(map, node_ids = kai_n.id)

##貝塚線の路線情報(way)のみを含むサブセットを作成
kai_w.id <- unique(find(map, way(tags(v == "貝塚線"))))
kai_w.id <- find_down(map, way(kai_w.id))
kai_w.set <- subset(map, ids = kai_w.id)

##天神大牟田線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
ten.id <- unique(find(map, node(tags(v == "天神大牟田線"))))
ten.set <- subset(map, node_ids = ten.id)
ten_n.id <- unique(find(ten.set, node(tags(k == "railway" & v == "station"))))
ten_n.set <- subset(map, node_ids = ten_n.id)

##天神大牟田線の路線情報(way)のみを含むサブセットを作成
ten_w.id <- unique(find(map, way(tags(v == "天神大牟田線"))))
ten_w.id <- find_down(map, way(ten_w.id))
ten_w.set <- subset(map, ids = ten_w.id)

#グラフの作成
#xlim/ylim は表示する緯度経度の範囲。対象地域に合わせて調整する。
#add = TRUE の重ね描きでは座標範囲は最初の plot_nodes に従うため、
#後続の呼び出しでは xlim/ylim を指定しない。
par(mar = c(5.5, 6, 4.1, 2))
par(mgp = c(4, 1.2, 0))
plot_nodes(kai_n.set, xlim = c(130.18, 130.50),
    ylim = c(33.41, 33.73), main = "福岡市内における西日本鉄道の経路図",
    xlab = "経度", ylab = "緯度", pch = 1, cex.lab = 2,
    cex.axis = 1.8, cex.main = 1.8)
plot_nodes(ten_n.set, pch = 16, xlab = "", ylab = "", add = TRUE)
legend("topleft", legend = c("貝塚線", "天神大牟田線"),
    pch = c(1, 16), cex = 1.5, pt.cex = 2, bty = "n")
plot_ways(kai_w.set, xlab = "", ylab = "", add = TRUE)
plot_ways(ten_w.set, xlab = "", ylab = "", add = TRUE)

期待した駅・路線が表示されない場合は、入力XMLファイルに必要なタグ(路線名 name=貝塚線 など、および駅を示す railway=station)が含まれているかを確認する。サブセットが空になっていないかは、print(kai_n.set) のように出力して要素数を確認する。

名古屋市内の名古屋鉄道経路図の描画例

名古屋市内における名古屋鉄道の経路図

上図は名古屋鉄道の経路図で、名古屋本線、犬山線、津島線、小牧線、瀬戸線、築港線、常滑線、河和線の8路線の駅と路線を表示している。プログラムを以下に示す。

#ライブラリの読み込み
library("osmar")
library("XML")   #xmlParse()を使用するため明示的に読み込む

#地図データの読み込み(Windowsのパスを指定。区切りは / または \\ を使う)
#readLinesでファイルを文字列として読み込み、xmlParseでXMLとして解析する。
#大きいファイルではメモリを多く消費する点に注意。
xml <- readLines('C:/Users/hoge/Documents/osm/nagoya.xml')
map <- as_osmar(xmlParse(xml))

##名古屋本線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
hon.id <- unique(find(map, node(tags(v == "名古屋本線"))))
hon.set <- subset(map, node_ids = hon.id)
hon_n.id <- unique(find(hon.set, node(tags(k == "railway" & v == "station"))))
hon_n.set <- subset(map, node_ids = hon_n.id)

##名古屋本線の路線情報(way)のみを含むサブセットを作成
hon_w.id <- unique(find(map, way(tags(v == "名古屋本線"))))
hon_w.id <- find_down(map, way(hon_w.id))
hon_w.set <- subset(map, ids = hon_w.id)

##犬山線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
inu.id <- unique(find(map, node(tags(v == "犬山線"))))
inu.set <- subset(map, node_ids = inu.id)
inu_n.id <- unique(find(inu.set, node(tags(k == "railway" & v == "station"))))
inu_n.set <- subset(map, node_ids = inu_n.id)

##犬山線の路線情報(way)のみを含むサブセットを作成
inu_w.id <- unique(find(map, way(tags(v == "犬山線"))))
inu_w.id <- find_down(map, way(inu_w.id))
inu_w.set <- subset(map, ids = inu_w.id)

##津島線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
tsu.id <- unique(find(map, node(tags(v == "津島線"))))
tsu.set <- subset(map, node_ids = tsu.id)
tsu_n.id <- unique(find(tsu.set, node(tags(k == "railway" & v == "station"))))
tsu_n.set <- subset(map, node_ids = tsu_n.id)

##津島線の路線情報(way)のみを含むサブセットを作成
tsu_w.id <- unique(find(map, way(tags(v == "津島線"))))
tsu_w.id <- find_down(map, way(tsu_w.id))
tsu_w.set <- subset(map, ids = tsu_w.id)

##小牧線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
omk.id <- unique(find(map, node(tags(v == "小牧線"))))
omk.set <- subset(map, node_ids = omk.id)
omk_n.id <- unique(find(omk.set, node(tags(k == "railway" & v == "station"))))
omk_n.set <- subset(map, node_ids = omk_n.id)

##小牧線の路線情報(way)のみを含むサブセットを作成
omk_w.id <- unique(find(map, way(tags(v == "小牧線"))))
omk_w.id <- find_down(map, way(omk_w.id))
omk_w.set <- subset(map, ids = omk_w.id)

##瀬戸線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
##(ノード・ウェイともに同一の路線名 "瀬戸線" で統一する)
sto.id <- unique(find(map, node(tags(v == "瀬戸線"))))
sto.set <- subset(map, node_ids = sto.id)
sto_n.id <- unique(find(sto.set, node(tags(k == "railway" & v == "station"))))
sto_n.set <- subset(map, node_ids = sto_n.id)

##瀬戸線の路線情報(way)のみを含むサブセットを作成
sto_w.id <- unique(find(map, way(tags(v == "瀬戸線"))))
sto_w.id <- find_down(map, way(sto_w.id))
sto_w.set <- subset(map, ids = sto_w.id)

##築港線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
tku.id <- unique(find(map, node(tags(v == "築港線"))))
tku.set <- subset(map, node_ids = tku.id)
tku_n.id <- unique(find(tku.set, node(tags(k == "railway" & v == "station"))))
tku_n.set <- subset(map, node_ids = tku_n.id)

##築港線の路線情報(way)のみを含むサブセットを作成
tku_w.id <- unique(find(map, way(tags(v == "築港線"))))
tku_w.id <- find_down(map, way(tku_w.id))
tku_w.set <- subset(map, ids = tku_w.id)

##常滑線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
jkt.id <- unique(find(map, node(tags(v == "常滑線"))))
jkt.set <- subset(map, node_ids = jkt.id)
jkt_n.id <- unique(find(jkt.set, node(tags(k == "railway" & v == "station"))))
jkt_n.set <- subset(map, node_ids = jkt_n.id)

##常滑線の路線情報(way)のみを含むサブセットを作成
jkt_w.id <- unique(find(map, way(tags(v == "常滑線"))))
jkt_w.id <- find_down(map, way(jkt_w.id))
jkt_w.set <- subset(map, ids = jkt_w.id)

##河和線の駅の座標(node)のみを含むサブセットを作成
##(駅は railway=station で表される前提。キー railway を明示して絞り込む)
kww.id <- unique(find(map, node(tags(v == "河和線"))))
kww.set <- subset(map, node_ids = kww.id)
kww_n.id <- unique(find(kww.set, node(tags(k == "railway" & v == "station"))))
kww_n.set <- subset(map, node_ids = kww_n.id)

##河和線の路線情報(way)のみを含むサブセットを作成
kww_w.id <- unique(find(map, way(tags(v == "河和線"))))
kww_w.id <- find_down(map, way(kww_w.id))
kww_w.set <- subset(map, ids = kww_w.id)

#グラフのパラメータ設定
#XLIM/YLIM は表示する緯度経度の範囲。対象地域に合わせて調整する。
#add = TRUE の重ね描きでは座標範囲は最初の plot_nodes に従う。
par(mar = c(5.5, 6, 4.1, 2))
par(mgp = c(4, 1.2, 0))
XLIM <- c(136.7, 137.1)
YLIM <- c(35.0, 35.3)
LEGEND <- c("名古屋本線", "犬山線", "津島線", "小牧線", "瀬戸線", "築港線", "常滑線", "河和線")
plot_nodes(hon_n.set, xlim = XLIM, ylim = YLIM, main = "名古屋市内における名古屋鉄道の経路図",
    xlab = "経度", ylab = "緯度", col = 1, cex.lab = 2, cex.axis = 1.8, cex.main = 1.8)
plot_nodes(inu_n.set, xlab = "", ylab = "", col = 2, add = TRUE)
plot_nodes(tsu_n.set, xlab = "", ylab = "", col = 3, add = TRUE)
plot_nodes(omk_n.set, xlab = "", ylab = "", col = 4, add = TRUE)
plot_nodes(sto_n.set, xlab = "", ylab = "", col = 5, add = TRUE)
plot_nodes(tku_n.set, xlab = "", ylab = "", col = 6, add = TRUE)
plot_nodes(jkt_n.set, xlab = "", ylab = "", col = 7, add = TRUE)
plot_nodes(kww_n.set, xlab = "", ylab = "", col = 8, add = TRUE)

legend("bottomleft", legend = LEGEND, col = c(1:8),
    pch = 1, cex = 1.5, pt.cex = 2, bty = "n")

plot_ways(hon_w.set, xlab = "", ylab = "", col = 1, add = TRUE)
plot_ways(inu_w.set, xlab = "", ylab = "", col = 2, add = TRUE)
plot_ways(tsu_w.set, xlab = "", ylab = "", col = 3, add = TRUE)
plot_ways(omk_w.set, xlab = "", ylab = "", col = 4, add = TRUE)
plot_ways(sto_w.set, xlab = "", ylab = "", col = 5, add = TRUE)
plot_ways(tku_w.set, xlab = "", ylab = "", col = 6, add = TRUE)
plot_ways(jkt_w.set, xlab = "", ylab = "", col = 7, add = TRUE)
plot_ways(kww_w.set, xlab = "", ylab = "", col = 8, add = TRUE)

期待した駅・路線が表示されない場合は、入力XMLファイルに必要なタグ(各路線名、および駅を示す railway=station)が含まれているかを確認する。8路線のいずれかが描画されないときは、その路線のサブセット(例: sto_n.set)が空になっていないかを print() で出力して要素数を確認する。