前回(その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 件のコメント:
コメントを投稿