ルモーリン
ホーム更新サービス雑談ランドナーコースガイド鉄ゲタ自転車Linuxリンク連絡先

お手軽XML操作

背景

サイクリングでGPSロガーが記録した走行記録をgpxとして入手できる。 これをプログラムで加工してからGoogleマップにインポートしたい。 gpxはXML形式なのでPerlで操作しよう。

ライブラリ

XML::TreePPを使います。
XML::TreePP - Pure Perl implementation for parsing/writing XML documents - metacpan.org
これで入力の際は1行目のxmlタグを除く行をハッシュリファレンスにするし、出力の際は自動でxmlタグでラップします。

読み込み

my $tpp = XML::TreePP->new(utf8_flag => 1);
my $xml = $tpp->parsefile($query->tmpFileName($gpx_fh));
1行目でTreePPオブジェクトを作る際にテキストにutf8フラグを追加して、gpx内の日本語テキストを適切に扱えるようにします。 2行目でCGIにアップロードしたファイルをTreePPが読み込んでハッシュリファレンスにします。

不要なタグを削除

gpxファイルはタグtrkptが連続していて、不要なタグを削除する処理です。 TreePPの読み込みは【デフォルトの設定では】、あるタグが1個だけの場合、ハッシュの値にそのタグのリファレンスが入ります。 同じタグが複数ある場合は代わりに配列のリファレンスが入り、配列の要素にタグのリファレンスが入ります。 タグの属性名もキーに入っていますが、属性名に「-」を前置してあるので下位のタグと区別できます。 配列をforループで回す中で要素を削除すると面倒なので、削除判定したタグにremove属性を付けておき、改めてループを回して削除します。 この辺り、任意のフラグや設定を追加できるのがハッシュリファレンスの便利な点です。
sub simple_trk {
        my ($xml) = @_;
        my $trkpt = $xml->{gpx}->{trk}->{trkseg}->{trkpt};

        # 座標を要約する
        # 経度、緯度それぞれを簡易換算
        # 次の座標が至近距離であれば破棄

        my ($a_x, $a_y);
        my ($b_x, $b_y);
        for (@{$trkpt}) {
                my $b_x = $_->{"-lat"};
                my $b_y = $_->{"-lon"};
                if (defined $a_x) {
                        my $dist_x = ($a_x - $b_x) * 100000 / 13 * 10;
                        my $dist_y = ($a_y - $b_y) * 100000 / 9 * 10;
                        my $dist = sqrt($dist_x ** 2 + $dist_y ** 2);
                        $_->{"-remove"} = 1 if $dist < 1;
                }
        }
        for (my $i = 0; $i < @{$trkpt}; $i++) {
                if (exists ${$trkpt}[$i]->{"-remove"}) {
                        splice @{$trkpt}, $i, 1;
                        $i--;
                }
        }
}

タグの分割、複製

Googleマップはタグtrkptが400個を超えると節点を編集できなくなるのでtrkptを400個ずつ分割し、タグtrkを複数にします。 タグの階層はtrk→trkseg→trkptです。 trkにタグnameがあり、これに名称があるので2個目以降にもコピーします。 前述の通り、同じタグが複数並ぶ場合は配列のリファレンスを入れてタグのリファレンスを要素に入れます。 trkの末尾と次のtrkの先頭が同じ場所になるようtrkptをラップさせています。 元のtrkが持つ別のタグは要がないので無視します。
sub gpx_split {
        my ($xml) = @_;

        my $trkpt = $xml->{gpx}->{trk}->{trkseg}->{trkpt};
        my $trk_name = $xml->{gpx}->{trk}->{name};
        my @trk;
        $xml->{gpx}->{trk} = \@trk;

        for (my $i = 0; $i < @{$trkpt} - 1;) {
                my $j = @{$trkpt} <= $i + 399 ? @{$trkpt} - 1 : $i + 399;
                my @trkpt = @{$trkpt}[$i .. $j];
                push @trk, {
                        name => $trk_name,
                        trkseg => {
                                trkpt => \@trkpt,
                        },
                };

                $i = $j;
        }
}

書き出し

ファイルの名前とハッシュリファレンスを渡せば書き出せます。
$tpp->writefile($gpx, $xml);