Narrateur de Jeu

Peace cannot be kept by force. It can only be achieved by understanding.

2016 G.W. (11)

今日の午前中は関空のスタバでプログラム書いてます。

f:id:tomohiko37_i:20160501120133j:plain

今日も caramel macchiato で攻めます。アメリカンワッフルと一緒に。朝食兼昼食。

f:id:tomohiko37_i:20160501120235j:plain

CM のところが顔になっているのが可愛い。店員さんの遊び心が光ります。

tomohiko37-i.hatenablog.jp

さて、飛行機に乗った時に GPS データを取得し続けているわけですが、データがごっちゃになっているので少し整理したいと思っていました。先週くらいに Ruby で分割するコードを書いていたのですが、XML に簡単にアクセスできるのはいいとして扱いづらいというかわかりづらくてブチ切れそうになったので Java で書きました。

JavaXML にアクセスするコードは JAXB というライブラリを使います。これははっきり言ってパーフェクトなライブラリで、初めてこれを使った時はまさに革命が起きたような感じでした。作った人は天才だと素直に思いました。

KML より GPX を

GPS レシーバーからは .gpx という拡張子のファイルを抽出します。中身は XML なんですけど拡張子XML じゃない。それをツール.kml に変換して Google Earth に読ませているわけですが、当初は .kml をプログラムで読んで分割しようとしていました。ところが、この .kml が相当複雑というか cdata とかあって面倒なので *.gpx の方を眺めてみたらエラくシンプルなのでこっちを読めばいいや、と。

XML Schema を定義する

Java が他のプログラミング言語よりすぐれている点のひとつが「型の厳密さ」なのですが、何事においても Java は厳密です。時にはそれが煩わしく思うこともありますが、Ruby とか Python のようなスクリプト言語に触ると型を定義しないで変数を作るのはやはりおかしいと思いますし、動的型付けはやっぱり遅い原因になっていると思います。

その厳密さを体現したのが JAXB であり、XML を読むのに XML Schema を必要とします。

というわけで書いたさ。

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" 
        targetNamespace="http://www.topografix.com/GPX/1/0" 
        xmlns:tns="http://www.topografix.com/GPX/1/0" 
        elementFormDefault="qualified">
    
    <!-- GPX ファイル root タグの定義 -->
    <element name="gpx">
        <complexType>
            <sequence>
                 <element ref="tns:time"   minOccurs="1" maxOccurs="1" />
                 <element ref="tns:bounds" minOccurs="1" maxOccurs="1" />
                 <element ref="tns:trk"    minOccurs="1" maxOccurs="unbounded" />       
            </sequence>
            <attribute name="version" type="string" />
            <attribute name="creator" type="string" />
        </complexType>        
    </element>
    
    <!-- time タグの定義 -->
    <element name="time" type="string" />
    
    <!-- bounds タグの定義 -->
    <element name="bounds">
        <complexType>
            <attribute name="minlat" type="string" />
            <attribute name="minlon" type="string" />
            <attribute name="maxlat" type="string" />
            <attribute name="maxlon" type="string" />
        </complexType>
    </element>
    
    <!-- trk タグの定義 -->
    <element name="trk">
        <complexType>
            <sequence>
                <element ref="tns:name" minOccurs="1" maxOccurs="1" />
                <element ref="tns:desc" minOccurs="1" maxOccurs="1" />
                <element ref="tns:trkseg" minOccurs="1" maxOccurs="1" />
            </sequence>
        </complexType>
    </element>
    
    <!-- name タグの定義 -->
    <element name="name" type="string" />
    
    <!-- desc タグの定義 -->
    <element name="desc" type="string" />
    
    <!-- trkseg タグの定義 -->
    <element name="trkseg">
        <complexType>
            <sequence>
                <element ref="tns:trkpt" minOccurs="1" maxOccurs="unbounded" />
            </sequence>
        </complexType>
    </element>
    
    <!-- trkpt タグの定義 -->
    <element name="trkpt">
        <complexType>
            <sequence>
                <element ref="tns:ele"   minOccurs="1" maxOccurs="1" />
                <element ref="tns:time"  minOccurs="1" maxOccurs="1" />
                <element ref="tns:speed" minOccurs="1" maxOccurs="1" />
                <element ref="tns:name"  minOccurs="1" maxOccurs="1" />
            </sequence>
            <attribute name="lat" type="string" />
            <attribute name="lon" type="string" />
        </complexType>
    </element>
    
    <!-- ele タグの定義 -->
    <element name="ele" type="string" />
    
    <!-- speed タグの定義 -->
    <element name="speed" type="string" />
        
