<ruby>kmlつく〜ろ〜

今回は

poolbooyer.hatenablog.jp

の続きです。 前回生成した情報をもとにしてgoogle earthに表示できるようにします。

目次

  1. やること
  2. 使う技術
  3. 具体的なプログラムの構成
  4. 実際のソースコード
  5. その他

1.やること

やることはこんな感じ - Pigで作ったデータを読み込む - 読み込んだデータを年ごとに分ける - それぞれのデータをkml形式に吐き出し という感じです。

2.使う技術

今回はREXMLというものを使います。
REXML
普通にパッケージとして入っているので特に入手のためにアレヤコレヤする必要はありません。

require 'rexml/document'

で使えます。

3. 具体的なプログラムの構成

3.1 そ入力データの確認

今回扱う元のデータはこんな感じです。

f:id:poolbooyer:20190130140355p:plain
入力として使うデータ
(1行目は説明用に書いてますが実際のデータでは消しています) この中のLat(緯度)とLong(経度)に対応する位置にピンを立てます。

3.2 全体の流れ

このプログラムの流れはこんな感じ

  • pigで生成されたデータの読み込み
  • 読み込んだデータの分割(年ごとに配列に振り分け)
  • 配列の各要素ごとに出力を行う

3.3 kmlってそもそもなんだ

kmlの説明をふっとばしてたんでここで説明します。 kmlとはXMLの一種でgoogle earth上で読み込むことで地図上にピンや線を配置することができるようになるものです。 今回はピン表示のみの実現を目指します。

今回作成したいkmlのサンプルはこんな感じ

f:id:poolbooyer:20190130172444p:plain
生成するkmlの例
あんまり詳しく書きませんが今回使うタグの説明はこんな感じ

タグ 説明
Placemark このタグの中に個々のピンの情報を記述
name 位置の名称を記述(ピンのところに表示される)
description ピンのバルーンに表示される内容を記述(html対応)
styleUrl ピンのスタイルを記述したところのURLを記載
Point 位置に関する記述をする
coordinates 位置の情報を経度、緯度、高度の順で記述

スタイルについてはもうちょっと調べて別で記事かくと思います。

4. 実際のソースコード

今回も各部分に分割して書いていきます。

4.1 データの読み込み処理

まずは元になるデータを読み込みます 読み込み方法は普通です。読み込んだデータは改行コード,タブ空白単位で配列に保存します。

コードはこんな感じ

def read_data()
    #データを読み込み
    read_data = []
    File.open("data/output/high/part-r-00000", mode = "rt"){|f|
        read_data = f.read
    }
    #読み込んだデータを改行コードで配列に格納
    read_data=read_data.split("\n")
    line_data=[]
    #配列をtab空白単位で分割
    read_data.each do |line|
        line_data.push(line.split("\t"))
    end
    return line_data
end

特に当たり障りのない普通のファイル読み込みですね。

4.2 データの分割

今のままだと、一行単位で分割されただけになっているので各行のデータをカンマ区切りで分割していく必要があります。 そのための処理を行っていきます。

#データの分割
def divideData(full_data)
    #読み込んだ情報を,単位で分割
    split_data=[]
    full_data.each do |line|
        line.each do |cel|
            split_data.push(cel.split(","))
        end
    end
    return split_data
end

これまた特に当たり障りのない処理ですね。

4.3 情報を年単位でまとめる

このままだと情報が11年分まとまりもなく同じ配列に収まっている状態でいただけないのでそこを解決します。 年単位で情報を分割します。 どうやって分割しようかと思ったのですが、三次元の配列に保存しました。

コード的には三次元ですが、図で書くとこんな感じのイメージです

f:id:poolbooyer:20190130231353p:plain
取り扱うデータのイメージ図

def split_by_year(data)
    #年ごとに配列に一時保存
    stack=[]
    for num in 0..10 do
        stack[num]=[]
    end
    #年ごとに振り分け
    data.each do |line|
        if line[5]=="2008"then
            stack[0].push(line)
        elsif line[5]=="2009" then
            stack[1].push(line)
        elsif line[5]=="2010" then
            stack[2].push(line)
        elsif line[5]=="2011" then
            stack[3].push(line)
        elsif line[5]=="2012" then
            stack[4].push(line)
        elsif line[5]=="2013" then
            stack[5].push(line)
        elsif line[5]=="2014" then
            stack[6].push(line)
        elsif line[5]=="2015" then
            stack[7].push(line)
        elsif line[5]=="2016" then
            stack[8].push(line)
        elsif line[5]=="2017" then
            stack[9].push(line)
        elsif line[5]=="2018" then
            stack[10].push(line)
        end
    end
    return stack
end

4.4 まとめた情報を出力する

さあ、本編(?)です。
今回のいちばん重要な部分です。kml出力処理を行います。
kmlで出力する時の流れとしては

  • オブジェクトを作る
  • 作ったオブジェクトにclass,内容を追加(add_attribute,add_text)
  • 親のオブジェクトに追加(add_element)
  • 全体をファイル出力

といった流れです。 実際のプログラムは下のとおりです。

#kml出力を行う
def create_kml(data)
    #元のノード(コメント)を生成
    doc = REXML::Document.new
    doc << REXML::XMLDecl.new('1.0', 'UTF-8')

    # ルートノードを作成
    kml = REXML::Element.new('kml')
    kml.add_attribute('xmlns','http://www.opengis.net/kml/2.2')
    doc.add_element(kml)
    
    #ドキュメントノードを作成
    root = REXML::Element.new('Document')
    # ルートノードの下に子ノードを追加
    data.each do |line|
        #台風番号を名称に格納
        name = REXML::Element.new('name')
        name.add_text(line[0])

        #親ノードに名称を追加
        placemark = REXML::Element.new('Placemark')
        placemark.add_element(name)

        #説明を追加
        description = REXML::Element.new('description')
        title="<h1>"+line[5]+""+line[0][-2..-1]+"号</h1>"
        date="<dl><dt>date</dt><dd>"+line[5]+"/"+line[6]+"/"+line[7]+" "+line[8]+"時</dd></dl>"
        info="<dl><dt>気圧</dt><dd>"+line[1]+"</dd><dt>データ数</dt><dd>"+line[4]+"</dd></dl>"
        text=title+date+info
        description.add_text(text)
        
        #親ノードに名称を追加
        placemark.add_element(description)

        #スタイル読み込み用タグを追加
        styleurl= REXML::Element.new('styleUrl')
        stylepath="style.kml#"+line[5]
        styleurl.add_text(stylepath)
        placemark.add_element(styleurl)
        #位置情報を格納
        point = REXML::Element.new('Point')
        coordinates = REXML::Element.new('coordinates')
        coordinates.add_text(line[3]+","+line[2]+",0.")
        
        #親ノードに位置情報を追加
        point.add_element(coordinates)
        placemark.add_element(point)
        root.add_element(placemark)
    end
    #元ノードに追加
    kml.add_element(root)
    #ファイルに出力
    #各年のファイル名を作成
    fname="kml/high/"+data[0][5]+".kml"
    File.open(fname, 'w') do |file|
        doc.write(file, indent=2)
    end
end

ファイルのパスのところはちょっと変えてます。 これを使うことでkmlで出力できます。 これで実際に出力されたkmlはこんな感じ

f:id:poolbooyer:20190131000848p:plain
出力されるkml

5. その他

生成されたデータをgoogle earthで読み込むとこうなります。

f:id:poolbooyer:20190131000936p:plain
google earthで読み込んでみたよ