<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で読み込んでみたよ

<hadoop Pig>pig使ってみた

今回はhadoop pigを使って情報の抽出を行ってみました。
文献が少ないので大したことをしていないのですが書き残しておこうかなと思います。

目次

  1. 今回すること
  2. hadoop pigとは
  3. どのような流れか
  4. 実際のコード . その他

1. 今回すること

今回は気象庁から提供される台風情報のCSVデータ(偶然見つけた)をもとにして、各台風について気圧が最低になったときの情報を抽出します。 元データの例

2. hadoop Pigとは

実際のコードを書く前にhadoop Pigがどのようなものかを簡単に書いておきます。
分散処理を行う際に用いられるhadoopというフレームワーク上で動くスクリプト言語がpigです。ベースはJavaらしいです。 Apacheが提供しているみたいです。 Apache Pig

3.どのような流れか

データ読み込み

元になるCSVファイルをすべて読み込みます。 今回はdata/input/以下に複数の対象データを保存しています。

読み込みデータをソート

読み込まれたデータは日時等の順番がばらばらになっているので年月日時でソート処理を行います。

ソートしたデータのグループ化

ソートしたデータを台風それぞれでグループ化します。各台風での情報の比較がしやすい状態にしていきます。

各台風が最低気圧だった時の情報を抽出

これが今回作成するプログラムの肝になっています。グループ化したそれぞれの台風について、気圧が一番低くなっている時の情報(勢力が一番強くなっている)を切り出すようにします。

古いデータの削除

あんまり気にすることもないとは思うんですが、Pigの仕様として古い出力データが残っているとエラー起こすっていう問題があります。1回しか実行しないなら特にいらないんですが、デバッグとかで回数実行するときは古いデータの削除を行う必要があります。

処理結果の出力

処理結果をファイル出力します。今回は更に加工しようかなと思っていたのでCSVに近い形式で出力します。各行についてカンマ区切りでデータを書き出しました。

4.実際のコード

実際のコードも上と同じ順番で軽く書いていきます。

データの読み込み

データを読み込むときにはLOADメゾッドを使います

-- データを読み込む typhoon_input = LOAD 'data/input/'USING PigStorage( ',' ) AS (year:int, month:int, day:int, time:int,num:int, name:chararray,level:int, lat:double, long:double,power:double );

USING PigStorage(',')を使うことで、データの区切り文字を指定できます。 AS以降には各データの形式を指定しています。ASは必須ではないのですが、あったほうが後々扱いやすくなるのではないかなと思います。
ホントは経度をlongと書きたかったのですが予約語になっているので諦めます。logにしました。

読み込みデータをソート

データのソートをするときにはORDER BYを用います。

--データを月、日、時でソート
order_data = ORDER typhoon_input BY year,month,day,time ;

ORDER 対象 BY キーで並べ替えができます。このときのキーは複数でも扱うことができます。 今回は複数キーを同時に扱っています。

ソートしたデータをグループ化

ソートしたデータを各台風単位でグループにします。

-- ソートしたデータを名称ごとグループ化 typhoon_data_all = Group order_data BY num;

GROUP 対象 BY キーでグループ化できます。

各台風が最低気圧だった時の情報を抽出

グループ化された各データの中でも気圧が最低の時の情報を抽出します。

-- 一番気圧が低下した時のデータ、各台風のデータ数を記録 hpower_data = FOREACH typhoon_data_all {sorted = ORDER order_data BY power; mdata = LIMIT sorted 1; GENERATE group , FLATTEN(mdata.power) AS power ,FLATTEN(mdata.lat) AS lat , FLATTEN(mdata.log) AS log , COUNT(order_data),FLATTEN(mdata.year) AS year,FLATTEN(mdata.month) AS month,FLATTEN(mdata.day) AS day,FLATTEN(mdata.time) AS time;};

FOREACHを使って対象の各グループについて実行しています。 各グループについて気圧を基準にして並べ替えをしました。 そのデータの先頭にあるデータが気圧が最低になった時のデータになっているのでそのデータをFLATTENというグループ化の解除をしながら呼び出します。 また、各台風のデータ数を確認するためにCOUNTを用いています。

古いデータの削除

Pig内でコマンドが使えるらしいです。 rmを用いることも可能なのですが、フォルダ系の削除に対応したrmfを用いることで出力の古いデータの確実な削除をしています。

-- outputデータを削除 rmf data/output;

データの出力

データをファイルに出力します。 そのときにはSTOREを用いることで出力が可能になります。

-- 取得したデータをoutput ;STORE hpower_data INTO 'data/output' USING PigStorage(',');

そうするとこんな感じで出力されます。

f:id:poolbooyer:20190125141828p:plain
出力結果のサンプル

その他

今回の参考文献はこちら

pig.apache.org

次のエントリでこの出力結果を加工するお(すでにしてるんだけどエントリに書く時間がなさげ)

<ruby>pdfをcsvにしてやんよ

してやんよ

目次

  1. 背景
  2. 今回やること
  3. 使った環境
  4. コードの説明
  5. その他

1.背景

とある授業で気象庁のデータを元にして、解析を行おうと思っていたのですが、、
pdf形式でオープンデータしてくれたので元のデータをcsvにかこうしなきゃいけないことになっちゃった、、、って感じです。
多分ググればそれに近いのあるんだろうけどそれもつまんないなぁって忙しいのに手を付けたくなってしまったのでやってました。

2.今回やること

入力データとしてpdfを入れる 入力データ中の日付、時間、位置情報(緯度経度)、中心気圧を取り出します。 これらのデータしか欠損等がないデータがないので上のデータでなんとかします。 取り出したデータを元にCSVファイルに吐き出します。

3.使った環境

安定してMac で作りました。 今回はVSCode上で制作を行いました。 rubyとpdfを読み込むgemとしてpdf-readerを使いました。 このgemを選択した理由としては他のgemも試したのですが、これ以外のgemは同じファイルでも読み込むときによって結果が変動したりしたからです。

4.コードの説明

今回はgithub上でソースを管理しました。

github.com

この中で特に引っかかったとこ書きます。

他でやることできちゃったので時間ができたら書きます。

5. その他

現時点で何個か読み込めないやつがあります それはそのうち直します。