見出し画像

【GIS】#15 フォーマット変換の話

QGIS向けに作成したxmlタイルマップのxml表記からカシミール3D向けのjsonの記述への変換させるためのpythonコードの話です.

どちらかというと将棋や囲碁の「感想戦」のような個人的な備忘録メモともいえます.

はじめに

これまで今昔マップの旧版地形図を代表的な二つの地図Viewerである『QGIS』と『カシミール3D』で表示させるためのプリセットファイルを作成してNote上でも公開してきました.

この記事はこれらのファイル作成にかかるTipsとなります.

フォーマット

QGISはxml形式,カシミール3Dはjson形式という記述フォーマットです.

作成の経緯(手順)としては,はじめにQGIS向けのプリセットはxmlを手入力で一通り作成.(さすがに最初のオリジナルは自動化はできません).次にカシミール3D向けにはこのxmlをpythonでjson化させるというステップを踏みました.

xmlもjsonも記述フォーマットはどちらもスタンダードなもの.なので,標準的なコンバータがあるのではないかと高をくくっていました.

ところが,両者の変換はそれほど簡単ではなかったという教訓を今回の体験で肌で実感しました.

QGISのプリセットの場合のxml形式は次のような表記です.年代ごとに一行づつまとめられ,9つのプリセットがスッキリとまとめられております.

<?xml version="1.0"?> 
<!DOCTYPE connections>
<qgsXYZTilesConnections version="1.0">

  <xyztiles name="今昔マップ_首都圏_1896-1909" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/2man/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1917-1924" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/00/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1927-1939" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/01/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1944-1954" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/02/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1965-1968" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/03/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1975-1978" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/04/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1983-1987" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/05/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1992-1995" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/06/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />
  <xyztiles name="今昔マップ_首都圏_1998-2005" zmin="8" zmax="16" url="http://ktgis.net/kjmapw/kjtilemap/tokyo50/07/{z}/{x}/{-y}.png" username="" password="" authcfg="" referer="" />

</qgsXYZTilesConnections>

これをカシミール3Dのプリセットでのjson記述では,次のようなファイルを年代ごと(9つ分)に作成が必要となります.

[
{
"name": "今昔マップ_首都圏_1896-1909",
"copyright": "今昔マップ on the web",
"url": "ktgis.net/kjmapw/kjtilemap/tokyo50/2man/$Z/$X/$Y",
"ext": "png",
"no_ext": "false",
"port": "80",
"url_type": "standard",
"denshi-kokudo_type": "map",
"origin": "bottom_left",
"min_zoom": "8",
"max_zoom": "16",
"dem": "false",
"dem_type": "yamatabi"
}
]

xmlは階層を複雑にできることや,key-valueの定義も「方言」が数々存在し,やや面倒なフォーマットと思っていました.どちらかというとjson派でありました.

ところが,今回のようなプリセットではjsonよりもシンプルにスッキリと記述できる利点があることがわかり見直したところです.

もちろん,xmlでは不得手なデータ構造もありますので諸手を挙げてxml推奨ということではありません.以下のサイトではjsonが得意とするところがわかりやすい事例としてまとめられ,参考になりました.

使う場面で的確にデータ構造を判断できる素養が必要なことを,改めて実感したところです.

コード

QGIS向けのxmlからカシミール3D向けのjsonへの変換コードが以下となります.

#-------------------------------------------------
# xml2json_converter.py
#
# This software is released under the MIT License.
#-------------------------------------------------
# -*- coding: utf-8 -*-


"""
QGIS向けのタイルマップXMLからカシミール3D向けのタイルマップjson形式へのフォーマット変換
xmlは複数ファイルを一括で変換するケースに対応.

"""


#ライブラリ
import glob
import xmltodict
import json

# 各種のユーティリティ関数の定義
#ファイル読み込み関数
def read_files(extension):

   # 読み込みファイルの取得
   path = '*.' + extension
   input_files = glob.glob(path)

   # 読み込みファイル数の取得
   number_of_files = len(input_files)

   return input_files, number_of_files

#ゲッター・セッター
def get_patameter(f,num):
   tile_name = f['qgsXYZTilesConnections']['xyztiles'][num]['@name']
   tile_url = f['qgsXYZTilesConnections']['xyztiles'][num]['@url']
   max_zoom = f['qgsXYZTilesConnections']['xyztiles'][num]['@zmax']
   
   return tile_name, tile_url, max_zoom

def set_patameter(tile_name, tile_url, max_zoom):
   kashmir[0]["name"]= tile_name
   kashmir[0]["url"]= tile_url.replace('http://''').replace('{z}/{x}/{-y}.png''$Z/$X/$Y')
   kashmir[0]["max_zoom"]= max_zoom
   
   return

#カシミール3Dのjsonスキーマ
kashmir = [{
"name":"",
"copyright":"今昔マップ on the web",
"url":"",
"ext":"png",
"no_ext":"false",
"port":"80",
"url_type":"standard",
"denshi-kokudo_type":"map",
"origin":"bottom_left",
"min_zoom":"8",
"max_zoom":"",
"dem":"false",
"dem_type":"yamatabi",
}]

# 実行処理
if __name__ == '__main__':

   #QGISのタイルレイヤの拡張子
   default_extension = 'xml'
   
   #ファイルの読み込み
   [flist, f_num] = read_files(default_extension)

   for i in range(f_num):
       
       with open (flist[i],'r' ,encoding='utf-8'as f:
           xml_text = f.read()
   
       # xmlを辞書構造に変換
       dict = xmltodict.parse(xml_text)

       #xmlにかかれているタイルマップの数を取得
       number_of_map = len(dict['qgsXYZTilesConnections']['xyztiles'])

       # ゲッターで必要なパラメータを読み出しとセッターで書き込み
       for j in range(number_of_map):
           tile_name, tile_url, max_zoom = get_patameter(dict,j)
           set_patameter(tile_name, tile_url, max_zoom)
 
           # jsonファイルとして出力
           with open(tile_name + '.json''w', encoding='utf-8'as f:
               json.dump(kashmir,f, indent = 0, ensure_ascii=False)
               print (kashmir)


ポイントとしては,xmlの読み取りはElementTreeなどのXMLパーサーを使わず,xmltodictというライブラリーを使っています.

xmltodictはxml構造からディクショナリ構造へ一発で変換してくれるので,親子関係の階層を気にすることなく,xmlでのkey-valueがjson風に簡単に取得できるようになります.

あとは,取得したkey-valueをカシミール3Dの定形のjsonフォーマットへはめ込む方法をとっています.

getter, setterもclass化させデコレーターの方法も検討しましたが,コードがやや冗長化するので,このような単一的な変換だけであればdef関数化で十分かと判断しました.

より良い方法もあるかもしれません.

コードファイルはこちらより



この記事が気に入ったらサポートをしてみませんか?