LoginSignup
5
6

More than 5 years have passed since last update.

バイナリベクトルタイルを地図ライブラリ以外で扱う

Posted at

Mapbox GL JSLeaflet といった地図ライブラリを使わずに JavaScript でバイナリベクトルタイルを使う方法です。

やること

fetchpbf (mvt) 形式のファイルを取得し、Features の配列に変換する。

コード

npm で pbf@mapbox/vector-tile モジュールをインストールします。

$ npm install --save pbf @mapbox/vector-tile

fetch して response → ArrayBuffer → Pbf の順に変換し、GeoJSON Feature の配列を取得

import Pbf from 'pbf';
import { VectorTile } from '@mapbox/vector-tile';

const tileCoord = { x: 14582, y: 6414, z: 14 };
const baseUrl = 'https://cieloazul310.github.io/mvt-tiles/tile/population_250m/';
const layerName = '250mesh';

fetch(baseUrl + `${tileCoord.z}/${tileCoord.x}/${tileCoord.y}.mvt`)
  .then(res => res.arrayBuffer())
  .then(buffer => new Pbf(buffer))
  .then(pbf => {
    const layer = new VectorTile(pbf).layers[layerName];
    const features = [];
    if (layer) {
      for (let i = 0; i < layer.length; i++) {
        const feature = layer.feature(i).toGeoJSON(tileCoord.x, tileCoord.y, tileCoord.z);
        features.push(feature);
      }
    }
    return features;
  })
  .then(features => {
    console.log(features);
    // => Array(46) [ feature, feature, feature, ..., feature ]
  });

バイナリベクトルタイルの拡張子は作成時に mvt にしていたので、この記事でも mvt としています。

しかし、この分野の国内における先駆者である@hfuさんの記事 バイナリベクトルタイルの拡張子が mvt であったり pbf であったりするのはなんでだろう に下記のようなコメントが付いていたので、今後は pbf にします。

その後、Mapzen (Tilezen) のサービスが(いったん)終了して状況が変化したので、hfu も拡張子を pbf に変更しました。

応用

fetchTile という関数を作成します。

この関数はタイル座標のオブジェクト tile を入力し、Promise オブジェクト(fetch)を返します。

fetch が成功したら mvt (pbf) 形式のファイルの変換を行ない、GeoJSON Feature の配列 features を返し、失敗したら空の配列を返します。

import Pbf from 'pbf';
import { VectorTile } from '@mapbox/vector-tile';

/**
  * @param {object} tile 
  * @returns {Promise} Promise object
  */ 
function fetchTile(tile) {
  return fetch(baseUrl + `${tile.z}/${tile.x}/${tile.y}.mvt`)
  .then(res => res.arrayBuffer())
  .then(buffer => new Pbf(buffer))
  .then(pbf => {
    const layer = new VectorTile(pbf).layers[layerName];
    const features = [];
    if (layer) {
      for (let i = 0; i < layer.length; i++) {
        const feature = layer.feature(i).toGeoJSON(tileCoord.x, tileCoord.y, tileCoord.z);
        features.push(feature);
      }
    }
    return features;
  })
  .catch(() => []);
}

d3-tile などで求めたタイル座標の組の配列 tiles から、array.map で Promise オブジェクトの配列 tasks を生成します。

Promise オブジェクトの配列 tasks を生成することで、 Promise.all(tasks) で全てのタイルの取得が終わった後に、一括で処理を行なうことができます。

const tiles = [{ x, y, z }, { x, y, z }, ..., { x, y, z }];
const tasks = tiles.map(tile => fetchTile(tile));
// => Array [ Promise, Promise, ..., Promise ]

Promise.all(tasks)
  .then(data => {
    console.log(data);
    // => Array [ features, [], features, features, [], ...]

    // 二次元配列である data を一次元の配列 features に変換
    const features = data.reduce(
      (accum, curr) => [...accum, ...curr], 
      []
    );

    return features;
    // => Array [ feature, feature, ..., feature ]
  });

国内のサッカースタジアム周辺の人口を求めるという記事では、上記の方法で人口データの入ったバイナリベクトルタイルを取得し、Turf.js を使って距離円との演算を行いました。

参照

5
6
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
6