JavaScript
Node
geo

node でベクトルタイルをファイルシステムに展開

More than 1 year has passed since last update.

node でタイルを展開できないとおかしい

  1. gh:openmaptiles/openmaptilesgh:mapbox/tippecanoe で作成したバイナリベクトルタイルは、mbtiles という形式のファイルの中に格納されますが、このファイルをホストするためには、CPU time を使用する TileServer GL light のようなソフトウェアを使う必要があります。スタティックなストレージによりバイナルベクトルタイルを提供するためには、mbtiles を生のバイナリベクトルタイル群に展開する必要があります。
  2. 上記の展開を行うための Ruby スクリプトとして、fan-out.rb (gh:hfu/jig-fan-out) を使ってきましたが、ベクトルタイル Advent Calendar の3日目、ベクトルタイルに関して私が選択した技術体系で、node を使うと宣言したのですから、node でタイルを展開できないとおかしいです。

ということで、

fan-out を node で再実装してみました

雑なコードで恐縮ですが、https://github.com/hfu/fan-out-js/blob/master/fan-out.js にあるとおりです。

const fs = require('fs-extra')
const Database = require('better-sqlite3')
const zlib = require('zlib')

const report = function(c, count, path) {
  if(c === count || c % 1000 === 0) {
    console.log(`${c} of ${count} (${Math.round(c * 100.0 / count)}%) ${path}`)
  }
}
const run = function(mbtiles, dir) {
  const db = new Database(mbtiles, {readonly: true})
  const count = db.prepare('SELECT count(*) FROM tiles').get()['count(*)']
  let c = 0
  for(const r of db.prepare('SELECT * FROM tiles').iterate()) {
    const buf = zlib.unzipSync(r.tile_data)
    const z = r.zoom_level
    const x = r.tile_column
    const y = (1 << z) - r.tile_row - 1
    fs.mkdirsSync(`${dir}/${z}/${x}`)
    fs.writeFileSync(`${dir}/${z}/${x}/${y}.mvt`)
    report(++c, count, `${dir}/${z}/${x}/${y}.mvt`)
  }
  db.close
}

if(process.argv.length == 4) {
  run(process.argv[2], process.argv[3])
} else {
  console.log("usage: node fan-out {tiles.mbtiles} {tiles_dir}")
}

歴史的経緯により y の方向が逆向きに格納されているのを戻す必要があるのと、タイルデータが gzip 圧縮されているのを戻す必要があるところがポイントです。

同期処理で書くことの妥当性

今回のコードは同期処理で書いています。これは、gzip の書き出しは CPU を、ファイルの書き出しはファイルシステムを占有する傾向がある処理なので、非同期にしても遊びを有効利用することが少なく、かえってキューが溜まって輻輳してしまう傾向が見られたからです。

ネットワークからの転送待ちが大きく、コネクションを複数本活用することで複数倍の加速が期待された qdltc と違って、非同期処理にするメリットが少ないと見たので、単純さのために同期処理で書いています。