はじめに
HERE Maps API for JavaScriptの特徴の一つとしては、ベクトルタイルを使って地図をレンダリングしますので、ユーザーが地図を任意に回転や傾斜させることができます。(マウスの場合はAlt
キー/Option
キーを押しながらドラッグ、スマホやタッチスクリーンの場合は2本指で回転や平行移動)
しかしHERE Maps API for JavaScriptはデフォルトでは回転後の地図の向きを示すための、いわゆるコンパスアイコンを用意してくれていないので、今回はそのコンパスアイコンに該当するものを追加してみたいと思います。
実現したい機能:
- 地図の向きを示す
- クリックされたら地図の向きを北向きに戻す
- 自動的に表示/非表示 (Optional)
動作環境
- OS: macOS Monterey (Version 12.6.2)
- Browser: Google Chrome (Version 108.0.5359.124)
- HERE Maps API for JavaScript (Version 3.1.37.0)
事前準備
HERE Maps API for JavaScriptを使うには、HERE APIKEYが必要になりますが、無料APIKEYの取得しかたはすでにたくさんの方々が詳しく紹介してくださりましたので、ここでは割愛させて頂きます。
日本地図の表示
HERE Maps API for JavaScriptはデフォルトでは日本の詳細地図を表示してくれないので、下記公式ドキュメントを参考にして、まずベースとなるウェブページを作ります。
<!DOCTYPE html>
<html>
<head>
<title>HERE Maps API for JavaScript - Japan</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<script src="https://js.api.here.com/v3/3.1/mapsjs-core.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-service.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"></script>
<script src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"></script>
<link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" />
<style>
body {
font-family: FiraGO, "Fira Sans", sans-serif;
margin: 0;
padding: 0;
}
#map {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
const platform = new H.service.Platform({
apikey: '{YOUR_HERE_APIKEY}',
});
const defaultLayers = platform.createDefaultLayers();
const omvService = platform.getOMVService({path: 'v2/vectortiles/core/mc'});
const baseUrl = 'https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/';
const style = new H.map.render.webgl.Style(`${baseUrl}normal.day.yaml`, baseUrl);
const omvProvider = new H.service.omv.Provider(omvService, style);
const omvLayer = new H.map.layer.TileLayer(omvProvider, {max: 22});
const map = new H.Map(
document.getElementById('map'),
omvLayer,
{
center: {lat: 35.68, lng: 139.76},
zoom: 12,
}
);
const ui = H.ui.UI.createDefault(map, defaultLayers);
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
window.onresize = function () {
map.getViewPort().resize()
};
</script>
</body>
これで日本の詳細地図も表示できたし、日本のために特別にデザインされた地図スタイルも適用できました。ついでによく使うズームなどのボタンの追加や、地図のインターラクティブ化もできました。
ただし実はこれだけだとまだ一つ盲点があります。それは右下のレイヤー選択ボタンで一旦他のレイヤーを選んでしまうと、もう元の詳細地図に戻せなくなってしまうことです。
ちょっとDirty Hack感がありますが、個人的には以下のように無理やり直しています。
...
const omvLayer = new H.map.layer.TileLayer(omvProvider, {max: 22});
const map = new H.Map(
document.getElementById('map'),
omvLayer,
{
center: {lat: 35.68, lng: 139.76},
zoom: 12,
}
);
+ defaultLayers.vector.normal.map = omvLayer;
const ui = H.ui.UI.createDefault(map, defaultLayers);
const behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
...
この一行を足すだけです。つまりユーザーがMap viewをクリックする度に、ui
はdefaultLayers
を参照して、現在表示レイヤーをdefaultLayers.vector.normal.map
に戻していますが、そのdefaultLayers.vector.normal.map
の中身を無理やり我々のomvLayer
に変えてしまえばとりあえず期待通りの動きになる、ということになりです。
これらを全部まとめると、以下になります。これは個人的によく使っているテンプレートにもなっています。
See the Pen HERE Maps API for JavaScript - Japan by kairyu (@kairyu1103) on CodePen.
コンパスアイコンの表示
ここからはやっと本題に入ります、まずはいい感じのアイコンを見つけて来ます。SVGが理想的ですね。
See the Pen Untitled by kairyu (@kairyu1103) on CodePen.
アイコンを表示させる方法が色々ありますが、せっかくなのでHERE Maps API for JavaScriptのH.ui.Control
機能を使ってネイティブっぽく実装してみたいと思います。
...
window.onresize = function () {
map.getViewPort().resize()
};
+ const label = '<svg class="H_icon" viewBox="0 0 1000 1000"><path transform-origin="center" d="M388.56 500L500 834.037 611.299 500 500 165.963zm173.453-1.202l-60.81 231.79-62.51-230.8z"/></svg>';
+ const compassButton = new H.ui.base.Button({label});
+ const compassControl = new H.ui.Control();
+ compassControl.addChild(compassButton);
+ compassControl.setAlignment('top-right');
+ ui.addControl('compass-control', compassControl);
...
これで地図ビューの右上にネイティブっぽいボタンを追加出来ました。
ただしそのままだと、アイコンが少し小さすぎる気がするし、形的にもコンパスっぽくないので、少しスタイルを調整します。
...
const compassButton = new H.ui.base.Button({label});
const compassControl = new H.ui.Control();
+ compassControl.addClass('compass');
compassControl.addChild(compassButton);
compassControl.setAlignment('top-right');
ui.addControl('compass-control', compassControl);
...
<style>
body {
font-family: FiraGO, "Fira Sans", sans-serif;
margin: 0;
padding: 0;
}
#map {
width: 100vw;
height: 100vh;
}
+ .compass .H_btn {
+ border-radius: 50%;
+ }
+ .compass .H_icon {
+ width: auto;
+ height: auto;
+ }
<style>
border-radius
で四角を円にするというよくある技の応用です。これで見た目はOKです。
コンパスアイコンを動かせる
次はコンパスアイコンを地図の回転と連動させるために、コードを追加します。
map.addEventListener('mapviewchange', function (evt) {
if (evt.modifiers & H.map.ChangeEvent.Type.HEADING) {
const path = document.querySelector('.compass svg path');
const heading = 180 - evt.newValue.lookAt.heading;
path.setAttribute('transform', `rotate(${heading})`);
}
});
HERE Maps API for JavaScriptはmapviewchange
というイベントを提供しています。名前の通りに地図のビューに何かしらの変更が加えられた時に発生するイベントで、その都度に予めに渡した関数をコールバックしてくれますが、今回は地図の向きの変更にしか興味がないので、evt.modifiers & H.map.ChangeEvent.Type.HEADING
でさらに範囲を絞って無駄を減らします。
コンパスアイコンの向きの変更はSVG内の図形のパスそのものに回転を適用させることで実現しています。実際の効果は以下のようになります。
地図向きのリセット
地図の向きが自由に変えられるのは良いものの、デフォルトにリセットしたい時もありますよね。そんな時のために、コンパスアイコンがクリックされたら地図の向きを北向きに戻す機能を実装します。
コンパスアイコンのボタンを作成する時のコードに少し手を加えます。
...
- const compassButton = new H.ui.base.Button({label});
+ const compassButton = new H.ui.base.Button({
+ label,
+ onStateChange: function (evt) {
+ if (evt.target.getState() === H.ui.base.Button.State.UP) {
+ const viewModel = map.getViewModel();
+ const lookAtData = Object.assign(viewModel.getLookAtData(), {
+ heading: 180,
+ incline: 0,
+ tilt: 0,
+ };
+ viewModel.setLookAtData(lookAtData, true)
+ }
+ },
+ });
const compassControl = new H.ui.Control();
...
label
以外に新たにonStateChange
を指定することによってクリックイベントをハンドリングします。地図ビューのsetLookAtData
関数を呼び出すことによって任意にビューの視点を変えられますが、このLookAtData
には向きと傾斜以外に中心やズームレベルなどのデータも含まれますので、変えたくないデータはObject.assign
でコピーします。
そしてsetLookAtData
の2番めの引数にtrue
を渡すことによって、視点がいきなり変わるのではなく、自動的にアニメーション付きでゆっくり変化してくれるので、かなりいい感じです。実際の効果は以下のようになります。
自動的に表示/非表示
正直ここまでできたらほぼ満足していますが、せっかくですので某地図アプリみたいに、地図がすでに北向きになっている時にコンパスアイコンが自動的に隠れるようにさらに実装してみたいと思います。
mapviewchange
イベントをハンドリングするコードに少し手を入れます。
...
compassControl.addChild(compassButton);
compassControl.setAlignment('top-right');
- ui.addControl('compass-control', compassControl);
...
map.addEventListener('mapviewchange', function (evt) {
if (evt.modifiers & H.map.ChangeEvent.Type.HEADING) {
const path = document.querySelector('.compass svg path');
- const heading = 180 - evt.newValue.lookAt.heading;
- path.setAttribute('transform', `rotate(${heading})`);
+ if (path) {
+ const heading = 180 - evt.newValue.lookAt.heading;
+ path.setAttribute('transform', `rotate(${heading})`);
+ }
}
+
+ if (evt.modifiers & (
+ H.map.ChangeEvent.Type.HEADING |
+ H.map.ChangeEvent.Type.INCLINE |
+ H.map.ChangeEvent.Type.TILT
+ )) {
+ if (evt.newValue.lookAt.heading != 180 ||
+ evt.newValue.lookAt.incline != 0 ||
+ evt.newValue.lookAt.tilt != 0
+ ) {
+ if (!ui.getControl('compass-control')) {
+ ui.addControl('compass-control', compassControl);
+ }
+ }
+ else {
+ if (ui.getControl('compass-control')) {
+ ui.removeControl('compass-control', compassControl);
+ }
+ }
+ }
+ });
...
おわりに
HERE Maps API for JavaScriptの高いカスタマイズ性と柔軟性のおかげで、無事にコンパスアイコンにまつわる一連の機能をすべて実装できました。
最終のコードをまとめて以下に置いておきました。似たような機能を実現されたい方のご参考になれば幸いです。
See the Pen HERE Maps API for JavaScript - Japan by kairyu (@kairyu1103) on CodePen.