🎚️

Mapbox Newsletter WEEKLY TIPSの解説 -「タイムスライダーを作成」

2024/07/09に公開

はじめに

この記事は、先日配信されたMapbox NewsletterのWEEKLY TIPSで紹介されていた「タイムスライダーを作成」についての解説です。このサンプルではMap#setFilterの使い方を例示しています。また、Newsletterの購読はこちらからお申し込みいただけます。

以下が本サンプルのデモです。

コードを確認

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

日本語サイト

英語サイト

基本的に同じコードですが、英語版はスタイルとしてMapbox Light v11、プロジェクション(地図投影法)としてヴィンケルトリペル図法を使用しています。プロジェクションの変更はMapbox GL JS v2.6からサポートされています。ここでは新機能を使っている英語版を参照します。また、英語版はMapbox GL JS v3が使用されています。

HTML/CSS

まずHTMLを見ていきましょう。

以下は地図を表示するエレメントです。

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

以下は左上のタイムスライダー部分の定義です。1つ目のdivがスライダー、2つ目のdivが凡例部分です。

<div class="map-overlay top">
  <div class="map-overlay-inner">
    <h2>Significant earthquakes in 2015</h2>
    <label id="month"></label>
    <input id="slider" type="range" min="0" max="11" step="1" value="0">
  </div>
  <div class="map-overlay-inner">
    <div id="legend" class="legend">
      <div class="bar"></div>
      <div>Magnitude (m)</div>
    </div>
  </div>
</div>

また、以下はタイムスライダー部分のスタイルです。

.map-overlay {
  font:
    12px/20px 'Helvetica Neue',
    Arial,
    Helvetica,
    sans-serif;
  position: absolute;
  width: 25%;
  top: 0;
  left: 0;
  padding: 10px;
}

...中略...

.map-overlay input {
  background-color: transparent;
  display: inline-block;
  width: 100%;
  position: relative;
  margin: 0;
  cursor: ew-resize;
}

さらに、後ほどD3の機能を使用するため、ライブラリをロードしています。

<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>

Mapの作成

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

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/light-v11',
  projection: 'winkelTripel',
  center: [-45, 0],
  zoom: 0.25
});

地震データの取得

途中を飛ばして、地震データの取得を見ていきます。ソースとレイヤーはスタイルが読み込まれた後に作成する必要がありますが、データの取得とのタイミングを合わせるのが難しいです。そこでここでは地図のロードを待ってからデータの取得を行っています。そのため、map.on('load', () => { /* ここ */})の部分に処理を書きます。

ここではd3.jsを使ってJSONデータを取得しています。

// Data courtesy of http://earthquake.usgs.gov/
// Query for significant earthquakes in 2015 URL request looked like this:
// http://earthquake.usgs.gov/fdsnws/event/1/query
//    ?format=geojson
//    &starttime=2015-01-01
//    &endtime=2015-12-31
//    &minmagnitude=6'
//
// Here we're using d3 to help us make the ajax request but you can use
// Any request method (library or otherwise) you wish.
d3.json(
  'https://docs.mapbox.com/mapbox-gl-js/assets/significant-earthquakes-2015.geojson',
  jsonCallback
);

コメントにもありますが、fetchでも問題ありません。サンプルで使用しているD3はバージョンが古い (v3) ので、アプリケーション開発の際には最新のプラクティスにしたがうのがよいでしょう。

ソース・レイヤーの作成

データの処理

d3.jsonでデータの取得が完了すると function jsonCallback(err, data)が呼び出されます。処理中身を見ていきます。

データの取得に失敗すると第一引数のerrに値が入っているので、例外を発生させて以降の処理を中断します。

if (err) {
  throw err;
}

元データのtimeプロパティにはタイムスタンプが入っています。スライダーでは月単位で表示を切り替えるのでgetMont()で月 (0 - 11)を取得し、monthというプロパティに設定しています。

data.features = data.features.map((d) => {
  d.properties.month = new Date(d.properties.time).getMonth();
  return d;
});

ソースの作成

先ほど加工したGeoJSONデータを用いて、ソースを作成します。

map.addSource('earthquakes', {
  'type': 'geojson',
  data: data
});

レイヤーの作成

レイヤーは2つ作成しています。

1つ目は地震の大きさを表現するためのCircleレイヤーです。Expressionsとしてはinterpolateを使用し、['get', 'mag']で取得したマグニチュードの大きさに応じて円の色 (earthquake-circles) および円の大きさ (circle-radius) を変えています。

map.addLayer({
  'id': 'earthquake-circles',
  'type': 'circle',
  'source': 'earthquakes',
  'paint': {
    'circle-color': [
      'interpolate',
      ['linear'],
      ['get', 'mag'],
      6,
      '#FCA107',
      8,
      '#7F3121'
    ],
    'circle-opacity': 0.75,
    'circle-radius': [
      'interpolate',
      ['linear'],
      ['get', 'mag'],
      6,
      20,
      8,
      40
    ]
  }
});

2つ目はマグニチュードの値を表示するSymbolレイヤーです。['get', 'mag']で取得した数値のマグニチュードをto-stringで文字列に変換し、さらにconcatを使ってmという文字列と連結しています。

map.addLayer({
  'id': 'earthquake-labels',
  'type': 'symbol',
  'source': 'earthquakes',
  'layout': {
    'text-field': ['concat', ['to-string', ['get', 'mag']], 'm'],
    'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
    'text-size': 12
  },
  'paint': {
    'text-color': 'rgba(0,0,0,0.5)'
  }
});

スライダーの処理

最後がスライダーの処理です。

まずは途中で飛ばしていた処理を確認します。以下は月の定義です。getMonth()0始まりの月を返すので、month配列で月名に変換します。

const months = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December'
];

findBy関数は数値の月(0 - 11)を引数に取ります。Map#setFilterは第1引数にレイヤーID、第2引数に設定するフィルタを指定します。ここでは引数で与えられた月に合致するデータのみを表示するように各レイヤーのフィルタを設定しています。「データの処理」でgetMonth()を使ってmonthというプロパティを作成しましたが、ここでその値を使ってフィルタを掛けます。そうすることで、選択した月のデータのみがレイヤーとして描画されます。最後にスライダーの上に表示されている月名ラベルを更新しています。

function filterBy(month) {
  const filters = ['==', 'month', month];
  map.setFilter('earthquake-circles', filters);
  map.setFilter('earthquake-labels', filters);

  // Set the label to the month
  document.getElementById('month').textContent = months[month];
}

最初にfilterBy0で呼び出すことで、1月で初期化します。

filterBy(0);

スライダーの値が変更されるのに応じてfilterByを呼び出すことでフィルタを変更し、地図上の表示を切り替えます。

document.getElementById('slider').addEventListener('input', (e) => {
  const month = parseInt(e.target.value, 10);
  filterBy(month);
});

まとめ

少し長めのコードでしたが、処理を分解してみていくことで一つ一つは単純な処理であることがわかりました。また、データは最初にすべてソースとして作成し、レイヤーのフィルタで表示を切り替える手法を学びました。

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

Discussion