🧭

maplibre-gl-compass でヘディングアップな地図を作る

2024/12/17に公開

はじめに

地図の向きを表す際、北が上になる表示を ノースアップ (north-up) 、進行方向が上になる表示を ヘディングアップ (heading-up) と呼びます。

通常、Web 地図はノースアップで表示されますが、今回はヘディングアップな地図を作るための MapLibre GL JS 用プラグイン maplibre-gl-compass を作成したので紹介します。

https://www.npmjs.com/package/maplibre-gl-compass

このようなものが作れます。

端末の向きに連動して地図が回転

デモはこちら

前置き

MapLibre GL JS は、ベクター地図であるため 地図の回転や傾きをサポートしています。
以下のように簡単に設定が可能です。

map.setBearing(180) // 南を上にする
map.setPitch(45)    // 45度寝かす

南が上の日本地図
南が上の日本地図

しかし、コンパス機能は提供されていません
厳密に言うと、MapLibre GL JS が提供している NavigationControl には showCompass オプションがあり true にすると以下の表示となります。

NavigationControl
拡縮ボタン下にコンパスボタンが追加

ただしこのボタンは、地図をノースアップに戻すもので、 端末の現在の向きに応じて地図を自動で回転させるものではありません
GitHub の Issue[1] でヘディングアップの要望も上がっていましたが、MapLibre GL JS に組み込む予定はないとの回答であったため、プラグインの開発に至りました。

基本的な使い方

MapLibre GL JS に組み込むのは非常に簡単です。

プラグインをインストールし、

npm install maplibre-gl-compass

CompassControl を地図に追加すれば ok です。

import { Map } from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { CompassControl } from 'maplibre-gl-compass'
import 'maplibre-gl-compass/style.css'

const map = new Map({/* YOUR_MAP_OPTION */})

map.addControl(new NavigationControl({ showCompass: false }))
map.addControl(new CompassControl())

コンパスボタン
コンパスボタン

応用的な使い方① GeolocateControl との連動

ヘディングアップは、現在位置と組み合わせて使いたいケースが多いと思いますので、
MapLibre GL JS が提供している GeolocateControl と組み合わせて、コンパスを押したら現在位置も取得する実装をしてみます。

import { Map, GeolocateControl } from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import { CompassControl } from 'maplibre-gl-compass'
import 'maplibre-gl-compass/style.css'

// STEP1: 地図を生成し Compass と Geolocate のコントロールを追加する
const map = new Map({/* YOUR_MAP_OPTION */})
const compass = new CompassControl()
const geolocate = new GeolocateControl({
  positionOptions: { enableHighAccuracy: true },
  trackUserLocation: true,
})
map.addControl(compass)
map.addControl(geolocate)

// STEP2: 各ボタンを連動させるための制御をおこなう
compass.on('turnon', () => {
  // コンパスを押したときに、ジオロケーションを有効にする
  if (geolocate._watchState !== 'ACTIVE_LOCK') {
    geolocate.trigger()
  }
})
geolocate.on('userlocationlostfocus', () => {
  // NOTE: コンパスが向きを設定 (setBearing) すると自動トラッキングが OFF になるため、ユーザー操作でない変更時は再度 ON にする
  if (!isOperating) {
    geolocate.trigger()
  }
})

// STEP3: 上記の userlocationlostfocus が発火した時、ユーザー操作起因かどうかを判断するためのフラグを管理する
let isOperating = false
map.on('touchstart', () => (isOperating = true))
map.on('touchend', () => (isOperating = false))

これでコンパス押下で、現在位置取得も連動するようになります。
コンパス + 現在地ボタン
コンパス + 現在地ボタン

応用的な使い方② 独自カスタマイズ

さらに独自で UI も作りたい場合は、下記のようにすることで標準ボタンを非表示に出来ます。
コンパス機能を自身でハンドリングしたい場合の、応用的な使い方となります。

const compass = new CompassControl({
  visible: false // ボタンを非表示にする
})
map.addControl(compass)

// 独自ボタンでコンパス ON / OFF
customizeButton.on('click', () => {
  if (toggle) {
    compass.turnOn()
  } else {
    compass.turnOff()
  }
})

compass.on('compass', (event: CompassEvent) => {
  // コンパスが値を取得した時のロジックを書けます
})

ハマった & 苦労したポイント

以下は今回の実装でハマったポイントでした。

回転がジャギジャギする

センサーの値は 1秒あたり約60件発生 するのですが、そのまま使うと外れ値がたまにあり回転がカクツク現象がありました
これは移動平均フィルタで外れ値を平滑化し、直近100件の平均値 とすることで滑らかな表示になりました。

ズーム中にモッサリする

ズーム中 (FlyTo 時) に回転 (setBearing)をおこなうと描画がバッティングするためか、かなりモッサリした表示になりました。
そのため、ズーム中は回転しないよう制御することで対応しました。

端末 / OS 差分がある

Android / iOS で参照するセンサーのイベントが異なっていたり、iOS の特定バージョン以降は 許可 (requestPermission) が必要で、分岐を入れたりと若干複雑でした。

おわりに

いかがでしたでしょうか。
お手軽にコンパス機能を追加できる、 maplibre-gl-compass でヘディングアップな地図を作ってみてください。

PR や issue も受け付けてます!
https://github.com/qazsato/maplibre-gl-compass

脚注
  1. https://github.com/maplibre/maplibre-gl-js/issues/1358 ↩︎

Discussion