Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

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() もこれを基本にしています。

copy

    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))
となります。

copy

// 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

copy

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

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