位置情報の取得

ここからの説明は、実際に位置情報取得センサ付のデバイスでのみ動かせる。 まず、HTTPS配付可能なディレクトリにて、HTML 文書ファイルと JavaScript プログラムを作成し、 それをセンサつきモバイルデバイスで確認するという手順が必要である。

https://www.w3.org/TR/geolocation-API/

GPSはアメリカによる測位衛星システムの固有名詞であり、 近年では衛星による位置を取得する一般名詞としてGNSS (Global Navigation Satellite System) を使うのが推奨されている。

Geolocation API

HTML5 Geolocation APIでは次の3つのインターフェースが用意されている。

getCurrentPosition(successCallback[, errorCallback[, option]])

センサからの位置情報を1度だけ取得試行し、成功したら successCallback を、失敗したら errorCallback を呼ぶ。 成功時の関数には Potision オブジェクトが、 失敗時の関数には PotisionError オブジェクトが渡される。

watchPosition(successCallback[, errorCallback[, option]])

位置情報取得を繰り返す。引数は getCurrentPosition() と同様。

返却値としてIDが返るので、この値を保存しておき、位置情報取得 を止めたいときに次の clearWatch() に渡す。

clearWatch(watchId)

指定したIdの追跡を止める。止めない限り追跡は続き、 成功・失敗によって指定した関数が呼ばれるため、 止める処理を盛り込む必要がある。

情報取得を試行する2つの関数は、いずれも第1、第2引数にそれぞれ 取得成功時に呼ぶ関数、失敗時に呼ぶ関数を指定する。このように 将来何かのタイミングで呼び返してほしい関数のことをコールバック関数 という。

第3引数に指定するのは挙動を制御するオプションで、 以下のプロパティが設定できる。

enableHighAccuracy 高精度の測定をするかどうか。false はしない。false 以外であればする。デフォルトは false で大雑把な値が分かればよいときにはこちらにする。 現在位置を即時に取得したいときには true を指定する。 trueの場合デバイスの電気消費が激しくなるので注意する。
timeout 位置情報が取得できるまでの最大待ち時間(ミリ秒)。
maximumAge 以前に取得してキャッシュしてある位置情報の有効期限(ミリ秒)。 0(デフォルト)に指定するとキャッシュを利用せずかならず測定を試みる。

位置情報取得手順

たとえば、1回のみの情報取得を試みるコードは以下のようになる。

// ここまでに変数 mymap にLeaflet地図オブジェクトが入っているとする。
var locmarker = L.marker(mymap.getCenter()).addTo(mymap);
// マーカを作り、取得に関する情報をポップアップ表示する用途に用いる。
locmarker.bindPopup("取得中...").openPopup();

function tryGetLOC() {	// 取得を開始する処理を行なう関数の定義
    navigator.geolocation.getCurrentPosition(
	onSuccess, onError,{	// 下で定義する2つの関数をコールバック指定
	    maximumAge: 0, timeout: 2000, enableHighAccuracy: true
	});
}
function onSuccess(pos) { // 成功時のコールバック。関数名は何でもよい。
    // pos.coords に位置情報が入る。LeafletのLatLngに変換する。
    var latlng = L.latLng([pos.coords.latitude, pos.coords.longitude]);
    mymap.panTo(latlng);		// 地図の中心を取得した位置に
    locmarker.setPopupContent(		// ポップアップ表示を
	"ここは "+latlng+"です."		// 更新後の緯度経度に変え、
    ).openPopup().setLatLng(latlng);	// ポップアップし、ポイントも変更する
}
var nTrial = 10;			// 最大試行回数を決めておく
function onError(err) {			// 失敗時のコールバック
    restN = "あと"+(--nTrial)+"回試行します。";
    locmarker.setPopupContent("取得失敗:"+restN).openPopup();
    if (nTrial > 0) {			// 残り回数があれば
	tryGetLOC();			// 再度取得を試行する
    }
}
tryGetLOC();