</schema>

XML Schema は初見だと難しく見えますが、最上位のタグを定義してひとつずつ子要素を定義して一番末端までたどり着けばいいので慣れれば比較的スムースに書けます。久々に書くと忘れていて調べながらになりますが。

xjc でコンパイル

JAXB の最大の魅力である XML をオブジェクトとして扱うためのコードを静的に吐くことができます。

$> xjc GpxDef.xsd

Java6 くらいから JDK に JAXB が含まれるようになりさらに便利になりました。なので JDK が入っていれば普通に xjc コマンドが実行できます。XML Schema を指定すれば Java のソースが出力されます。

読む・書く

ここまでくればあとは煮るなり焼くなり好きにします。

package jp.or.din.tomi.tool.gpxdatadivide;

import java.io.File;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import com.topografix.gpx._1._0.Gpx;
import com.topografix.gpx._1._0.Trk;

/**
 * GPS レシーバで記録したログデータをトラックごとに分割する処理.
 * 
 * @author tomohiko37_i
 *
 */
public class GpxDataDivide {

    /**
    * 起動.
    * @param args 起動パラメータ.
    */
    public static void main(final String[] args) {
        new GpxDataDivide(args);
    }
    
    /**
    * コンストラクタ.
    * @param args 起動パラメータ
    */
    public GpxDataDivide(final String[] args) {
        this.execute(args);
    }
    
    /**
    * 主処理.
    * 
    * trk ごとにファイルを分割する.
    * 
    * @param args 起動パラメータ
    */
    private void execute(final String[] args) {
        
        File file = new File("/Users/tomohiko/Documents/workspace/GpxDataDivide/data/2016-04-09 11:09:56 +0000.gpx");
        try {
            JAXBContext context = JAXBContext.newInstance("com.topografix.gpx._1._0");
            Unmarshaller unmarshaller = context.createUnmarshaller();
            Gpx gpx = (Gpx) unmarshaller.unmarshal(file);
            
            List<Trk> trkList = gpx.getTrk();
            for (int i = 0; i < trkList.size(); i++) {
                
                // 新しい gpx のインスタンスを作る
                Gpx tmpGpx = new Gpx();
                
                // とりあえず固定で設定する内容をセットする
                tmpGpx.setVersion(gpx.getVersion());
                tmpGpx.setCreator(gpx.getCreator());
                tmpGpx.setTime(gpx.getTime());
                tmpGpx.setBounds(gpx.getBounds());
                
                tmpGpx.getTrk().add(gpx.getTrk().get(i));
                
                JAXBContext writeContext = JAXBContext.newInstance("com.topografix.gpx._1._0");
                Marshaller marshaller = writeContext.createMarshaller();
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
                
                File outputFile = new File("/Users/tomohiko/Documents/workspace/GpxDataDivide/output/gpx_file_trk_" + i + ".gpx");
                marshaller.marshal(tmpGpx, outputFile);
            }
            
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

手軽ではないですが、やっていることに無理がないし、シンプルな設計に基づいた美しい手法です。それが JAXB の凄さ。JAXB を知ってからもう 10 年くらいになりますが、プライベートでも活躍してくれる柔軟さが大好きなのです。

とりあえず 30 分くらいでサクッと書いたコードですが目的を果たしてくれました。

分割した結果はまた今度。