技術研究所のまつけんです。

 

前回の続きです。前回は、東京近郊の路線図を (根性で) 隣接行列に変換したものを自動で路線図 (SVGファイル) に戻し、確認作業をしました。その結果、少なくとも3か所の誤りが見つかりました。それがどこだったかと言いますと、
・尾久駅が無い
・海芝浦駅が必要ない
・新宿駅-西国分寺駅が接続されていない
でした。

一部の線だけ赤色で描画するには、前回のプログラムの139-147行目を

string colour;
if (1 < matrix[i][j]){
colour = "red";
}else{
colour = "grey";
}
cout
<< "<line x1=\""
<< (matrix2[1][idx_from] - min_lat) * scale
<< "\" y1=\""
<< (max_lon - matrix2[0][idx_from]) * scale
<< "\" x2=\""
<< (matrix2[1][idx_to] - min_lat) * scale
<< "\" y2=\""
<< (max_lon - matrix2[0][idx_to]) * scale
<< "\" stroke=\"" + colour + "\" stroke-width=\"1\" />\t<!--\t"
<< station_from << " => " << station_to << "\t-->" << endl;

に置き換えた上で、赤色にしたい部分について隣接行列の値を2以上にします。

さて、それはそれとして、今回は、より見やすい路線図を生成してみたいと思います。前回の路線図は緯度・経度情報を使って正確な位置に駅名を配置しているのですが、粗なエリアと密なエリアがあるために見づらい印象でした。そこで、駅同士の位置関係は保ったまま、座標を正規化します。今回は趣向を変えてPythonでやってみました。

import pandas as pd
# 必要な駅だけを抽出
adjmat = pd.read_csv('adj_mat_tokyo.csv')
coords_T = pd.read_csv('jr_east.csv', header=None, index_col=0).transpose()
names = list(adjmat.columns.values) # 隣接行列で使われている駅名のリスト
names.append('戸塚') # 横浜、磯子、大船が一直線に並んでしまうのを防ぐために戸塚を入れる
coords_T2 = pd.DataFrame()
for name in names:
coords_T2[name] = coords_T[name].values.transpose().flatten()[0 : 2]
coords_T2.transpose().to_csv('jr_east_2.csv', header=None)
coords_T2.to_csv('jr_east_2_T.csv', index=None)
# 座標を正規化
coords2_norm = coords_T2.transpose()
coords2_norm[2] = range(len(coords2_norm)) # 元の並び順に戻すために行番号を追加
coords2_norm = coords2_norm.sort_values(by=[0]) # 緯度でソート
coords2_norm[0] = range(len(coords2_norm)) # 連番で上書き
coords2_norm = coords2_norm.sort_values(by=[1]) # 経度でソート
coords2_norm[1] = range(len(coords2_norm)) # 連番で上書き
coords2_norm = coords2_norm.sort_values(by=[2]) # 元の順番に戻す
coords2_norm = coords2_norm.drop(2, axis=1) # 行番号を削除
coords2_norm.to_csv('jr_east_2_norm.csv', header=None)
coords2_norm.transpose().to_csv('jr_east_2_T_norm.csv', index=None)

Pythonでは、pandasというライブラリを使うことで、簡単にCSVファイルを扱うことができます。やっていることとしては、必要な駅を取り出し、緯度でソートして緯度を連番で埋め、経度でソートして経度を連番で埋めるだけです。ただし、そのままですと、横浜、磯子、大船が一直線に並んでしまうので、それを防ぐためにダミーとして戸塚を入れます。結果は以下の通りです:

前回の路線図SVG生成プログラムを使って路線図を生成すると以下のようになります (「const double scale = 1800.;」の部分を「const double scale = 10.;」に変更):

PDFにしたものは、こちらです。なお、路線図の生成は、Pythonでも可能です :

scale = 10
encoding = 'utf-8'
coords2_norm_T = coords2_norm.transpose()
names = list(adjmat.columns.values)
n = len(names)
min_lat = 0
max_lon = n
f = open('out2.svg', 'w', encoding=encoding)
f.write('<?xml version="1.0" encoding="' + encoding + '"?>\n')
f.write('<svg version="1.1"\n')
f.write('xmlns="http://www.w3.org/2000/svg"\n')
f.write('xmlns:xlink="http://www.w3.org/1999/xlink"\n')
f.write('x="0px" y="0px"\n')
f.write('viewBox="-100 -20 590 530" >\n')
for idx_from, name_from in enumerate(names):
for idx_to in range(n):
if '1' == adjmat[name_from][idx_to] or '2' == adjmat[name_from][idx_to]:
name_to = names[idx_to]
f.write(
'<line x1="'
+ str((coords2_norm_T[name_from][1] - min_lat) * scale)
+ '" y1="'
+ str((max_lon - coords2_norm_T[name_from][0]) * scale)
+ '" x2="'
+ str((coords2_norm_T[name_to][1] - min_lat) * scale)
+ '" y2="'
+ str((max_lon - coords2_norm_T[name_to][0]) * scale)
+ '" stroke="grey" stroke-width="1" />\t<!--\t' + name_from + ' => ' + name_to + '\t-->\n'
)
for name in names:
f.write(
'<text transform="matrix(1 0 0 1 '
+ str((coords2_norm_T[name][1] - min_lat) * scale) + ' '
+ str((max_lon - coords2_norm_T[name][0]) * scale)
+ ')" font-size="8" text-anchor="middle">' + name + '</text>\n'
)
f.write('</svg>\n')
f.close()

Pythonの場合、引用符としてシングルクォートとダブルクォートが使えるので、ダブルクォートを含む文字列をたくさん出力する場合には書きやすく、読みやすいですね。

さて、今回は、路線図を自動生成するにあたって、座標を正規化する方法について紹介しました。グラフを可視化する場面があったら、是非活用してみていただければと思います。