実例(locget-timer.html)

成功時のコールバックには Position オブジェクトが渡る。 Position は2つのプロパティを持つ。

coords位置に関する情報
timestamp取得時刻

coords プロパティはさらに以下のプロパティを持つ。

latitude緯度(10進)
longitude経度(10進)
altitudeWGS84高度(メートル)(取れなければnull)
accuracy緯度経度の測定誤差(精度)
altitudeAccuracy高度の測定誤差(精度)
heading移動方向(北から反時計回り360度まで)
speed水平移動速度(m/s)

モバイルデバイスを用いての実験はこのURLから:
https://www.yatex.org/lect/gnss/loc-get.html https://www.yatex.org/lect/gnss/loc-get.html

https://www.yatex.org/lect/gnss/loc-watch.html https://www.yatex.org/lect/gnss/loc-watch.html

機種によって異なるが、次のようなことが気になる場合がある。

また、watchPosition() の場合は捕捉に成功するまで続くが逆に

という問題があり、デバイスによっては電力消費が激しくなることもある。

作成練習

loc-chase.html(実例)

loc-chase.html

<!DOCTYPE html>
<html lang="ja">
<head>
<title>GNSS宝探し</title>
<link rel="stylesheet" type="text/css" href="../src/leaflet.css">
<script type="text/javascript" src="../src/leaflet.js"></script>
<script type="text/javascript" src="loc-chase.js"></script>
<style type="text/css">
<!--
div#locationmap {width: 90vw; height: 75vw; margin: auto;}
-->
</style>
</head>

<body>
<h1>たからさがし<span id="title"></span></h1>
<p>
<button id="start" type="button">START</button>
<button id="stop" type="button">STOP</button>
<a href="../src/loc-chase.js">src</a>
</p>
<div id="locationmap"></div>


</body>
</html>

loc-chase.js

document.addEventListener("DOMContentLoaded", () => {
    var mymap = L.map("locationmap").setView([38.891, 139.824], 16);
    L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png', {
	attribution:
	'&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> \
		contributors'
    }).addTo(mymap);

    var locmarker = L.marker(mymap.getCenter()).addTo(mymap);
    locmarker.bindPopup("STARTおしてね").openPopup();

    var nTrial = 100
    var watchId = null;		// 最初はnullにしておく
    function stopLOC() {	// watchが動いていたら止めてnullにする
	console.log("watchId="+watchId);
	if (watchId != null) {	// nullかどうかで比較しないとだめ(初期値0)
	    navigator.geolocation.clearWatch(watchId);
	    document.getElementById("title").textContent = "stop";
	    locmarker.setPopupContent("停めました");
	}
	watchId = null;
    }
    function tryWatchLOC() {
	stopLOC();		// 二重で動かないように注意する
	watchId = navigator.geolocation.watchPosition(
	    onSuccess, onError,{
		maximumAge: 0, timeout: 3000, enableHighAccuracy: true});
	document.getElementById("title").textContent = "START!!";
    }
    function onSuccess(pos) {	// 衛星信号が取れたらここに来る
	var latlng = L.latLng([pos.coords.latitude, pos.coords.longitude]);
	mymap.panTo(latlng);// 地図の中心をそこにする
	console.log(latlng);
	// ★★★★★ ここから
	locmarker.setLatLng(latlng).setPopupContent(
	    "ここは lat="+latlng.lat+", lng="+latlng.lng+" です."
	).openPopup();
	// ★★★★★ ここまでの latlng.lat と latlng.lng を
	// if文などで判定して、特定の場所に近づいたら「GOAL!」と
	// 表示するように変えてみよ。
    }
    function onError(err) {
	restN = "あと"+(--nTrial)+"回試行します。";
	locmarker.setPopupContent("捕捉失敗:"+restN).openPopup();
	if (nTrial <= 0) {
	    navigator.geolocation.clearWatch(watchId);
	}
    }
    // STARTボタンに開始を仕込む
    document.getElementById("start").addEventListener("click", tryWatchLOC);
    // STOPボタンに停止を仕込む
    document.getElementById("stop").addEventListener("click", stopLOC);
}, false);

