🛫

Mapbox Newsletter WEEKLY TIPSの解説 -「マップの場所をスライドショーとして再生」

2023/09/29に公開

はじめに

この記事は、先日配信されたMapbox NewsletterのWEEKLY TIPSで紹介されていた「マップの場所をスライドショーとして再生」についての解説です。このサンプルではflytTomoveendの使い方を紹介しています。また、Newsletterの購読はこちらからお申し込みいただけます。

コードを確認

まずExamplesのコードを見に行きましょう。

日本語サイト

英語サイト

基本的に同じコードですが、英語版はスタイルがMapbox Streets v12にアップグレードされているのでこちらを使用します。Mapbox Streets v11ではデフォルトのプロジェクションがWebメルカトルであるのに対し、Mapbox Streets v12ではGlobe(3D表示された地球)なので、印象がかなり異なります。

HTML/CSS

CSSとしては以下のスタイルを定義しています。

.map-overlay-container {
  position: absolute;
  width: 25%;
  top: 0;
  left: 0;
  padding: 10px;
  z-index: 1;
}
 
.map-overlay {
  font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
  background-color: #fff;
  border-radius: 3px;
  padding: 10px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
 
.map-overlay h2,
.map-overlay p {
  margin: 0 0 10px;
}

このスタイルは以下のHTMLのスタイリングに利用されています。これは、地図の左上に表示されている紹介文を表示する部分のエレメントです。

<div class="map-overlay-container">
  <div class="map-overlay">
    <h2 id="location-title"></h2>
    <p id="location-description"></p>
    <small>Text credit:
      <a target="_blank" href="http://www.nycgo.com/neighborhoods">nycgo.com</a></small>
  </div>
</div>

また、以下は地図を表示するエレメントを作成しています。

<div id="map"></div>

Mapの作成

次にJavaScriptのコードを見ていきます。以下のコードはいつも通り、Mapオブジェクトを作成しています。containerで地図を表示するHTMLエレメントのidを指定します。

const map = new mapboxgl.Map({
  container: 'map',
  // Choose from Mapbox's core styles, or make your own style with Mapbox Studio
  style: 'mapbox://styles/mapbox/streets-v12',
  center: [-74.0315, 40.6989],
  maxZoom: 16,
  minZoom: 9,
  zoom: 9.68
});

紹介文のための処理

紹介文を表示するためにHTMLのエレメントを取得してtitledescriptionという変数でアクセスできるようにしています。

const title = document.getElementById('location-title');
const description = document.getElementById('location-description');

各地点のデータ

次は、各地点のデータです。title, descriptionおよびcameraというデータが各地点に含まれているのがわかります。

const locations = [
  {
    'id': '2',
    'title': 'The Bronx',
    'description':
    "This is where hip-hop was born, where the Yankees became a dynasty and where you can find New York City's leading zoo and botanical garden.",
    'camera': {
      center: [-73.8709, 40.8255],
      zoom: 12.21,
      pitch: 50
    }
  },
...
];

ヘルパ関数の定義

ここでは2個のヘルパ関数が定義されています。

1つ目はレイヤーのフィルターを変更するためのヘルパ関数です。紹介文に該当するポリゴンのみが表示する処理を行います。具体的には、highlightレイヤーのborocodeプロパティの値がcodeと一致するポリゴンのみが表示されるようになります。

function highlightBorough(code) {
  // Only show the polygon feature that corresponds to `borocode` in the data.
  map.setFilter('highlight', ['==', 'borocode', code]);
}

2つ目はアニメーションの処理を行うためのヘルパ関数です。titledescriptionをセットし、ポリゴンを表示させます。またflyToでその地点にカメラ(視点)を移動させます。

map.onceはイベント発火時に一度だけ処理を行う際に使用します。map.onはイベント発火時に毎回行われる処理を登録する機能なので、違いに注意しましょう。

また、ここではmoveendイベントを使用しています。このイベントは地図上の移動が終了した際に発火します。flyToは飛行機で離陸・着陸するようなアニメーションを伴う移動なので、ある程度の時間が必要です。そこで、moveendイベントでアニメーションが終了したことを検知し、その3秒後にplaybackを再度呼び出して次の地点へと移動します。

function playback(index) {
  title.textContent = locations[index].title;
  description.textContent = locations[index].description;
   
  highlightBorough(locations[index].id ? locations[index].id : '');
   
  // Animate the map position based on camera properties.
  map.flyTo(locations[index].camera);
   
  map.once('moveend', () => {
    // Duration the slide is on screen after interaction.
    window.setTimeout(() => {
      // Increment index, looping back to the first after the last location.
      index = (index + 1) % locations.length;
      playback(index);
    }, 3000); // After callback, show the location for 3 seconds.
  });
}

紹介文の初期化

以下のコードでtextおよびdescriptionの初期化を行っています。locationsの最後の要素の値で初期化しています。

// Display the last title/description first.
title.textContent = locations[locations.length - 1].title;
description.textContent = locations[locations.length - 1].description;

ソース、レイヤーの作成

loadイベント(map.on('load', () => {})の中身)で1つのソース、1つのレイヤーを追加しています。

まず、ソースを作成しています。mapbox://mapbox.8ibmsn6uというベクタータイルセットを使用しています。boroughsというidのソースとして読み込んでいます。

map.addSource('boroughs', {
  'type': 'vector',
  'url': 'mapbox://mapbox.8ibmsn6u'
});

このデータは、(画面を明るくしないと見にくいですが)以下のようにニューヨークの各自治区のポリゴンデータが含まれるベクタータイルセットです。また、先程出てきたborocodeというプロパティがあることがわかります。レイヤー(ベクタータイルセットにおけるレイヤーとは、データのグループ名です)はoriginalなので、addLayersource-layerではこの値を使用します。
polygon

次にレイヤーをhighlightというidで作成しています。ソースとしては先程のboroughsを使用し、fillつまりポリゴンの色と不透明度を設定しています。また、フィルター(filter)のExpressionsは['==', 'borocode', '']となっているので一致するデータは存在しない、つまり表示されるポリゴンはありません。

map.addLayer(
  {
    'id': 'highlight',
    'type': 'fill',
    'source': 'boroughs',
    'source-layer': 'original',
    'paint': {
      'fill-color': '#fd6b50',
      'fill-opacity': 0.25
    },
    'filter': ['==', 'borocode', '']
  },
  'road-label' // Place polygon under labels.
);

アニメーションの開始

playback関数を呼び出すことでアニメーションを開始します。playback関数の中でhighlightBoroughが実行され、map.setFilter('highlight', ['==', 'borocode', code]);によってフィルタの条件が変わります。「borocodeプロパティがcodeに一致するポリゴンを表示」というExpressionsになるので、該当するポリゴンのみが表示されます。

playback(0);

まとめ

flytTomoveendの使い方を確認しました。少し長めのコードでしたが、要素を分解してみると簡単だったかと思います。

おまけ

カメラコントロールの代表的なものに以下の3個があります。

以下はこれらの動きを試すデモです。右上のボタンをクリックすると、東京駅・秋葉原駅間を移動します。

GitHubで編集を提案
マップボックス・ジャパン合同会社

Discussion