GIS奮闘記

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

【ArcMapとArcpy】オープンデータを使って自分だけの地図を作ろう! ~阪神・淡路大震災編~

さて、本日は神戸市のオープンデータを利用してみようと思います。阪神・淡路大震災「1.17の記録」阪神・淡路大震災の記録写真が公開されています。前回行ったようにこの写真にジオタグを追加して、その緯度経度にポイントを配置してみます。

データのダウンロード

上記サイトにおいて、画像データの一括ダウンロード>CSVでダウンロードを行います。
こんな感じのCSVがダウンロードできました。緯度経度の項目があるにも関わらず、ここは空白になっています・・・仕方ないので以前行ったジオコーディングを使用したいと思います。ただ、場所が空白、もしくは、ジオコーディングできない(衣掛町5丁目2「こうべ地震災害対策広報」みたいな記述)写真は取込対象外とします。
f:id:sanvarie:20160121082028p:plain

ポイントとなる技術

主に以下のような技術を使用します。
①画像のダウンロード
②画像にジオタグ(geotag)の追加
③ジオコーディング
ジオタグを追加した画像をポイントに変換

日本地図のダウンロード

こちらも以前行ったようにESRIのサイトからダウンロードを行ってください。
全国市区町村界データ | 製品 | ESRIジャパン

f:id:sanvarie:20160121082956p:plain

サンプルコード

上述した「ポイントとなる技術」を盛り込んだサンプルコードです。パスなどはご自身の環境にあったものに書き換えてください。

# -*- coding: utf-8 -*-
import pandas as pd
import urllib2
import cookielib
import os
import pyexiv2
from PIL import Image
import geocoder
import arcpy

def main():
    #ダウンロードしたcsvの読込
    df = pd.read_csv(r"D:\python\geotag\20160120142702.csv",encoding="SHIFT-JIS")

    #各種設定
    folder = "D:\python\geotag"
    outFeatures = r"C:\ArcPySample\ArcPySample.gdb\geotag"
    badPhotosList = r"C:\ArcPySample\ArcPySample.gdb\empty"
    photoOption = "ONLY_GEOTAGGED"
    attachmentsOption = ""

    cj = cookielib.CookieJar()
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))

    #CSVの行数分ループ
    for key,rowD in df.iterrows():

        #リスエスト
        req = urllib2.Request(rowD.image)

        #出力先を指定
        with open(os.path.join(folder,rowD.filename), 'wb') as f:
            f.write(opener.open(req).read())

        #場所が無いデータは無視
        if type(rowD.place) != float:

            #緯度経度取得
            loc = get_coordinate(rowD.place)

            #井戸経度が取得できた場合
            if loc.lat is not None:
                #ジオタグを画像に追加
                set_gps_location(os.path.join("D:\python\geotag",rowD.filename),loc.lat,loc.lng)
            loc = None

    #ジオタグ付き写真をポイントに変換
    arcpy.GeoTaggedPhotosToPoints_management(folder, outFeatures, badPhotosList, photoOption, attachmentsOption)

def get_coordinate(location_name):
    try:
        #地名から座標を取得する
        ret = geocoder.google(location_name)
    except KeyError, e:
        print "KeyError(%s)" % str(e)
        return

    return ret

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):

    try:
        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()
    except:
        print u"エラー。たまに出る。とりあえず飛ばす"

if __name__ == '__main__':
    main()

結果を確認してみます。それっぽい箇所にポイントが配置されましたが、全然関係ない場所にも配置されてしまっています。これは例えば「若松町」という場所が他の都道府県にも存在するという原因かと思います。完璧に行う場合はもう少し工夫が必要ですがとりあえず気にせず進みます(笑)
f:id:sanvarie:20160121090210p:plain

うまくいったっぽい箇所をGoogleMapと比較して確認してみます。「ポートターミナル周辺」でジオコーディング、GoogleMapで検索した結果です。
f:id:sanvarie:20160121085744p:plain

f:id:sanvarie:20160121085813p:plain

大体あってますかね。ちなみにこんな写真です。ジオタグもちゃんと追加されていますね。
f:id:sanvarie:20160121085942j:plain

f:id:sanvarie:20160121090021p:plain

ArcMapでポイントの個別属性から画像を参照することができます。
f:id:sanvarie:20160121090413p:plain

f:id:sanvarie:20160121090426p:plain

これだけで終わるのもあれなので、ここで一工夫してみようと思います。まずフィールドの追加を行います。

arcpy.AddField_management("geotag", "HTML_Path", "String")

こうなったらOKです。
f:id:sanvarie:20160121090941p:plain

その後、以下のようにフィールド演算を行います。

codeblock = """
def setHTMLPath(Path):
    return "<img src='"  + Path + "'" + "Width='300'>"
"""

expression = "setHTMLPath(!Path!)"
arcpy.CalculateField_management("geotag", "HTML_Path", expression,"PYTHON_9.3",codeblock)

こうなったらOKです。
f:id:sanvarie:20160121091827p:plain

ArcMapでHTMLポップアップ機能を使用します。
f:id:sanvarie:20160121091904p:plain

任意のポイントを選択するとポップアップが起動します。属性にもっと詳細な情報を入れたらより充実したマップになりますね。
f:id:sanvarie:20160121091958p:plain

今回わかったことは
①ダウンロードしたCSVの情報がかなり大雑把。そのため、ジオコーディングに一工夫が必要。
ジオタグ追加→ポイント変換のコンボはかなり面白い!GPSの精度がさらに向上すれば、こういった案件も増えてきそうな予感。
③写真を見るとあらためて震災の怖さというものを感じた。

もっとオープンデータが充実すればさらに精度の高いマップを作成することができると思うので、今後そこを期待したいですね。

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