はじめに(読み飛ばしてね)
この記事はWebページ上で動く、地図ライブラリLeafetで端末の現在地を取得し、地図上に描画するというものです。
そもそも、この機能は私が極秘?に開発している「河川カメラViewer」、国土交通省が河川に設置している河川カメラを見られるようにするアプリに搭載されています。
たまに私は淀川河川敷に走りに行っていて、そこで10分ごとに更新される河川カメラに映ることをちょっと途中目標にして走っているのですが(←かなりの変人)、河川カメラを見るには「川の防災情報」という政府が提供するWebアプリを開く必要があり、また、河川カメラを見るにはアプリを色々操作して自分の映っているカメラを見つける必要があります。
まあ政府のアプリですから使い勝手はあまりよろしくない(他のマイナとかホームページに比べたら1000倍マシですが…)というか読み込みサイズがデカいので外出中には不便ということがありました。
じゃあ自分で気の済むアプリを作っちゃえばええやん、ということでアプリを作ったのですが、淀川にはだいたい橋の根本?にカメラが付いているので、自分が今どの橋にいるのか把握する必要があります。まあそんなのマップアプリを見たら済む話なのですが、変人は自分のアプリに現在地機能をつけたいと思います。
それで、Leafletで現在地を表示する機能を開発したというわけです。長くてすみません。
https://nanka.cloudfree.jp/bin/webapps/scam_demo/
下のボタン「現在地取得」を押してくださいね。
ちなみに現在地アイコンはGoogleマップをパクって自分で作りました。
作ろう
まずそもそもWebなんかで現在地という超個人情報を取得できるのか???ということがあると思いますが、ユーザーが許可してくれれば上の画像のようにまさかの【精度:±5m】で現在地を取得できます。(つまり一度ユーザーの許可を得られればあなたの現在地はすぐに丸裸になるということです。)
まず、基本方針としては、位置はもちろんGNSS(GPS)から、方位に関してはGNSSから取得できないことが大多数だったのでデバイスの磁気センサーから取得するようにしました。
位置はGNSSの正確な位置ですが、方位に関してはあんまり信頼しないでください。デバイスの磁気センサーはアクセスできても全然当てになりません。まあ一応計算はしといたよって感じです。北を向いているのに端末によっては南に表示されることもありますので…。
navigator.geolocation.getCurrentPosition(function(position) {
var lat = position.coords.latitude;
var lng = position.coords.longitude;
var accuracy = position.coords.accuracy;
var gnssLatlng = L.latLng(lat, lng);
}
で、ユーザーに現在地使用の許可を出して現在地が取得できます。
getOrientationYaw()
そして、これでデバイスの磁気センサーから方角を取得します。
ここの関数は、ヨー角という謎の値を煮たり焼いたりチンしたりしてなんとか現実の3D座標に直して方角にするらしいです。マジで面倒くさそうというか全くわからなかったのでGeminiちゃんに丸投げ&丸コピしましたので解説できません。許してね🙏
そして、上の謎処理で方角を求めてからマーカーを描画します。
function drawGnssMarker() {
var gnssMarkerStyle;
console.log(direction);
if (direction != null) {
gnssMarkerStyle = L.icon({
iconUrl: 'source/gnss_direction.svg', // アイコン画像のパス
iconSize: [80, 80], // アイコンのサイズ [幅, 高さ]
iconAnchor: [40, 40], // アイコンの「先端」の位置(マーカーの座標と一致させる部分)
popupAnchor: [0, -80] // ポップアップが表示される位置のオフセット
});
// 新しいマーカーを追加
currentGnssMarker = L.marker(gnssLatlng, {icon: gnssMarkerStyle, rotationAngle: direction}).addTo(map);
currentGnssMarker.bindPopup(`緯度:${lat.toFixed(5)}<br>経度:${lng.toFixed(5)}<br>精度:±${accuracy.toFixed(2)}m<br>方角:${directionText}, ${direction.toFixed(2)}°`);
} else {
gnssMarkerStyle = L.icon({
iconUrl: 'source/gnss_nodirection.svg', // アイコン画像のパス
iconSize: [80, 80], // アイコンのサイズ [幅, 高さ]
iconAnchor: [40, 40], // アイコンの「先端」の位置(マーカーの座標と一致させる部分)
popupAnchor: [0, -80] // ポップアップが表示される位置のオフセット
});
// 新しいマーカーを追加
currentGnssMarker = L.marker(gnssLatlng, {icon: gnssMarkerStyle}).addTo(map);
currentGnssMarker.bindPopup(`緯度:${lat.toFixed(5)}<br>経度:${lng.toFixed(5)}<br>精度:±${accuracy.toFixed(2)}m<br>方角:取得不可`);
}
// 地図を現在地に移動
map.setView(gnssLatlng, 15); // 現在地を中心にズームレベル15に設定
console.log("現在地: 緯度 " + lat + ", 経度 " + lng);
$('#gnssLoading').removeClass('display');
}
デバイスによって(iPad等)は磁気センサーにアクセスできないというかそもそもなさそうなので、方角が取得できません。よってアイコンを方角表示なしのものにしています。
(そもそも磁気センサーがあってもテキトーな方角を表示してるんやから、磁気センサーがなくてもテキトーに表示しといても変わらんやろ、というツッコミは置いといて…。)
とりあえずこれで現在地を表示することができました!!!(方角はオマケ)
何書いてるのか分からんからもっとここ解説せえや とか ここなんの処理してんねんお前の記事読むよりAIちゃんのほうが1000倍ええやんけボケナス などなど、ご不満等あれば、コメント欄へ↓
GitHub
コードはとりまGitHubに上げときました。
今回のJS全体↓
現在地マーカー(方角あり)
https://nanka.cloudfree.jp/bin/webapps/scam_demo/source/gnss_direction.svg
現在地マーカー(方角なし)
https://nanka.cloudfree.jp/bin/webapps/scam_demo/source/gnss_nodirection.svg