★★★★★★ここから〜ここまでの部分に、 現在位置に応じたコードを書き足すことにより 様々な処理を行うプログラムとなる。

Leaflet Geolocation

Geolocation APIをLeafet.jsのスキーマで使うメソッドが用意されている。 Leaflet地図と連動させるならこちらのほうが使いやすい。LeafletのMap オブジェクトに備わるlocate, stopLocateメソッドで制御する。 Geolocation APIでは取得開始に getCurrentPosition, watchPosition の2つのメソッドが用意されているが、 Leafletでは locate メソッドに与えるプロパティでどちらを 使うかを決める。また、Geolocation APIではIDを記録しておき、 clearWatchに渡す必要があるが、Leafletでは Mapオブジェクトに内包されるのでユーザプログラムでは管理しなくてよい。

以下、MapオブジェクトのGeolocation methodについての説明である。

locate(LocateOption)

位置情報取得を開始する。取得に関連する情報は全て LocateOption のプロパティで設定する。

プロパティデフォルト値意味
watchfalse trueならwatchPositionを呼び、 この場合はあとでmap.stopLocate()を呼んで停める。 デフォルトはfalsegetCurrentPosition を呼ぶ。
setViewfalse 位置取得できた場所と精度に応じて地図表示を移動する。
maxZoomInfinity setViewのときの最大Zoom値。
timeout10000 locationerrorイベントを出すまでの最大待ち時間(ミリ秒)。
maximumAge0 Geolocation APIのものと同じ。
enableHighAccuracy0 Geolocation APIのものと同じ。
stopLocate()

map.locate({watch:true}) で開始した位置情報取得を停止する。

位置取得できたときの処理と、できなかったときの処理はそれぞれ locationfoundイベント、 locationerrorイベントを補足するハンドラで行う。

LeafletのAPIを使って、現在地を地図に表示するコードは以下のようになる。

// ここまでに変数 mymap にLeaflet地図オブジェクトが入っているとする。
var locmarker = L.marker(mymap.getCenter()).addTo(mymap);
locmarker.bindPopup("取得中...").openPopup();
// マーカを作り、取得に関する情報をポップアップ表示する用途に用いる。

function tryLocate() {// 取得を開始する処理を行なう関数の定義
	mymap.locate({
	    watch:		false,
	    setView:	true,
	    timeout:	4000,
	    maximumAge:	8000,
	    enableHighAccuracy:	true
	});
}
function onSuccess(pos) {	// 成功時のコールバック。関数名は何でもよい。
	// pos.coords に位置情報が入る。LeafletのLatLngに変換する。
	locmarker.setPopupContent(	// ポップアップ表示を
	    "ここは "+pos.latlng+"です."	// 更新後の緯度経度に変え、
	).openPopup().setLatLng(pos.latlng);// ポップアップし、ポイントも変更する
}

var nTrial = 10;// 最大試行回数を決めておく
function onError(err) {		// 失敗時のコールバック
	let restN = "あと"+(--nTrial)+"回試行します。";
	locmarker.setPopupContent("取得失敗:"+restN).openPopup();
	if (nTrial > 0) {		// 残り回数があれば
	    tmId = setTimeout(tryLocate, 2000); // 2秒後に再試行
	    // tryLocate();		// 再度取得を試行する
	}
}
mymap.on('locationfound', onSuccess)
mymap.on('locationerror', onError)
tryLocate();

実例(leaflet-locate.html), leaflet-locate.js(source)

Leafletのmap.locateを使うメリットとしては、latlngを そのままLeafletオブジェクトに指定できる点と、watchする場合の オブジェクトIDを管理しなくてよい点が挙げられる。