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点間の距離を測るのには適していないようです。

この件はここまでとします。