GIS奮闘記

元GISエンジニアの技術紹介ブログ。主にPythonを使用。

スポンサーリンク

Pythonで画像ファイルにジオタグ(geotag)を追加しよう!

本エントリー作成時にはサンプルコードが正しく動作したのですが、現状、正しく動作が判明しないことがわかりました。以下エントリーの方法で同じことが実現できますので、こちらを参照してください。

www.gis-py.com




さて、本日は「Pythonで画像ファイルにジオタグ(geotag)を追加しよう!」です。GPSを搭載したスマートフォンなどは、撮影時にその場所の緯度経度情報を写真データに埋め込むことが可能で、この位置情報を「ジオタグ」と呼びます。このジオタグですが、注意が必要です。自宅で撮った写真をブログやSNSで公開する場合、ジオタグ付きの写真をそのままネットで公開してしまうと、撮影場所の緯度経度、つまりあなたの住所が丸わかりになってしまいます。悪用されたら怖いですね・・・

インストール

pyexiv2、PILが必要なので、インストールをお願いします。

サンプルデータ

神戸市のオープンデータを使用してみました。
f:id:sanvarie:20160120112028p:plain

サンプルコード

1ファイルだけを対象としたサンプルコードです。

# -*- coding: utf-8 -*-
import pyexiv2
from PIL import Image

def to_deg(value, loc):
    if value < 0:
        loc_value = loc[0]
    elif value > 0:
        loc_value = loc[1]
    else:
        loc_value = ""
    abs_value = abs(value)
    deg =  int(abs_value)
    t1 = (abs_value-deg)*60
    min = int(t1)
    sec = round((t1 - min)* 60, 5)
    return (deg, min, sec, loc_value)

