読者です 読者をやめる 読者になる 読者になる

ill-identified diary

所属組織の業務や見解とは一切無関係なアフィリエイト付きメモ帳。所属とは関係ないけどここを見て所属先にも興味を持っていただけると喜びます。

[GIS] [R] 日本国内の鉄道網を可視化してみる (後編)

R GIS Google Earth 鉄オタ データハンドリング/データ加工 視覚化

画像は60年代の東京都心の路線 (変わってない)

f:id:ill-identified:20140907175500j:plain

前回までのあらすじ

国交省の国土数値情報データベースからダウンロードした国内の過去存在した鉄道のデータを R で読み込み, 任意の時期の鉄道を表示する方法を紹介した. しかし, R では静止画像で表示することが限界で, 日本全体の情報を可視化するのは難しかった.

今回やること

そこで, 今回は, さらに別のソフトウェアを利用することにした. 地理空間データを視覚化する無料のソフトだと, QGIS がある. しかし QGIS はそれ自体が本格的な地理情報処理ソフトであり, 今は最低限視覚化することができればよく, それ以外の機能は蛇足になる. そこで, 次に考えられるのは, グーグルマップ, もしくはグーグルアースが挙げられる. この2つなら導入の敷居は低いので今回の用途に向いている.

しかし, 詳しく調べたところ, グーグルマップには結構厳しい制約があることがわかった. Google マップでの KML のサポートによると, ファイルサイズは未圧縮KMLで 10MB, アイテム個数の上限1000, 等々とあり, 今回用意したファイルは大きすぎるようだ.

そこで, ファイルのアップロード上限のないグーグルアースを使うことにした.

駅の位置情報を追加する

前回は路線データのみだったが, 今回は駅の位置情報データも読み込み, 駅名を表示できるようにしたい. 前回のように画像に出力しただけでは, 大量の駅名のラベルが重なりまともに見ることが出来なかった. しかし自由に拡大縮小できるグーグルアースならこれは問題にならない.

以下が駅情報データの加工コードだが, 基本的に路線を読み込む方法と変わらないため, 説明は省略する. ただ, 路線情報と駅情報を読み込むコードをまとめたので, 前回と全く同じではない.

library(maptools)
###### read rail history ######
railHist <- readShapeLines('GIS/mlit/rail_hist_2013/N05-13_RailroadSection2.shp', proj4string=CRS("+init=epsg:4612"))
stationHist <- readShapePoints('GIS/mlit/rail_hist_2013/N05-13_Station2.shp', proj4string=CRS("+init=epsg:4612"))


railHist@data    <- as.data.frame(apply(railHist@data, 2, function(x){iconv(x, 'CP932', 'UTF-8')}))
stationHist@data <- as.data.frame(apply(stationHist@data, 2, function(x){iconv(x,'CP932','UTF-8')}))

column.names.new <- c('type','rail.name','company','begin.service', 'begin','end', 'rail.id', 'transition.id', 'change.value', 'note', 'others', 'station.name')
colnames(railHist@data) <- column.names.new[1:11]
colnames(stationHist@data) <- column.names.new[-10]

int.list <- c('begin.service','begin','end')
railHist@data[,int.list] <- apply(railHist@data[,int.list],2, function(x){as.integer(as.character(x))})
stationHist@data[,int.list] <- apply(stationHist@data[,int.list],2, function(x){as.integer(as.character(x))})

railHist.lst <- list()
stationHist.lst <- list()
years <- seq(from=1960, to=2010, by=10)
for (i in 1:length(years) ){
  railHist.lst[[i]] <- railHist[railHist$begin <= years[i] & years[i] <= railHist$end,]
  stationHist.lst[[i]] <- stationHist[stationHist$begin <= years[i] & years[i] <= stationHist$end,]
}
railHist.lst[[length(years)+1]]    <- railHist[railHist$begin <= 2013 & 2013 <= railHist$end,]
stationHist.lst[[length(years)+1]] <- stationHist[stationHist$begin <= 2013 & 2013 <= stationHist$end,]

以上で, 路線・駅情報も R に読み込み, 1960年から2010年までの10年刻み+2013年度のデータを作成できた.

KML形式の出力

次に, グーグルアースに表示するため, KML形式のファイルとして出力する方法を紹介する. KML形式は グーグルマップとグーグルアース両方で対応している形式である.

R では, rgdal というパッケージを使うことで, 空間データオブジェクト (sp) をKML形式で出力できる.

ただし, rgdal 自体は, gdal というプログラムをRと連携するだけなので, 先にこれをインストールしていなければ rgdal インストール時にエラーとなる. Ubuntu の場合, libgrad1-dev, libproj-dev が必要だった*1ので, 先に端末で apt-get しておく. OS XWindows の場合, CRANのページに必要なバイナリファイルへのダウンロードリンクがある.

以下のコードでは, まず, description という属性の列に, 路線/駅の会社, 運用開始・終了年をまとめて文字として代入している. これは, グーグルアース上でクリックした際に吹き出しで表示される情報になる. さらに, もともとあった属性は, 残しておくと吹き出しに残るので消しておく.

library(rgdal)
set.description.and.drop <- function(x){
  x$description <- paste('鉄道会社:', x$company , '開始:', x$begin, '終了:', x$end, sep=' ')
  x$type <- NULL; x$begin.service <- NULL; x$rail.id <- NULL; x$transition.id <- NULL
  x$change.value <- NULL; x$note <- NULL; x$others <- NULL
  return(x)
} 

その後, writeOGR() を用いてKMLファイルとして出力する. 基本的には, 他のファイル出力系の関数と同じで, 出力したいオブジェクトとファイル名を代入すればいいのだが, グーグルアース上で表示させる「名前」と「概要」の部分はオプションとして与える必要がある. dataset_options 引数に gdal のオプション引数を与えられるので, これを用いる.

