2017年12月17日日曜日

OpenLayers 3 を使ってみよう(番外5:JavaScript で KML データと GPX データを読み込む方法)

OpenLayers 3 を使ってみよう(その0:はじめに:地理院地図を表示)に目次がある。

  前回(その22:Highcharts でルートの標高グラフの改良)は,Highcharts を使った標高図の表示の改良について書いた。 そこでは,JavaScript で KML 形式のデータファイルから直接データを読み込むはやめて,OpenLayers として読み込んだ KML データの vector データから標高図のデータを取得した。 しかし,JavaScript で KML や GPX などの XML 形式のデータを読み込む方法を調べたので,せっかくなのでまとめておこうと思う。

 XML データについては,https://www.w3schools.com/XML Tutorialを参考にした。 特に XML の各タグの属性(attribute)については XML Attributes が参考になった。

 KML フォーマットについては,Google マップでの KML レイヤのガイド に詳細な記述があるが,OpenLayers 3 を使ってみよう(番外2:KML ファイルのフォーマットについてにも書いたので,そちらを見て欲しい。
 以下に経路用の KML の例を示そう。 ここで,経路点のデータは「<Document>〜</Document>」の中の「<Folder>〜</Folder>」の中の「<Placemark>〜</Placemark>」の中の「<LineString>〜</LineString>」の中の「<coordinates>〜</coordinates>」の中に記述されている。

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<description>
    <![CDATA[<p>菊水山~摩耶山2014</p><p><a href="https://xxx.yyy.zzz">web site</a></p>]]>
</description>
<Style id="Line_1">
    <LineStyle>
        <color>ff0000ff</color>
        <width>3</width>
    </LineStyle>
</Style>
<Folder>
    <name>Tracks</name>
    <Placemark>
        <name>Track 1</name>
        <description><![CDATA[Original tracking filename : LOG00492.nma<br>From: 34.69,135.14<br>  To: 34.71,135.21]></description>
        <styleUrl>#Line_1</styleUrl>
        <LineString>
            <extrude>0</extrude>
            <tessellate>1</tessellate>
            <altitudeMode>clampToGround</altitudeMode>
            <coordinates>
                135.145456666667,34.69276,182.9
                135.145503333333,34.6927733333333,181.6
                135.14571,34.6927833333333,168.8
                135.1459,34.6927066666667,160.3
                135.146138333333,34.69277,158.2
                .........................
            </coordinates>
        </LineString>
    </Placemark>
</Folder>
</Document>
</kml>

 一方で GPX フォーマットだが,GPX フォーマットについては,GPX 1.1 Schema Documentation にGPC 1.1 スキーマの記述があるが,これがまたわかりにくい。 しかし,データ自体は XML 形式なので,以下の具体例を見ると大体わかると思う。
 以下にに経路用の GPX ファイルの例を示そう。 ここで,経路点のデータは「<gpx>〜</gpx>」の中の「<trk>〜</trk>」の中の「<trkseg>〜</trkseg>」の中の「<trkpt>〜</trkpt>」の中に記述されている。 KML 形式よりもわかりにくい点として,経路点の経度・緯度が「<trkpt>」の「Attributes」として記載してある点がある。 XML 形式の場合,「<XXX>」と「</XXX>」の間に XXX に関する値を書くことが多い。 この場合,「<XXX>」と「</XXX>」の間の値のことを「要素(Element)」と呼ぶ。 しかし,「<trkpt XXX="yyy">」の XXX="yyy" は「属性(Attribute)」と呼ばれる。
 KML 形式では全て「要素(Element)」として記載されるので,この点が読み出す時に注意点となる。

<?xml version="1.0" encoding="UTF-8"?>
<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1">
<metadata>
    <name>六甲全山縦走 track data file</name>
    <link href="https://xxx.yyy.zzz">
        <text>Matsup Motorcycle</text>
    </link>
</metadata>
<trk>
    <name>Track 1</name>
    <desc><![CDATA[Original tracking filename : LOG00190.nma (cyberJapan Elevation)]]></desc>
    <trkseg>
        <trkpt lat="34.633815" lon="135.082666666667">
            <ele>4</ele>
            <time>2017-12-01T20:38:00Z</time>
        </trkpt>
        <trkpt lat="34.6338116666667" lon="135.082668333333">
            <ele>4</ele>
            <time>2017-12-01T20:38:01Z</time>
        </trkpt>
        <trkpt lat="34.63385" lon="135.082846666667">
            <ele>4.4</ele>
            <time>2017-12-01T20:38:22Z</time>
        </trkpt>
        ................................
    </trkseg>
</trk>
</gpx>


 これら KML や GPX などの XML 形式のデータを JavaScript で読み込むには以下のようにすればよい。
    let req = new XMLHttpRequest(); // HTTPでファイルを読み込むためのXMLHttpRrequestオブジェクトを生成
    req.open('GET', data_file_name, true);
    req.setRequestHeader('Accept', 'application/vnd.google-earth.kml+xml'); // for KML
//    req.setRequestHeader('Accept', 'application/x-www-form-urlencoded');  // for GPX
    try {
        req.overrideMimeType('text/xml'); // unsupported by IE
    } catch (e) { }
// 標高データの読み込みとグラフの設定
    req.onreadystatechange = function () {
        if (req.readyState !== 4) return;
        if (req.status === 200) {
            alt_array = getAltArray(req.responseXML); // 準備ができたら読み込む
        }
//        (alt_array に対する処理を記載する)
    }; // req.onreadystatechange = function...
    req.send(null);
 ここで「getAltArray(....)」はこの後で定義する関数である。
今,XML 形式のデータを読み込みたいので,「XMLHttpRequest」で変数「req」を定義している。 その「req」に対して,open,setRequestHeader,の関数を実行したのちに,onreadystatechange として「req」の status が 200 になっていれば読み込みを実行している。 その後,読み込んだファイルに対してなんらかの処理をしたのちに,「req.send(null)」として(たぶん)メモリの開放をしている(と思ってる)。 req.setRequestHeader が KML と GPX で違っているのは形式違うからである(と思っている)。

 「getAltArray(....)」は KML と GPX でデータの構造が違うので,異なるルーチンとしなければいけない。 以下に具体的に見ていこう。

 まずは KML ファイルを読み込んで,XML 形式のデータから情報を読み取る関数である。 ここでは「Folder」の要素を取り出し,その中から「Placemark」を取り出し,さらに「LineString」を取り出し,最後に「coordinates」から要素を取り出している。 「coordinates」の中の要素は,複数の Node(ここでは複数の行かな?)を持ち,各 Node は「135.145456666667,34.69276,182.9」のようなデータとなっている。そこで,各 Node の値を「.childNodes[m].nodeValue」手続きを使って取り出している。
 ここでは便宜のために,一度一個の大きなテキスト変数に結合してから,後で各行にばらした後,それぞれのデータを取り出している。

// KML データの読み込み
function getAltArray(xml) {
    let inline = '';
    let inlines = [];
    let coords = [];
    let el = xml.getElementsByTagName('Folder'); // 入れ子構造になっている
    for (i=0; i < el.length; i++) {
        let el1 = el[i].getElementsByTagName('Placemark');
        for (j=0; j < el1.length; j++) {
            let el2 = el1[j].getElementsByTagName('LineString');
            for (k=0; k < el2.length; k++) {
                let el3 = el2[k].getElementsByTagName('coordinates'); // 値を読み取る部分
                for (l=0; l < el3.length; l++) {
                    for (m=0; m < el3[l].childNodes.length; m++) {
                        inline = inline + el3[l].childNodes[m].nodeValue; // 一度長い一つの文にする
                    }
                }
            }
        }
    }
    inlines = inline.split(/[\s\n]+/); // 改行で分割
    for (i = 0; i < inlines.length; i++) {
        let ll = inlines[i].split(','); // コンマで分割
//  ll[0]:経度(longitude),ll[1]:緯度(latitude),ll[2]:高度(altitude)
//      ll に対する処理を記載
    }
}

 次に GPX データの場合を見てみよう。
以下が XML 形式のデータから情報を読み取る関数である。 まずは「trk」の要素を取り出し,その中から「trkseg」を取り出し,さらに「trkpt」を取り出している。 そこで,「trkpt」の属性である緯度と経度の情報を「.attributes.getNamedItem("lat").nodeValue」のように取り出している。そして,さらに「trkpt」の要素である「ele」の内容を「.childNodes[m].nodeValue」を使って取り出している。
 KML 形式の時と同じように,便宜のために一度一個の大きなテキスト変数に結合してから,後で各行にばらした後,それぞれのデータを取り出している。

// GPX データの読み込み (from xxx.gpx)
function getAltArray(xml) {
    let inline = '';
    let inlines = [];
    let el = xml.getElementsByTagName('trk'); // 入れ子構造になっている
    for (i=0; i < el.length; i++) {
        let el1 = el[i].getElementsByTagName('trkseg');
        for (j=0; j < el1.length; j++) {
            let el2 = el1[j].getElementsByTagName('trkpt');
            for (k=0; k < el2.length; k++) {
                // ... の lat と lon を取り出す。
                inline = inline + k + "," + el2[k].attributes.getNamedItem("lat").nodeValue + ","
                                + el2[k].attributes.getNamedItem("lon").nodeValue + ",";
                let el3 = el2[k].getElementsByTagName('ele'); // 値を読み取る部分
                for (l=0; l < el3.length; l++) {
                    for (m=0; m < el3[l].childNodes.length; m++) {
                        inline = inline + el3[l].childNodes[m].nodeValue+"\n"; // 一度長い一つの文にする
                    }
                }
            }
        }
    }
    // inline の各行は,"k, lat, lon, ele" となっている。

    inlines = inline.split(/[\s\n]+/); // 改行で分割
    for (i = 0; i < inlines.length; i++) {
        let ll = inlines[i].split(','); // コンマで分割
//  ll[1]:緯度(latitude),ll[2]:経度(longitude),ll[3]:高度(altitude)
//      ll に対する処理を記載
    }
}

 参考にしてみて欲しい。

0 件のコメント: