5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【HERE WeGo!】ジオファン集まれ!地理空間情報、地図に関する記事を募集しています by HEREAdvent Calendar 2022

Day 20

HERE Maps API for JavaScript にコンパスアイコンを追加してみた

Last updated at Posted at 2022-12-19

はじめに

HERE Maps API for JavaScriptの特徴の一つとしては、ベクトルタイルを使って地図をレンダリングしますので、ユーザーが地図を任意に回転や傾斜させることができます。(マウスの場合はAltキー/Optionキーを押しながらドラッグ、スマホやタッチスクリーンの場合は2本指で回転や平行移動)

しかしHERE Maps API for JavaScriptはデフォルトでは回転後の地図の向きを示すための、いわゆるコンパスアイコンを用意してくれていないので、今回はそのコンパスアイコンに該当するものを追加してみたいと思います。

実現したい機能:

  1. 地図の向きを示す
  2. クリックされたら地図の向きを北向きに戻す
  3. 自動的に表示/非表示 (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はデフォルトでは日本の詳細地図を表示してくれないので、下記公式ドキュメントを参考にして、まずベースとなるウェブページを作ります。

index.html
<!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>

これで日本の詳細地図も表示できたし、日本のために特別にデザインされた地図スタイルも適用できました。ついでによく使うズームなどのボタンの追加や、地図のインターラクティブ化もできました。

ただし実はこれだけだとまだ一つ盲点があります。それは右下のレイヤー選択ボタンで一旦他のレイヤーを選んでしまうと、もう元の詳細地図に戻せなくなってしまうことです。
1.gif

ちょっと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をクリックする度に、uidefaultLayersを参照して、現在表示レイヤーを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 JavaScriptH.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);
...

これで地図ビューの右上にネイティブっぽいボタンを追加出来ました。
image.png

ただしそのままだと、アイコンが少し小さすぎる気がするし、形的にもコンパスっぽくないので、少しスタイルを調整します。

...
    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です。
image.png

コンパスアイコンを動かせる

次はコンパスアイコンを地図の回転と連動させるために、コードを追加します。

    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 JavaScriptmapviewchangeというイベントを提供しています。名前の通りに地図のビューに何かしらの変更が加えられた時に発生するイベントで、その都度に予めに渡した関数をコールバックしてくれますが、今回は地図の向きの変更にしか興味がないので、evt.modifiers & H.map.ChangeEvent.Type.HEADINGでさらに範囲を絞って無駄を減らします。

コンパスアイコンの向きの変更はSVG内の図形のパスそのものに回転を適用させることで実現しています。実際の効果は以下のようになります。
2.gif

地図向きのリセット

地図の向きが自由に変えられるのは良いものの、デフォルトにリセットしたい時もありますよね。そんな時のために、コンパスアイコンがクリックされたら地図の向きを北向きに戻す機能を実装します。

コンパスアイコンのボタンを作成する時のコードに少し手を加えます。

...
-   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を渡すことによって、視点がいきなり変わるのではなく、自動的にアニメーション付きでゆっくり変化してくれるので、かなりいい感じです。実際の効果は以下のようになります。
3.gif

自動的に表示/非表示

正直ここまでできたらほぼ満足していますが、せっかくですので某地図アプリみたいに、地図がすでに北向きになっている時にコンパスアイコンが自動的に隠れるようにさらに実装してみたいと思います。

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);
+         }
+       }
+     }
+   });
...

最終的にこんな感じになりました。
4.gif

おわりに

HERE Maps API for JavaScriptの高いカスタマイズ性と柔軟性のおかげで、無事にコンパスアイコンにまつわる一連の機能をすべて実装できました。

最終のコードをまとめて以下に置いておきました。似たような機能を実現されたい方のご参考になれば幸いです。

See the Pen HERE Maps API for JavaScript - Japan by kairyu (@kairyu1103) on CodePen.

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?