def set_gps_location(file_name, lat, lng):

    lat_deg = to_deg(lat, ["S", "N"])
    lng_deg = to_deg(lng, ["W", "E"])

    # 緯度、経度を10進法→60進法(度分秒)に変換
    exiv_lat = (pyexiv2.Rational(lat_deg[0]*60+lat_deg[1],60),pyexiv2.Rational(lat_deg[2]*100,6000), pyexiv2.Rational(0, 1))
    exiv_lng = (pyexiv2.Rational(lng_deg[0]*60+lng_deg[1],60),pyexiv2.Rational(lng_deg[2]*100,6000), pyexiv2.Rational(0, 1))
    metadata = pyexiv2.ImageMetadata(file_name)
    metadata.read()

    #メタデータを付与
    metadata["Exif.GPSInfo.GPSLatitude"] = exiv_lat
    metadata["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3]
    metadata["Exif.GPSInfo.GPSLongitude"] = exiv_lng
    metadata["Exif.GPSInfo.GPSLongitudeRef"] = lng_deg[3]
    metadata["Exif.Image.GPSTag"] = 654
    metadata["Exif.GPSInfo.GPSMapDatum"] = "WGS-84"
    metadata["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0'

    #メタデータを上書き
    metadata.write()

#ファイル名、緯度、経度を指定
set_gps_location(r"D:\python\geotag\a020.jpg", 34.6874422,135.1758424)

結果を見ると、指定した緯度経度が設定されていますね。
f:id:sanvarie:20160120112441p:plain

ためしにArcMap上でジオタグ付き写真→ポイントに変換してみました。それっぽい場所に配置されましたね。
f:id:sanvarie:20160120113036p:plain

ポイントをクリックすると写真のポップアップを起動することもできます。これはサイズ調整が必要ですが・・・
f:id:sanvarie:20160120113139p:plain

これをうまく活用すれば震災マップなども作成することができますね。

以上、簡単ではありますが、「Pythonで画像ファイルにジオタグ(geotag)を追加しよう!」でした。

【ArcMapとArcpy】オープンデータを使って自分だけの地図を作ろう! ~factbook編~

今回はfactbookを使用してみようと思います。factbookとは世界各国に関する情報を年鑑形式でまとめたアメリカ合衆国中央情報局 (CIA) の年次刊行物のことです。こんな便利なものが公開されているのですね。

ダウンロード

以下サイトからダウンロードします。JSONファイルです。
factbook/factbook.json: World Factbook Coun... - GitHub

JSONなのでこんな感じですね。項目一覧が無いので、欲しい情報がどれなのか一つ一つ項目を確認しなければならないのがちょっとしんどいですね。
f:id:sanvarie:20160119112336p:plain

サンプルコード

今回はその国の人口における65歳以上の割合、普通出生率GDP($)を取得したいと思います。普通出生率とは日本で一般的な合計特殊出生率ではなく、人口1000人あたりにおける出生数らしいです。まずはJSONのデータを取得するサンプルです。

# -*- coding: utf-8 -*-
import json
from os.path import join,relpath
from glob import glob
import pandas as pd

#ダウンロードしたフォルダのパスを指定
path = r"D:\python\factbook\factbook.json-master"

dataList = []

#各大陸のフォルダをループ
for fs in [relpath(x,path) for x in glob(join(path,'*'))]:
    #フォルダの場合
    if fs.find('.') == -1:
        folder = join(path , fs)
        #フォルダ内のjsonファイルをループ
        for js in [relpath(x,folder) for x in glob(join(folder,'*'))]:
            if js != "xx.json":
                #jsonファイルを開く
                with open(join(folder , js), "r") as f:
                    #ロード
                    jsonData = json.load(f)
                    #ここから階層をくだっていく
                    for name in jsonData.keys():
                        groupDict = jsonData[name]
                        if name == "Government":
                            #この下のキーでループ
                            for name2 in groupDict.keys():
                                groupDict2 =  groupDict[name2]
                                if name2 == "Country name":
                                    #この下のキーでループ
                                    for name3 in groupDict2.keys():
                                        groupDict3 =  groupDict2[name3]
                                        if name3 == "conventional short form":
                                            #この下のキーでループ
                                            for name4 in groupDict3.keys():
                                                groupDict4 =  groupDict3[name4]
                                                if name4 == "text":
                                                    counryName = groupDict4 + ","
                        elif name == "People and Society":
                            #この下のキーでループ
                            for name2 in groupDict.keys():
                                groupDict2 =  groupDict[name2]
                                if name2 == "Age structure":
                                    #この下のキーでループ
                                    for name3 in groupDict2.keys():
                                        groupDict3 =  groupDict2[name3]
                                        #65歳以上の割合
                                        if name3 == "65 years and over":
                                            #この下のキーでループ
                                            for name4 in groupDict3.keys():
                                                groupDict4 =  groupDict3[name4]
                                                if name4 == "text":
                                                    overSixtyFive = groupDict4[0:groupDict4.find("%")] + ","
                                #普通出生率
                                elif name2 == "Birth rate":
                                    for name3 in groupDict2.keys():
                                        groupDict3 =  groupDict2[name3]
                                        if name3 == "text":
                                            birthRate = groupDict3[0:groupDict3.find(" births")] + ","
                        elif name == "Economy":
                            #この下のキーでループ
                            for name2 in groupDict.keys():
                                groupDict2 =  groupDict[name2]
                                #GDP($)
                                if name2 == "GDP (official exchange rate)":
                                    #この下のキーでループ
                                    for name3 in groupDict2.keys():
                                        groupDict3 =  groupDict2[name3]
                                        if name3 == "text":
                                            if groupDict3.find(' million') != -1:
                                                GDP = str(float(groupDict3[1:groupDict3.find(" million")]) * 1000000)
                                            elif groupDict3.find(' billion') != -1:
                                                GDP = str(float(groupDict3[1:groupDict3.find(" billion")]) * 1000000000)
                                            elif groupDict3.find(' trillion') != -1:
                                                GDP = str(float(groupDict3[1:groupDict3.find(" trillion")]) * 1000000000000)
                                            else:
                                                GDP = "0"

                dataList.append([counryName,overSixtyFive,birthRate,GDP])

            #データフレーム作成
            df = pd.DataFrame(dataList)
            df.columns = [u"counryName",u"overSixtyFive",u"birthRate","GDP"]

factbookを世界地図に反映

以前使用した世界地図を用意します。

f:id:sanvarie:20160119120535p:plain

まず、このレイヤにカラム追加します。

arcpy.AddField_management("TM_WORLD_BORDERS-0.3", "overSF", "Double")
arcpy.AddField_management("TM_WORLD_BORDERS-0.3", "birthRate", "Double")
arcpy.AddField_management("TM_WORLD_BORDERS-0.3", "GDP", "Double")

こんな感じになったら成功です。
f:id:sanvarie:20160119132039p:plain

次にJSONから取得した値をフィーチャの属性に反映させます。

for key,rowD in df.iterrows():
    cursorJ = arcpy.UpdateCursor("TM_WORLD_BORDERS-0.3")
    for rowJ in cursorJ:
        #国名が微妙に異なるのはとりあえず無視(GambiaとThe Gambiaとか)
        if rowD.counryName == rowJ.NAME:
            rowJ.setValue("overSF", rowD.overSixtyFive)
            rowJ.setValue("birthRate", rowD.birthRate)
            rowJ.setValue("GDP", rowD.GDP)
            cursorJ.updateRow(rowJ)

こんな感じになったら成功です。
f:id:sanvarie:20160119132959p:plain

分析

データもそろったので分析をしてみたいと思います。

人口における65歳以上の割合

レイヤの設定を以下のようにします。
f:id:sanvarie:20160119133436p:plain

やはりわが日本が厳しい状態ですね。ヨーロッパ、カナダなどの先進国は高齢化していて、その反面、アフリカや南アジアなどの新興国は高齢者の割合が低いことが分かりました(アフリカなどは平均寿命の関係もありそうですが)。
f:id:sanvarie:20160119134215p:plain

普通出生率

前述した通り、普通出生率とは日本で一般的な合計特殊出生率ではなく、人口1000人あたりにおける出生数らしいです。

レイヤの設定を以下のようにします。
f:id:sanvarie:20160119133725p:plain

これもわが日本を含め先進国は厳しい状態ですね。
f:id:sanvarie:20160119133845p:plain

GDP($)

レイヤの設定を以下のようにします。
f:id:sanvarie:20160119134353p:plain

GDPはアメリカと中国が突出していて、次に日本を含めた先進国が高いという結果が出ました。まぁこれはみなさんよくご存知かもしれませんね。また、アフリカ、東欧、東南アジアに今後の伸びしろがあるのがわかります。東南アジア経済は今熱いですしね!
f:id:sanvarie:20160119135727p:plain

今回は65歳以上の割合、普通出生率GDP($)のデータを使用しましたが、factbookにはその他にもたくさんのデータがあります。例えば、エネルギー、軍事、経済、政治などですね。これらをうまく活用すれば世界を色々な視点から分析することが可能になりますね。

以上、「【ArcMapとArcpy】オープンデータを使って自分だけの地図を作ろう! ~factbook編~」でした。

PythonでArcObjectsを使ってみよう!

本日はPythonでのArcobjectsの使用方法を書いてみたいと思います。PythonだとArcpyしか使えず、ちょっと難しいことをやろうと思うと途端にその貧弱さが露呈してしまいましたが、ArcObjectsを使えば大丈夫?!なはずです。それじゃあ最初から.NETで開発しろよというツッコミはなしで(我らがPythonを使いたいのです!)。

Snippets moduleを作成

ArcGISのバージョンごとにこんなものが用意されています。

・9.3, 9.4 - http://pierssen.com/arcgis/upload/misc/python_arcobjects.zip
・10.0 - http://www.pierssen.com/arcgis10/upload/python/snippets100.py
・10.1 - http://www.pierssen.com/arcgis10/upload/python/snippets101.py
・10.2 - http://www.pierssen.com/arcgis10/upload/python/snippets102.py

これらのうちいずれかを例えば、C:\Python27\ArcGIS10.2\Scriptsに配置してください。# -*- coding: utf-8 -*- を忘れずに!

comtypesのインストール

https://pypi.python.org/pypi/comtypesにソースがありますので、そこからpython setup.py install からのeasy_install comtypes-1.1.2-py2.7.egg ですね。

サンプルコード

ポイントを作ってその座標を設定するサンプルです。

# -*- coding: utf-8 -*-
from comtypes.client import GetModule, CreateObject
from snippets102 import GetLibPath, InitStandalone
m = GetModule(GetLibPath() + "esriGeometry.olb")
p = CreateObject(m.Point, interface=m.IPoint)
p.PutCoords(2,3)
print p.X, p.Y

詳細は使用方法についてはこれから調査してみたいと思います。

以上、本日は「PythonでArcObjectsを使ってみよう!」でした。

【ArcMapとArcpy】オープンデータを使って自分だけの地図を作ろう! ~世界の空港編~

今回はオープンデータについてです。オープンデータとは誰もが自由に利用でき、再利用や再配布が許可されているデータのことを指します。つまり、著作権フリーで自由に利用可能」ということです。素晴らしいですね!オープンデータをうまく活用すれば地図上にこんなことをすることが可能です。

①「世界のサッカー場を配置してみる」
②「世界のダイビングポイントを配置してみる」
③「世界のお酒の名店を配置してみる」

ただし、それぞれ座標を調べる必要があるので、そういったものが公開されているデータを利用するのが現実的かと思います。←①を作りたいのですが、公開されているデータがなく断念しました。
とりあえず、今回は世界の空港が配置された世界地図を作ってみようと思います。まずは素の世界地図をthematicmapping.orgからダウンロードします。

いい感じですね!
f:id:sanvarie:20160116153906p:plain

続いて、空港のデータをOpenFlights: Airport and airline dataからダウンロードします。

datになっていますね。
f:id:sanvarie:20160116154151p:plain

コピペしてCSVを作成します。以下のような感じですね。
f:id:sanvarie:20160116155208p:plain

そして、マップレイヤに作成したCSVを追加してください。
f:id:sanvarie:20160116170853p:plain

CSVを右クリックして「XYデータの表示」をクリックしてください。←これArcpyでできないのかなぁ。
f:id:sanvarie:20160116171028p:plain

こんな感じでOKをおしてください。
f:id:sanvarie:20160116171100p:plain

エラーが出ますが、無視してかまいません。
f:id:sanvarie:20160116171128p:plain

ポイントが配置されました。空港ってこんなあるんですね(笑)
f:id:sanvarie:20160116171241p:plain

日本だけでもこんなにあります。
f:id:sanvarie:20160116171735p:plain

とりあえず国別の空港数と1空港あたりの利用人数を抽出してみました。

サンプルコード

numpyとpandasが必要です。

# -*- coding: utf-8 -*-
import arcpy
import pandas as pd
import numpy as np

#フィーチャの属性をpandasに食べさせる
arrAir = arcpy.da.TableToNumPyArray("AirPort", ('Country'))
df = pd.DataFrame(arrAir)

#国ごとの空港数を集計する
dfAir = pd.DataFrame(df['Country'].value_counts())
dfAir.columns = ['AirPortNum']
dfAir['Country'] = 0
for key,rowD in dfAir.iterrows():
    dfAir['Country'][key] = key

#フィーチャの属性をpandasに食べさせる
arrCountry = arcpy.da.TableToNumPyArray("TM_WORLD_BORDERS-0.3", ('NAME','POP2005'))
dfCountry = pd.DataFrame(arrCountry)
dfCountry.columns = ['Country', 'POP2005']

#データフレームを結合させる
dfMerge= pd.merge(dfAir,dfCountry,on='Country',how='right')

#1空港あたりの利用人数を計算
dfMerge['CAL'] = dfMerge['POP2005'] / dfMerge['AirPortNum']

#CSV出力
dfMerge.to_csv('D:\python\global\AirPortByCountry.csv',encoding="SHIFT-JIS")

出力したCSVを確認してみるとアメリカが圧倒的ですね。AirPortNumが空港数、POP2005が2005年の人口、CALが1空港あたりの利用人数です。これを元に国の色分けをしたらおもしろそうなので、以下スクリプトを追加してください。
f:id:sanvarie:20160116184249p:plain

#カラム追加
arcpy.AddField_management("TM_WORLD_BORDERS-0.3", "CAL", "Double")

for key,rowD in dfMerge.iterrows():
    cursorJ = arcpy.UpdateCursor("TM_WORLD_BORDERS-0.3")
    for rowJ in cursorJ:
        if rowD.Country == rowJ.NAME:
            rowJ.setValue("CAL", rowD.CAL)
            cursorJ.updateRow(rowJ)

こうなったら成功です。
f:id:sanvarie:20160116185234p:plain

シンボル設定をこのようにしてみました。
f:id:sanvarie:20160116185430p:plain

結果を見ると中国、インドなど人口の多い国が1空港あたりの利用人数が多いということがわかりました。また、欧米は1空港あたりの利用人数が少ないことがわかりました。なんでもヨーロッパなんかは線路を引くのが面倒だから飛行機を飛ばしちまえみたいな感覚だとかなんとか。
f:id:sanvarie:20160116185459p:plain

データの保存

データ>データのエクスポート を行います。これでデータの作成が完了しました。
f:id:sanvarie:20160116171408p:plain

f:id:sanvarie:20160116171517p:plain

こんな感じでシンボルを設定してもおもしろいですね。
f:id:sanvarie:20160116172054p:plain

上記ではShapeに出力しましたが、GDBに保存して、シンボル設定をしたレイヤファイルを作成した方がいいかもしれませんね。そして、今回はたまたま空港のデータを使用しましたが、色々探せばおもしろいデータを発見することができそうですね。例えば、国ごとの犯罪率や地震発生率などを視覚化することができますし、また、サッカーW杯優勝回数やその国に属するクラブの世界ランキングを考慮した上の国ごとの世界ランキングなんかも作れると思います。その場合、やはりヨーロッパ、南米が強いということが視覚的にわかるのではないでしょうか。次回以降も色々やってみたいと思います。

以上、本日は「【ArcMapとArcpy】オープンデータを使って自分だけの地図を作ろう! ~世界の空港編~」でした。

【Pythonで分析】ArcpyとPandasを使用して将来推計人口を視覚化する

本日は将来推計人口について考えてみます。将来推計人口とは、国連や各国政府が推計した将来の人口のことです。日本では、直近の国勢調査による人口数を基に、出生率や死亡率などを考慮して推計し、国立社会保障・人口問題研究所がほぼ5年ごとに作成、公表します。

今回は2015年の人口を「1」として将来推計人口の色分けを行ってみたいと思います。

日本地図の作成

まずは、前回使用した日本地図を都道府県ごとに集約します。つまり、ディゾルブですね。以下スクリプトを使用してください。

# -*- coding: utf-8 -*-
import arcpy

#入力用のShapeファイルと出力用のフィーチャクラスなどを指定
arcpy.Dissolve_management("D:\python\population\japan_ver80.shp", "C:\ArcPySample\ArcPySample.gdb\japan",
                          "KEN", "", "MULTI_PART",
                          "DISSOLVE_LINES")

これが
f:id:sanvarie:20160112081208p:plain

こんな感じでディゾルブされました。本当は市区町村別に出したかったのですが、一覧がみつからず今回は断念して、都道府県別にします。。。
f:id:sanvarie:20160112081212p:plain

将来推計人口データのダウンロード

国立社会保障・人口問題研究所から都道府県別の将来推計人口データをダウンロードします。
http://www.ipss.go.jp/pp-fuken/j/fuken2007/suikei.html

f:id:sanvarie:20160112082205p:plain

ダウンロードしたEXCELを以下のようなCSVに加工してください。
f:id:sanvarie:20160112144637p:plain

pandasのインストール

こういった場合はpandasが便利です。インストールをお願いします。

サンプルコード①

2015年の人口を「1」として将来推計人口の算出を行います。

# -*- coding: utf-8 -*-
import pandas as pd

#csv読込
df = pd.read_csv("D:\python\population\population.csv",encoding="SHIFT-JIS")

#pandasのカラム追加
df['percent2020'] = ""
df['percent2025'] = ""
df['percent2030'] = ""
df['percent2035'] = ""

#japanレイヤにカラムを追加
arcpy.AddField_management("japan", "PercentOf2020", "Double")
arcpy.AddField_management("japan", "PercentOf2025", "Double")
arcpy.AddField_management("japan", "PercentOf2030", "Double")
arcpy.AddField_management("japan", "PercentOf2035", "Double")

for key,rowD in df.iterrows():
    cursorJ = arcpy.UpdateCursor("japan")
    for rowJ in cursorJ:
        if rowD.todouhuken == rowJ.KEN:
            percent2020 = float(rowD.year2020.replace(",", "")) / float(rowD.year2015.replace(",", ""))
            percent2025 = float(rowD.year2025.replace(",", "")) / float(rowD.year2015.replace(",", ""))
            percent2030 = float(rowD.year2030.replace(",", "")) / float(rowD.year2015.replace(",", ""))
            percent2035 = float(rowD.year2035.replace(",", "")) / float(rowD.year2015.replace(",", ""))
            rowJ.setValue("PercentOf2020", percent2020)
            rowJ.setValue("PercentOf2025", percent2025)
            rowJ.setValue("PercentOf2030", percent2030)
            rowJ.setValue("PercentOf2035", percent2035)
            cursorJ.updateRow(rowJ)

正常終了後、テーブルウインドウを確認してみてください。以下のようになっていたら成功です。
f:id:sanvarie:20160113081311p:plain

以下のようにレイヤのプロパティを変更します。
f:id:sanvarie:20160113100246p:plain

サンプルコード②

以下スクリプトをArcMap上で実行してください。タイマーなどをしかけて一定間隔ごとに描画させてもいいかもしれませんね。

# -*- coding: utf-8 -*-
import arcpy

mxd = arcpy.mapping.MapDocument("CURRENT")
for lyr in arcpy.mapping.ListLayers(mxd):
    if lyr.symbologyType == "GRADUATED_COLORS":
        if lyr.symbology.valueField == "":
            lyr.symbology.valueField = "PercentOf2020"
        elif lyr.symbology.valueField == "PercentOf2020":
            lyr.symbology.valueField = "PercentOf2025"
        elif lyr.symbology.valueField == "PercentOf2025":
            lyr.symbology.valueField = "PercentOf2030"
        elif lyr.symbology.valueField == "PercentOf2030":
            lyr.symbology.valueField = "PercentOf2035"
        else:
            lyr.symbology.valueField = "PercentOf2020"
        lyr.symbology.classBreakValues = [0,0.80 ,0.825 ,0.85, 0.875, 0.90, 0.925, 0.95 , 0.975, 1.00, 1.025, 1.05,1.075, 1.10]
        arcpy.RefreshActiveView()
2020年の将来推計人口

ほとんどの地域が微減といったところでしょうか。東京と見づらいのですが沖縄だけ唯一微増していますね。
f:id:sanvarie:20160113095422p:plain

2025年の将来推計人口

北海道、東北、北陸、四国、山陰、九州南部に顕著な現象が見られます。
f:id:sanvarie:20160113095522p:plain

2030年の将来推計人口

ほとんどの地域で大きな人口減少が見られます。秋田県はなんと現在の人口の8割程度になってしまうみたいですね。
f:id:sanvarie:20160113095644p:plain

2035年の将来推計人口

どの地域も現在と比べると8割、良くて9割程度の人口になってしまうみたいですね。沖縄だけは微増をキープしています。これは興味深いですね。
f:id:sanvarie:20160113095726p:plain

2035年以降も人口減少は続くと思うので、このままだと日本の衰退は確実ですね。かといって移民政策をとるわけにもいかず、出生率をあげることもできず、ジリ貧が続くのではないでしょうか。なんとか良くなってほしいものですがなかなか難しい問題ですね。今回は将来推計人口についてでしたが、高齢化についての分析などもおもしろそうですね。

以上、「ArcpyとPandasを使用して将来推計人口を視覚化する」でした。

Pythonで基盤地図情報の数値標高モデルを解析してGeoTiffに変換する(横須賀編) ~Numpy、GDAL、Arcpyを駆使して横須賀の標高(地盤高)を視覚化する~

今回は数値標高モデル(DEM)の解析を行ってみたいと思います。数値標高モデル(DEM)とは地形のデジタル表現のことで我らが国土地理院が公開しています。参考までにですが、これは火星のティトニウム・カズマの数値標高モデル(DEM)を三次元表示した画像です。
f:id:sanvarie:20160110140732p:plain

基盤地図情報(数値標高モデル)データのダウンロード

以下サイトでダウンロードできます。ユーザー登録が必要です。
基盤地図情報ダウンロードサービス

横須賀が含まれるデータをダウンロードします。
f:id:sanvarie:20160108210231p:plain

こんな感じで任意のフォルダにXMLのみを格納してください。承認申請のXMLは不要です。また、GeoTiff出力用のフォルダも作成してください。
f:id:sanvarie:20160108211326p:plain

基盤地図情報(数値標高モデル)データを解析する

ファイル名について

基盤地図情報(数値標高モデル)データには 5mメッシュと10mメッシュがありますが、今回はより細かい 5mメッシュを使用します。

ファイル名は以下の形式になっています。

FG-GML-aaaa-bb-cc-DEM5X-yyyymmdd.xml

・フォーマット
aaaaは1次メッシュ番号
bbは2次メッシュ番号
ccは3次メッシュ番号
Xは測量方法 - A:レーザー測量、B:写真測量
yyyymmddは年月日

「FG-GML-5439-13-24-DEM5A-20130702.xml」みたいな感じですね。

ファイルの中身について

重要な情報のみを記述します。

・緯度経度
これをもとにGeoTiffに変換します。

<gml:lowerCorner>35.175 139.6</gml:lowerCorner>
<gml:upperCorner>35.183333333 139.6125</gml:upperCorner>

・GridEnvelope
メッシュを構成するグリッドセルの配列数を記載します。low属性は北西端を示す番号です。常に(0,0)です。
high属性は南東端を示す番号です。5mメッシュは(224,149)の固定です。つまり、5mメッシュのXMLは225×150=33750行の標高データが格納されているということです。ただし、後述しますが、startPointの値によってはその限りではありません。

<gml:limits>
    <gml:GridEnvelope>
        <gml:low>0 0</gml:low>
        <gml:high>224 149</gml:high>
    </gml:GridEnvelope>
</gml:limits>

・DataBlock

各グリッドセルの標高データが記載されます。tupleListタグで囲まれたデータが標高データになります。水面や標高データがない場合は、標高の値が"-9999."になります。

<gml:DataBlock>
	<gml:rangeParameters>
		<gml:QuantityList  uom="DEM構成点"/>
	</gml:rangeParameters>
	<gml:tupleList>
	地表面,23.16
	地表面,23.16
	地表面,23.61
	・
	・
	・
	</gml:tupleList>
</gml:DataBlock>

・coverageFunction
sequenceRuleでデータの並びを指定します。orderの"+x"はxが正の方向(西→東)であることを、"-y"はyは負の方向(北→南)であることを示します。北西端から南東端に向けて次のような並びで一列にしたデータがtupleListタグ内に配置されます。

f:id:sanvarie:20160108092737p:plain

gml:coverageFunction>
	<gml:GridFunction>
		<gml:sequenceRule  order="+x-y">Linear</gml:sequenceRule>
		<gml:startPoint>0 0</gml:startPoint>
	</gml:GridFunction>
</gml:coverageFunction>

startPointでグリッドセルの開始点を指定します。先頭部分にデータが存在しない場合、このようになります。

<gml:startPoint>5 1</gml:startPoint>

これの意味はデータのスタートがX方向が05列、Y方向が01行ということです。また、末尾部分にデータが存在しないグリッドは省略し、省略した旨を記載するタグは存在しません。
ですので、startPointを考慮してもデータ数が合わない場合は末尾部分のデータは省略されているということです。

上記の詳細は国土地理院にて公開されています。
http://fgd.gsi.go.jp/otherdata/spec/2014/FGD_DLFileSpecV4.0.pdf#search=%27%E5%9F%BA%E7%9B%A4%E5%9C%B0%E5%9B%B3%E6%83%85%E5%A0%B1%E3%83%80%E3%82%A6%E3%83%B3%E3%83%AD%E3%83%BC%E3%83%89%E3%83%87%E3%83%BC%E3%82%BF+%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BB%95%E6%A7%98%E6%9B%B8+%EF%BC%88%E5%B9%B3%E6%88%9026%E5%B9%B47%E6%9C%88+%E5%9B%BD%E5%9C%9F%E4%BA%A4%E9%80%9A%E7%9C%81%E5%9B%BD%E5%9C%9F%E5%9C%B0%E7%90%86%E9%99%A2%EF%BC%89%27

NumpyとGDALのインストール

配列を扱う場合はNumpyですね。また、GDALも必要なので、インストールをお願いします。

サンプルソース

ちょっと無理矢理感は否めませんが、参考までに。

# -*- coding: utf-8 -*-
import re
import numpy as np
import gdal
from os.path import join,relpath
from glob import glob

def main():
    #XMLを格納するフォルダ
    path = "D:\python\DEM"
    #GeoTiffを出力するフォルダ
    geopath = "D:\python\GeoTiff"
    #ファイル名取得
    files = [relpath(x,path) for x in glob(join(path,'*'))]

    for fl in files:
        xmlFile = join(path,fl)
        #XMLを開く
        with open(xmlFile, "r", encoding = "utf-8") as f:
            r = re.compile("<gml:lowerCorner>(.+) (.+)</gml:lowerCorner>")
            for ln in f:
                m = r.search(ln)
                #検索パターンとマッチした場合、スタートポジションを格納
                if m != None:
                    lry = float2(m.group(1))
                    ulx = float2(m.group(2))
                    break

            r = re.compile("<gml:upperCorner>(.+) (.+)</gml:upperCorner>")

            for ln in f:
                m = r.search(ln)

                #検索パターンとマッチした場合、スタートポジションを格納
                if m != None:
                    uly = float2(m.group(1))
                    lrx = float2(m.group(2))
                    break


            # 検索パターンをコンパイル
            r = re.compile("<gml:high>(.+) (.+)</gml:high>")
            for ln in f:
                m = r.search(ln)
                #検索パターンとマッチした場合、縦横の領域を格納
                if m != None:
                    xlen = int(m.group(1)) + 1
                    ylen = int(m.group(2)) + 1
                    break

            startx = starty = 0

            # 検索パターンをコンパイル
            r = re.compile("<gml:startPoint>(.+) (.+)</gml:startPoint>")
            for ln in f:
                m = r.search(ln)
                #検索パターンとマッチした場合、スタートポジションを格納
                if m != None:
                    startx = int(m.group(1))
                    starty = int(m.group(2))
                    break

        #numpy用にデータを格納しておく
        with open(xmlFile, "r", encoding = "utf-8") as f:
            src_document = f.read()
            lines = src_document.split("\n")
            num_lines = len(lines)
            l1 = None
            l2 = None
            for i in range(num_lines):
                if lines[i].find("<gml:tupleList>") != -1:
                    l1 = i + 1
                    break
            for i in range(num_lines - 1, -1, -1):
                if lines[i].find("</gml:tupleList>") != -1:
                    l2 = i - 1
                    break

        #セルのサイズを算出
        psize_x = (lrx - ulx) / xlen
        psize_y = (lry - uly) / ylen

        geotransform = [ulx, psize_x, 0, uly, 0, psize_y]
        create_options = [] #空のままでOKっぽい
        driver = gdal.GetDriverByName("GTiff")

        #拡張子を変更する(ファイル名はそのまま)
        dst = fl.replace('.xml', '.tif')
        tiffFile = join(geopath,dst)
        dst_ds = driver.Create(tiffFile, xlen, ylen, 1, gdal.GDT_Float32, create_options)

        dst_ds.SetProjection('GEOGCS["JGD2000",DATUM["Japanese_Geodetic_Datum_2000",SPHEROID["GRS 1980",6378137,298.257222101,AUTHORITY["EPSG","7019"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6612"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4612"]]')
        dst_ds.SetGeoTransform(geotransform)
        rband = dst_ds.GetRasterBand(1)

        narray = np.empty((ylen, xlen), np.float32)
        narray.fill(0)

        num_tuples = l2 - l1 + 1

        #スタートポジションを算出
        start_pos = starty*xlen + startx

        i = 0
        sx = startx

        #標高を格納
        for y in range(starty, ylen):
            for x in range(sx, xlen):
                if i < num_tuples:
                    vals = lines[i + l1].split(",")
                    if len(vals) == 2 and vals[1].find("-99") == -1:
                        narray[y][x] = float(vals[1])
                    i += 1
                else:
                    break
            if i == num_tuples: break
            sx = 0

        rband.WriteRaster(0, 0, xlen, ylen, narray.tostring())
        dst_ds.FlushCache()

def float2(str):
    lc = ""
    for i in range(len(str)):
        c = str[i]
        if c == lc:
            renzoku += 1
            if renzoku == 6:
                return float(str[:i+1] + c * 10)
        else:
            lc = c
            renzoku = 1
        return float(str)

if __name__ == "__main__":
    main()

結果を確認してみます。
Arcpyを使用してJ-SHISの表層地盤データを分析してみよう(横須賀編) ~ArcpyでShape読込、Shape出力、属性検索、空間検索、フィールド演算~ - GIS奮闘記 で使用した日本地図とGeoTiffを重ねあわせてみます。
f:id:sanvarie:20160110142332p:plain

おぉー。なんだかそれっぽいのが表示されました。しかし、まだ以下をする必要がありますね。
・取り込んだGeoTiffを一枚のラスターに変換(色の設定を一ファイルずつ行うのは非現実的なので。ちなみに、全部で239個のGeoTiffを取り込みました)。
・色の設定(白黒だと若干見づらいので)

ただ、ここからはArcpyを使用しますので、ArcGISのライセンスが無いという方はすみません。以下スクリプトでGeoTiffを一枚のラスタにします。

# -*- coding: utf-8 -*-
import arcpy
from glob import glob
from os.path import join,relpath

#GeoTiffを格納しているディレクトリ
path = "D:\python\GeoTiff"

#ファイル名取得
files = [relpath(x,path) for x in glob(join(path,'*'))]

#MosaicToNewRaster_managementの引数を作成
for fl in files:
    raster = raster + fl
    raster = raster + ";"
raster = raster[:-1]

#以下のように任意のフォルダをワークスペースにする
arcpy.env.workspace = "D:\python\Raster"

#ラスタを一枚にする
arcpy.MosaicToNewRaster_management(input_rasters = raster, output_location = "D:\python\Raster", \
                                   raster_dataset_name_with_extension = "DEM.tif",\
                                   pixel_type = "8_BIT_UNSIGNED", number_of_bands = "1", mosaic_method = "LAST",mosaic_colormap_mode = "FIRST")

こんな感じで一枚のラスタにすることができました。
f:id:sanvarie:20160110161430p:plain

ラスタのプロパティで色の設定をします。
f:id:sanvarie:20160110152713p:plain

それっぽい感じになりましたね。
f:id:sanvarie:20160110153929p:plain

あとは横須賀の範囲でラスタをクリップしたいのですが、Spatial Analystのライセンスを持っていないのでそこはあきらめます。単純なクリップ機能を使用すればある程度は抽出できますね。まずは以下スクリプトで横須賀のフィーチャを抽出します。

mxd = arcpy.mapping.MapDocument("CURRENT")
df = arcpy.mapping.ListDataFrames(mxd)[0]

#読み込んでいる日本地図を利用
addLayer = arcpy.mapping.Layer("japan_ver80")

#レイヤを追加
arcpy.mapping.AddLayer(df, addLayer, "BOTTOM")

#属性検索
zokuseiSearch = arcpy.SelectLayerByAttribute_management(addLayer.name,"NEW_SELECTION","SIKUCHOSON = '横須賀市'")

#Shape出力
arcpy.FeatureClassToFeatureClass_conversion(zokuseiSearch,
                                            "D:\python\Raster",
                                            "yokosuka")

横須賀のみのレイヤの作成に成功しました。
f:id:sanvarie:20160110155313p:plain

次にラスタのクリップを行います。ラスタデータセット作成用のGDBを一つ作成しておく必要があります。
f:id:sanvarie:20160110155322p:plain

f:id:sanvarie:20160110155545p:plain

こんな感じでラスタを横須賀の範囲くらいまでクリップすることができました(座標の設定などもあったのでここはArcMapを使ってしまいました)。
f:id:sanvarie:20160110155649p:plain

色の設定をします。標高が低い順に水色→緑→茶色→白 と色が設定されました。
f:id:sanvarie:20160110160136p:plain

Arcpyを使用してJ-SHISの表層地盤データを分析してみよう(横須賀編) ~ArcpyでShape読込、Shape出力、属性検索、空間検索、フィールド演算~ - GIS奮闘記 で横須賀は丘陵が多いことがわかりましたが、そのせいか横須賀は全体的に標高が高いですね(たしかに山を切り開いてつくった町も多いですね)。
あとは街区データなどがあれば街ごとに標高の高低差を確認することができ、もっと面白くなりそうですね!ZMAP-TOWN2を買おうかしら。

以上、本日は「Pythonで基盤地図情報の数値標高モデルを解析してGeoTiffに変換する(横須賀編) ~Numpy、GDAL、Arcpyを駆使して横須賀の標高(地盤高)を視覚化する~」でした。

Arcpyを使用してJ-SHISの表層地盤データを分析してみよう(横須賀編) ~ArcpyでShape読込、Shape出力、属性検索、空間検索、フィールド演算~

本日はJ-SHISと地盤についてです。ご存じの方も多いかも知れませんが、J-SHISとは「地震防災に資することを目的に、日本全国の「地震ハザードの共通情報 基盤」として活用されることを目指して作られたサービス」です。ここから全国の地盤データをダウンロードすることができます。詳細は以下サイトをご参照ください。
http://www.j-shis.bosai.go.jp/map/JSHIS2/download.html?lang=jp


横須賀を含むこの区域の表層地盤データをダウンロードします。
f:id:sanvarie:20160106110052p:plain


ただし、これだけだと横須賀以外の地域が含まれてしまいます。ですので、横須賀の表層地盤データのみを抽出したいと思います。

まず、ESRIから全国地図をダウンロードします。
全国市区町村界データ | 製品 | ESRIジャパン


以下pythonスクリプトをArcMap上で実行してください。ダウンロードした表層地盤データと全国地図を使用して横須賀の表層地盤データを抽出します。Arcpyを用いてこのようにけっこう色々なことをします。
・Shapeファイルの読み込み
・属性検索
・空間検索
・Shape出力
・読込したShapeファイルの削除
・フィールド演算

# -*- coding: utf-8 -*-
import arcpy

#J-SHISのデータ(表層地盤)
inLyr1 = ur"D:\python\地盤高\Z-V3-JAPAN-AMP-VS400_M250-SHAPE-5239.shp"

#ESRIのデータ(日本地図)
inLyr2 = ur"D:\python\地盤高\japan_ver80.shp"

mxd = arcpy.mapping.MapDocument("CURRENT")
df = arcpy.mapping.ListDataFrames(mxd)[0]
addLayer1 = arcpy.mapping.Layer(inLyr1)
addLayer2 = arcpy.mapping.Layer(inLyr2)

#Shapeファイルの読込
arcpy.mapping.AddLayer(df, addLayer1, "BOTTOM")
arcpy.mapping.AddLayer(df, addLayer2, "BOTTOM")

#属性検索(横須賀市を検索)
zokuseiSearch = arcpy.SelectLayerByAttribute_management(addLayer2.name,"NEW_SELECTION","SIKUCHOSON = '横須賀市'")

#空間検索(横須賀に交差する地盤高データを検索)
kukanSearch = arcpy.SelectLayerByLocation_management(addLayer1.name,"INTERSECT",zokuseiSearch,"","NEW_SELECTION")

#空間検索した結果をShape出力
arcpy.FeatureClassToFeatureClass_conversion(kukanSearch,
                                            u"D:\python\地盤高",
                                            "zibandaka")

#出力したShape以外のレイヤを削除
for df in arcpy.mapping.ListDataFrames(mxd):
    for lyr in arcpy.mapping.ListLayers(mxd, "", df):
        if lyr.name != "zibandaka":
            arcpy.mapping.RemoveLayer(df, lyr)

#コードブロック
codeblock = """
def GetBichikei(JCODE):
    if JCODE == 1:
        return "山地"
    elif JCODE == 2:
        return "山麓地"
    elif JCODE == 3:
        return "丘陵"
    elif JCODE == 4:
        return "火山地"
    elif JCODE == 5:
        return "火山山麓地"
    elif JCODE == 6:
        return "火山性丘陵"
    elif JCODE == 7:
        return "岩石台地"
    elif JCODE == 8:
        return "砂礫質台地"
    elif JCODE == 9:
        return "ローム台地"
    elif JCODE == 10:
        return "谷底低地"
    elif JCODE == 11:
        return "扇状地"
    elif JCODE == 12:
        return "自然堤防"
    elif JCODE == 13:
        return "後背湿地"
    elif JCODE == 14:
        return "旧河道"
    elif JCODE == 15:
        return "三角州・海岸低地"
    elif JCODE == 16:
        return "砂州・砂礫州"
    elif JCODE == 17:
        return "砂丘"
    elif JCODE == 18:
        return "砂州・砂丘間低地"
    elif JCODE == 19:
        return "干拓地"
    elif JCODE == 20:
        return "埋立地"
    elif JCODE == 21:
        return "磯・岩礁"
    elif JCODE == 22:
        return "河原"
    elif JCODE == 23:
        return "河道"
    elif JCODE == 24:
        return "湖沼"
    else:
        return "不明"
"""

# 条件式を設定
expression = "GetBichikei(!JCODE!)"

#微地形分類名カラムを追加
arcpy.AddField_management("zibandaka", "bichikei", "TEXT")

#フィールド演算で微地形分類名を設定
arcpy.CalculateField_management("zibandaka", "bichikei", expression, "PYTHON_9.3", codeblock)

以下のような結果が得られました。

f:id:sanvarie:20160106135022p:plain

日本地図と重ねあわせるとこんな感じです。ぴったり横須賀の表層地盤のみ出力できていますね。

f:id:sanvarie:20160106131731p:plain

上記プログラムで微地形分類コードの名称を取得していますが、微地形分類とは簡単にいうと、地盤の種類ですね。詳細は以下サイトで確認できます。
http://www.j-shis.bosai.go.jp/map/JSHIS2/data/DOC/DataFileRule/A-RULES.pdf

次は以下のようにArcMapでzibandakaレイヤのシンボルのプロパティを変更してください。どうやらレイヤのsymbologyTypeがOTHERSになっていると、Arcpyではレンダリングの編集はできないみたいです。なんて貧弱なんだ!←ESRIさんお願いしますよ。

f:id:sanvarie:20160106135322p:plain

このように横須賀の表層地盤の視覚化に成功しました(なぜ横須賀を選んだのかは秘密です)!これを見ると横須賀は丘陵、沿岸部に埋立地が多いことがわかりました。ちなみにこの微地形分類は震災時の被害予測データとして使われたりします。また、これに道路や建物のデータを重ねあわせると視覚的にはさらに面白くなりそうですね。

f:id:sanvarie:20160106154433p:plain

以上、「Arcpyを使用してJ-SHISの表層地盤データを分析してみよう(横須賀編) ~ArcpyでShape読込、Shape出力、属性検索、空間検索、フィールド演算~」でした。