目的
国土数値情報 ダウンロードサービスでは、国土交通省が管理するデータが取得できます。
今回は、鉄道データを用いて、その座標をGoogleMapにプロットしてみます。
デモ:
http://needtec.sakura.ne.jp/railway_location/railway
GIT:
https://github.com/mima3/railway_location
データについて
鉄道データは下記のページからダウンロードできます。
国土数値情報 鉄道データ
http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N02-v2_2.html
ダウンロードしたファイル中のXMLの使用については下記を参考にしてください。
http://nlftp.mlit.go.jp/ksj/gml/product_spec/KS-PS-N02-v2_1.pdf
簡単に説明すると、鉄道データには、路線の形を示す情報と、駅情報が格納されています。
ここで、重要な要素は以下の通りです。
・gml:Curve 曲線の情報
・ksj:railroadSection 鉄道区間の情報
・ksj:Station 駅情報
Curveには座標情報が格納されています。
railroadSectionとStationのlocation要素にて、Curveへのリンクが格納されています。
データベースへの格納
大量のデータをXMLで取り扱うのは困難なので、一旦リレーショナルデータベースに格納しています。
この時、大きなサイズのXMLを解析しますが、XMLファイル全体を一旦、メモリに格納してパースするというやりかたをすると、メモリ使用量が激増し、処理しきれなくなります。
そのため、lxml.etree.iterparseを用いて、順次処理するようにします。
しかしながら、N02-XX.xmlをlxml.etree.itreparseにて解析するとエラーが発生します。
これは、XML中に以下のような行があるからです。
xmlns:schemaLocation="http://nlftp.mlit.go.jp/ksj/schemas/ksj-app KsjAppSchema-N02-v2_0.xsd">
lxmlは、ここで指定しているURIを不正なURIとみなし、エラーを出力します。
これを回避するにはlxmlにて、XMLを解析する際に、recover=Trueを指定する必要があります。
http://stackoverflow.com/questions/18692965/how-do-i-skip-validating-the-uri-in-lxml
回避例:
context = etree.iterparse(
xml,
events=('end',),
tag='{http://www.opengis.net/gml/3.2}Curve',
recover=True
)
iterparseにおける、この引数は、lxml==3.4.1以降に導入されているため、バージョンを指定してlxmlをインストールする必要があります。
easy_install lxml==3.4.1
以上のことを踏まえて、鉄道データのXMLをデータベースにインポートする処理は以下のようになります。
railway_db.py
# -*- coding: utf-8 -*-
import sqlite3
import sys
import os
# easy_install lxml==3.4.1
from lxml import etree
from peewee import *
database_proxy = Proxy()
database = None
class BaseModel(Model):
"""
モデルクラスのベース
"""
class Meta:
database = database_proxy
class Curve(BaseModel):
"""
曲線情報モデル
"""
curve_id = CharField(index=True, unique=False)
lat = DoubleField()
lng = DoubleField()
class RailRoadSection(BaseModel):
"""
鉄道区間情報モデル
"""
gml_id = CharField(primary_key=True)
# 外部キーは主キーまたは一意制約をもっていないといけないので、
# 複数あるデータに関しては外部キーとしては指定できない。
location = CharField(index=True)
railway_type = IntegerField()
service_provider_type = IntegerField()
railway_line_name = CharField(index=True)
operation_company = CharField(index=True)
class Station(BaseModel):
"""
駅情報モデル
"""
gml_id = CharField(primary_key=True)
# 外部キーは主キーまたは一意制約をもっていないといけないので、
# 複数あるデータに関しては外部キーとしては指定できない。
location = CharField(index=True)
railway_type = IntegerField()
service_provider_type = IntegerField()
railway_line_name = CharField(index=True)
operation_company = CharField(index=True)
station_name = CharField(index=True)
railroad_section = ForeignKeyField(
db_column='railroad_section_id',
rel_model=RailRoadSection,
to_field='gml_id',
index=True
)
def setup(path):
"""
データベースのセットアップ
@param path データベースのパス
"""
global database
database = SqliteDatabase(path)
database_proxy.initialize(database)
database.create_tables([Curve, RailRoadSection, Station], True)
def import_railway(xml):
"""
国土数値院のN02-XX.xmlから路線と駅情報をインポートする
TODO:
外部キーがらみのインポートの効率がわるい
@param xml XMLのパス
"""
commit_cnt = 2000 # ここで指定した数毎INSERTする
f = None
contents = None
namespaces = {
'ksj': 'http://nlftp.mlit.go.jp/ksj/schemas/ksj-app',
'gml': 'http://www.opengis.net/gml/3.2',
'xlink': 'http://www.w3.org/1999/xlink',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
}
with database.transaction():
insert_buff = []
context = etree.iterparse(
xml,
events=('end',),
tag='{http://www.opengis.net/gml/3.2}Curve',
recover=True
)
for event, curve in context:
curveId = curve.get('{http://www.opengis.net/gml/3.2}id')
print (curveId)
posLists = curve.xpath('.//gml:posList', namespaces=namespaces)
for posList in posLists:
points = posList.text.split("\n")
for point in points:
pt = point.strip().split(' ')
if len(pt) != 2:
continue
insert_buff.append({
'curve_id': curveId,
'lat': float(pt[0]),
'lng': float(pt[1])
})
if len(insert_buff) >= commit_cnt:
Curve.insert_many(insert_buff).execute()
insert_buff = []
if len(insert_buff):
Curve.insert_many(insert_buff).execute()
insert_buff = []
context = etree.iterparse(
xml,
events=('end',),
tag='{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}RailroadSection',
recover=True
)
for event, railroad in context:
railroadSectionId = railroad.get(
'{http://www.opengis.net/gml/3.2}id'
)
locationId = railroad.find(
'ksj:location',
namespaces=namespaces
).get('{http://www.w3.org/1999/xlink}href')[1:]
railwayType = railroad.find(
'ksj:railwayType', namespaces=namespaces
).text
serviceProviderType = railroad.find(
'ksj:serviceProviderType',
namespaces=namespaces
).text
railwayLineName = railroad.find(
'ksj:railwayLineName',
namespaces=namespaces
).text
operationCompany = railroad.find(
'ksj:operationCompany',
namespaces=namespaces
).text
insert_buff.append({
'gml_id': railroadSectionId,
'location': locationId,
'railway_type': railwayType,
'service_provider_type': serviceProviderType,
'railway_line_name': railwayLineName,
'operation_company': operationCompany
})
print (railroadSectionId)
if len(insert_buff) >= commit_cnt:
RailRoadSection.insert_many(insert_buff).execute()
insert_buff = []
if len(insert_buff):
RailRoadSection.insert_many(insert_buff).execute()
insert_buff = []
context = etree.iterparse(
xml,
events=('end',),
tag='{http://nlftp.mlit.go.jp/ksj/schemas/ksj-app}Station',
recover=True
)
for event, railroad in context:
stationId = railroad.get('{http://www.opengis.net/gml/3.2}id')
locationId = railroad.find(
'ksj:location', namespaces=namespaces
).get('{http://www.w3.org/1999/xlink}href')[1:]
railwayType = railroad.find(
'ksj:railwayType',
namespaces=namespaces
).text
serviceProviderType = railroad.find(
'ksj:serviceProviderType',
namespaces=namespaces
).text
railwayLineName = railroad.find(
'ksj:railwayLineName',
namespaces=namespaces
).text
operationCompany = railroad.find(
'ksj:operationCompany',
namespaces=namespaces
).text
stationName = railroad.find(
'ksj:stationName',
namespaces=namespaces
).text
railroadSection = railroad.find(
'ksj:railroadSection',
namespaces=namespaces
).get('{http://www.w3.org/1999/xlink}href')[1:]
print (stationId)
insert_buff.append({
'gml_id': stationId,
'location': locationId,
'railway_type': railwayType,
'service_provider_type': serviceProviderType,
'railway_line_name': railwayLineName,
'operation_company': operationCompany,
'station_name': stationName,
'railroad_section': RailRoadSection.get(
RailRoadSection.gml_id == railroadSection
)
})
if len(insert_buff) >= commit_cnt:
Station.insert_many(insert_buff).execute()
insert_buff = []
if len(insert_buff):
Station.insert_many(insert_buff).execute()
データベースに格納してしまえば、あとは簡単に活用できます。
使用上の注意
国土数値情報(鉄道データ)を取り扱う場合に気付いた点を以下に記述します。
・路線名だけでは絞りこめない。
たとえば、「1号線」といった場合に、「横浜市」が保持する場合と、「千葉モノレール」が保持する場合がある。
そのため、「運行会社」と「路線名」で絞りこむ必要がある。
・いつも利用している名称とことなる名称の場合がある。
JR東日本は東日本旅客鉄道になるし、東京メトロは東京地下鉄になっている。
・いつも利用している路線と違う路線になっている場合がある。
たとえば、通常の路線図では「東京」は「中央線」に含まれている。
しかし、国土数値情報としては「東京」は「中央線」に含まれていない。
「東京」~「神田」は「東北線」とみなされる。
これは、東京駅 - 神田駅間は東北本線上に敷設された専用線路を走行しているためだと思われる。