Javascript でスマホの GPS を使ってみる
スマホでも、そんなに簡単にGPSを使ったアプリができるのかと驚いた顔をしている柴犬です。
近くの公園です。やっと二凛桜が咲きました。今年の3月末の寒さは格別でした。他の桜の木の蕾はまだ固いようです。
概要
今回は Javascript でアクセスした URL でGPSを実行するプログラムを書いてみました。
なんとか機能しているようですので記録することにします。
また、本ブログのミラーにした別のブログを作っているのですが、次のURLに置いてありますので、
適当に遊んでください。
https://sibainu.lsv.jp/memo/learninghtml/test.html
辞書代わりに使っています。
Android など他のプログラミング言語から Javascript を扱う時、思考回路を整理するときに読み直したりしています。
基本の関数
MDN web doc のHPのURLの関数を使ってみました。
https://developer.mozilla.org/ja/docs/Web/API/Geolocation_API
run1()、un2() もこれを基本にしています。
function geoFindMe() { const status = document.querySelector("#status"); const mapLink = document.querySelector("#map-link"); mapLink.href = ""; mapLink.textContent = ""; function success(position) { const latitude = position.coords.latitude; const longitude = position.coords.longitude; status.textContent = ""; mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`; mapLink.textContent = `緯度: ${latitude}°、経度: ${longitude}°`; } function error() { status.textContent = "Unable to retrieve your location"; } const options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }; if (!navigator.geolocation) { status.textContent = "このブラウザーは位置情報に対応していません"; } else { status.textContent = "位置情報を取得中…"; navigator.geolocation.getCurrentPosition(success, error, options); } }
緯度経度を直交座標にする変換関数
GRS80(測地系)により
赤道半径は、6378137mとされ
扁平率は、1 / 298.257222101 とされているようですので次のようにしました。
国土地理院のHPの次のURLの変換式を使いました。
https://vldb.gsi.go.jp/sokuchi/surveycalc/surveycalc/algorithm/trans/trans_alg.html
卯酉線曲率半径は、西修二郎氏のHPの次のURLに詳しい解説書がありますので、その式を使っています。
http://nishishu.net/geod/introgeod.pdf
N=a*a/Math.sqrt(a*a*cos(lati)*cos(lati)+b*b*sin(lati)*sin(lati))
なので
cos(lati)*cos(lati)=1-sin(lati)*sin(lati)
(a*a-b*b)/a*a=e*e
を使うと
N=a/Math.sqrt(1-e*e*sin(lati)*sin(lati))
となります。
// ido: 緯度 keido: 経度 hyoko: 楕円体からの高さ function xyzchange(ido, keido, hyoko) { // 緯度 [ラジアン] const lati = ido * Math.PI / 180.0; // 経度 [ラジアン] const longi = keido * Math.PI / 180.0; // 楕円体高 altitude [m] const h = hyoko; // 赤道半径 GRS80(測地系)による major axis [m] const a = 6378137.0; // 扁平率 GRS80(測地系)による const f = 1.0 / 298.257222101; // 極半径 minor axis const b = a * (1.0 - f); // 離心率 Eccentricity // Math.sqrt(a * a - b * b) / a に b を代入して整理する const e = Math.sqrt(2.0 * f - f * f); // 卯酉線曲率半径 Prime vertical radius of curvature const sinlati = Math.sin(lati); const e2 = (e * e) * (sinlati * sinlati); const W = Math.sqrt(1.0 - e2); const N = a / W; // 地心直交座標系 const X = (N + h ) * Math.cos(lati) * Math.cos(longi); const Y = (N + h ) * Math.cos(lati) * Math.sin(longi); const Z = (N * (1.0 - e2) + h ) * Math.sin(lati); // 配列で返します return [X, Y, Z]; }
GPS を使った Javascript
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <script type="text/javascript"> function geoFindMe() { const status = document.querySelector("#status"); const mapLink = document.querySelector("#map-link"); mapLink.href = ""; mapLink.textContent = ""; function success(position) { const latitude = position.coords.latitude; const longitude = position.coords.longitude; status.textContent = ""; mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`; mapLink.textContent = `緯度: ${latitude}°、経度: ${longitude}°`; } function error() { status.textContent = "Unable to retrieve your location"; } const options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }; if (!navigator.geolocation) { status.textContent = "このブラウザーは位置情報に対応していません"; } else { status.textContent = "位置情報を取得中…"; navigator.geolocation.getCurrentPosition(success, error, options); } } function run1() { const status = document.querySelector("#status"); const mapLink = document.querySelector("#map-link"); const point1_lati = document.querySelector("#point1_lati"); const point1_longi = document.querySelector("#point1_longi"); const point1_X = document.querySelector("#point1_X"); const point1_Y = document.querySelector("#point1_Y"); const point1_Z = document.querySelector("#point1_Z"); mapLink.href = ""; mapLink.textContent = ""; function success(position) { const latitude = position.coords.latitude; const longitude = position.coords.longitude; status.textContent = ""; point1_lati.innerText = ""; point1_longi.innerText = ""; point1_X.innerText = ""; point1_Y.innerText = ""; point1_Z.innerText = ""; mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`; mapLink.textContent = `緯度: ${latitude}°、経度: ${longitude}°`; point1_lati.innerText = latitude.toFixed(8); point1_longi.innerText = longitude.toFixed(8); const posi = xyzchange(latitude, longitude, 8); point1_X.innerText = posi[0].toFixed(8); point1_Y.innerText = posi[1].toFixed(8); point1_Z.innerText = posi[2].toFixed(8); } function error() { status.textContent = "Unable to retrieve your location"; } const options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }; if (!navigator.geolocation) { status.textContent = "このブラウザーは位置情報に対応していません"; } else { status.textContent = "位置情報を取得中…"; navigator.geolocation.getCurrentPosition(success, error, options); } } function run2() { const status = document.querySelector("#status"); const mapLink = document.querySelector("#map-link"); const point2_lati = document.querySelector("#point2_lati"); const point2_longi = document.querySelector("#point2_longi"); const point2_X = document.querySelector("#point2_X"); const point2_Y = document.querySelector("#point2_Y"); const point2_Z = document.querySelector("#point2_Z"); mapLink.href = ""; mapLink.textContent = ""; function success(position) { const latitude = position.coords.latitude; const longitude = position.coords.longitude; status.textContent = ""; point2_lati.innerText = ""; point2_longi.innerText = ""; point2_X.innerText = ""; point2_Y.innerText = ""; point2_Z.innerText = ""; mapLink.href = `https://www.openstreetmap.org/#map=18/${latitude}/${longitude}`; mapLink.textContent = `緯度: ${latitude}°、経度: ${longitude}°`; point2_lati.innerText = latitude.toFixed(8); point2_longi.innerText = longitude.toFixed(8); const posi = xyzchange(latitude, longitude, 8); point2_X.innerText = posi[0].toFixed(8); point2_Y.innerText = posi[1].toFixed(8); point2_Z.innerText = posi[2].toFixed(8); } function error() { status.textContent = "Unable to retrieve your location"; } const options = { enableHighAccuracy: true, timeout: 5000, maximumAge: 0, }; if (!navigator.geolocation) { status.textContent = "このブラウザーは位置情報に対応していません"; } else { status.textContent = "位置情報を取得中…"; navigator.geolocation.getCurrentPosition(success, error, options); } } function run3() { const point1_X = document.querySelector("#point1_X"); const point1_Y = document.querySelector("#point1_Y"); const point1_Z = document.querySelector("#point1_Z"); const point2_X = document.querySelector("#point2_X"); const point2_Y = document.querySelector("#point2_Y"); const point2_Z = document.querySelector("#point2_Z"); const x1 = point1_X.value; const y1 = point1_Y.value; const z1 = point1_Z.value; const x2 = point2_X.value; const y2 = point2_Y.value; const z2 = point2_Z.value; const x = x2 - x1; const y = y2 - y1; const z = z2 - z1; const span_L = document.querySelector("#span_L"); const span = Math.sqrt(x * x + y * y + z * z); span_L.innerText = span.toFixed(3); } // ido: 緯度 keido: 経度 hyoko: 楕円体からの高さ function xyzchange(ido, keido, hyoko) { // 緯度 [ラジアン] const lati = ido * Math.PI / 180.0; // 経度 [ラジアン] const longi = keido * Math.PI / 180.0; // 楕円体高 altitude [m] const h = hyoko; // 赤道半径 GRS80(測地系)による major axis [m] const a = 6378137.0; // 扁平率 GRS80(測地系)による const f = 1.0 / 298.257222101; // 極半径 minor axis const b = a * (1.0 - f); // 離心率 Eccentricity // Math.sqrt(a * a - b * b) / a に b を代入して整理する const e = Math.sqrt(2.0 * f - f * f); // 卯酉線曲率半径 Prime vertical radius of curvature const sinlati = Math.sin(lati); const e2 = (e * e) * (sinlati * sinlati); const W = Math.sqrt(1.0 - e2); const N = a / W; // 地心直交座標系 const X = (N + h ) * Math.cos(lati) * Math.cos(longi); const Y = (N + h ) * Math.cos(lati) * Math.sin(longi); const Z = (N * (1.0 - e2) + h ) * Math.sin(lati); // 配列で返します return [X, Y, Z]; } </script> </head> <body> <button id="find-me" onClick="geoFindMe();">現在の位置を表示</button><br> <p id="status"></p><br> <a id="map-link" target="_blank"></a><br><br> <button id="get-point1" onClick="run1();">ポイント1</button><br><br> <textarea id="point1_lati" rows="1" cols="1" style="width:300px;" readonly>point1_lati</textarea><br> <textarea id="point1_longi" rows="1" cols="1" style="width:300px;" readonly>point1_longi</textarea><br> <textarea id="point1_X" rows="1" cols="1" style="width:300px;" readonly>point1_X</textarea><br> <textarea id="point1_Y" rows="1" cols="1" style="width:300px;" readonly>point1_Y</textarea><br> <textarea id="point1_Z" rows="1" cols="1" style="width:300px;" readonly>point1_Z</textarea><br><br> <button id="get-point2" onClick="run2();">ポイント2</button><br><br> <textarea id="point2_lati" rows="1" cols="1" style="width:300px;" readonly>point2_lati</textarea><br> <textarea id="point2_longi" rows="1" cols="1" style="width:300px;" readonly>point2_longi</textarea><br> <textarea id="point2_X" rows="1" cols="1" style="width:300px;" readonly>point2_X</textarea><br> <textarea id="point2_Y" rows="1" cols="1" style="width:300px;" readonly>point2_Y</textarea><br> <textarea id="point2_Z" rows="1" cols="1" style="width:300px;" readonly>point2_Z</textarea><br><br> <button onclick="run3();">距離測定</button><br><br> <textarea id="span_L" rows="1" cols="1" style="width:300px;" readonly>span_L</textarea><br> </body> </html>
スマホで実行してみた
URL を開いたときは、左の画面です。同じ場所に留まって「ポイント1」タップして実行して、「ポイント2」を続いてタップしてみました。「距離測定」をタップして「ポイント1」と「ポイント2」の距離を算出してみました。
同じ場所にも関わらず6mほど距離が離れていると誤差が出ています。
少し移動して、同じ場所に留まって上と同じことをしてみました。今度は、3.5m離れていると算出されました。現在の位置を表示のリンク先を開いてみたのが、右の画面です。
画面中央が、その緯度経度で示されていると考えれば「まあこんなところか」という表示です。
移動せずに、繰り返してみました。びっくりです。50m離れていると計算されました。
現在の位置を表示のリンク先を開いてみたのが、右の画面です。全く同じです。
私の方法では、大体の位置を知るにはいいけれども、2点間の距離を測るのには適していないようです。
この件はここまでとします。