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