# dataset_options に gdal のオプションを与えることができる (ただし 0.8-16 現在で "experimental" とのこと )
years <- c('60','70','80','90','00','10','13')
for (i in 1:length(years)){
  writeOGR(set.description.and.drop(railHist.lst[[i]]),
           dsn =paste('railHist',years[i],'s.kml' ,sep=''), paste('railHist',years[i], 's', sep=''),
           'KML',
           dataset_options = c('NameField=rail.name', 'DescriptionField=description') )
  writeOGR(set.description.and.drop(stationHist.lst[[i]]),
           dsn =paste('stationHist',years[i],'s.kml' ,sep=''), paste('stationHist',years[i], 's', sep=''),
           'KML',
           dataset_options = c('NameField=station.name', 'DescriptionField=description') )
}

NameField= 引数には, 地物の名称を与えるので, 路線名・駅名の入っている rail.name または station.name を与えている. DescriptionField= も文字通り「概要」に対応するフィールドを指定するオプションなので, description を与える. これらは端末上でのコマンドなので, 上記のコードのように, 文字列でまとめて dataset_options= の引数として与える.

もう1押し

しかし, gdal の KML 変換機能は限定的で, 例えば地物のアイコンを設定することができない. 上のコードで出力したファイルをそのままグーグルアースでインポートしたところ, 駅のアイコンは押しピンとなった. グーグルアースには電車マークのアイコンが用意されているので, これを使ってみたい. グーグルアースに読み込んでから手動で変更することもできるが, 膨大な数の駅のアイコンを全て変更するのは現実的ではない.

ところがここで一つ問題がある. writeOGR() では, 上のコードで使用したオプション以外に, kml への書き込み情報を指定する方法がないので, これだけではアイコンの設定を指定できない.

実は KML ファイルはXMLファイルの一種なので, 実は記述は単純である. さっきの名称も,

<name>HOGEHOGE 駅</name>
<description>鉄道会社:○○電鉄 開始:XXXX年 終了:XXXX年</description>

と言うふうに出力される. アイコンの記述も単純で, <Style> タグの中に

<IconStyle>
    <Icon>
        <href>(アイコン画像パス)</href>
    </Icon>
    (その他の設定)
</IconStyle>

という感じで設定すればいい.

よって, 一旦 writeOGR() 関数で出力したファイルを再度 xml パッケージを用いて読み込み, スタイル属性を変更する, ということをする.

library(xml)
library(XML)
file.list <- dir()[grep("^stationHist",dir())]
for (j in file.list){
  f <- xmlTreeParse(j, getDTD = F)
  f$children$kml$children$Document$children$Style <- 
    xmlNode('Style',attrs = c(id="station_point"),
            xmlNode('IconStyle',
                    xmlNode('scale','0.7'),
                    xmlNode('Icon',
                            xmlNode('href','http://maps.google.com/mapfiles/kml/shapes/rail.png')
                    )
            )
    )
  names.list <- grep('^Placemark$', names(f$children$kml$children$Document$children$Folder$children))
  system.time(
    for (i in names.list){
      f$children$kml$children$Document$children$Folder$children[i]$Placemark$children$styleUrl <- xmlNode(name = 'styleUrl','#station_point')
    }
  )
  saveXML(f$children$kml, paste(unlist(strsplit(j,'.',T))[1], '_2.kml',sep='') )
}   

xml パッケージはあまり使ったことがないため, もしかするともっと効率的なコードが掛けるかもしれないが, とりあえずこれで動くはず. xmlNode() 関数を入れ子にすることで, 上の <Style>...</Style> に対応する xmlのオブジェクトを作成し, 読み込んだオブジェクトに挿入している.

for で結構な回数をループさせているが, xml の操作自体にはほとんど時間がかかっておらず, むしろファイルサイズが大きいためか, 書き込みだけで数分~数十分の時間がかかることがある.

また, 最後の saveXML() 関数でファイルに書き出しているのだが, この関数はデフォルトでは冒頭のXML宣言 <?xml ... ?>encoding= が書き出されない. Windows だともしかすると文字化けの原因になるかもしれないので, 実際にうまく読み込めない場合は書き足した方がいいだろう.

グーグルアースに出力した結果

自分の環境では Ubuntu 版がなぜかうまく起動しないので, Windows 版のグーグルアースで読み込んだ. 駅はズームアウトするとごちゃごちゃするので適度にズームインしよう.

まずは2013年の都心の路線のみ.

f:id:ill-identified:20140907174903j:plain

つづいて駅も表示する.

f:id:ill-identified:20140907174921j:plain

駅のデータは, 路線・運営会社別に存在するため, ターミナル駅などで路線や駅敷地を共有している場合, 同じ名前のオブジェクトが重なってしまう. このへんが気になる場合は, 出力前に加工したほうがいいだろう.

都心の場合, 60年前と劇的に変わるということがないため, 今回は京都市中心部の画像を掲載する.

こちらが2013年時点

f:id:ill-identified:20140907175011j:plain

こちらが1960年時点になる.

f:id:ill-identified:20140907174958j:plain

概ね現在の市営バス路線に沿って, 市営路面電車が走っていたことが分かる.

なお, グーグルアースには電車の他に地下鉄と路面電車のアイコンも用意されている. この鉄道時系列データは路線の種類が掲載されていないため, 面倒なので電車マークで統一したが, 工夫すれば路線の種類ごとにアイコンの種類を変えることもできるはずである.

参考文献

*1:R 3.0.2, rgdal 0.8-16, Ubuntu 14.04