2020-11-07

leafletのタイルURLをAsyncで設定したい

TileLayerのURLとして指定できる値は基本的にカーリーブラケットでタイル位置を表現できる形式のURLのみなので、IndexedDBにBlobをつっこんで非同期に値を設定することができない。ファイルが一個だけなら取得後にTileLayerを作ることでなんとでもなるが、タイル画像だとルールに基づいた下記のようなURLを渡す必要がある。

https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png

Leaflet.functionaltilelayerみたいなものはあるが現行のleafletではうまくうごかなかった。そんなにコード量もないので、leafletのGridLayerをextendして

L.GridLayer.Functional = L.GridLayer.extend({
  _tileFunction: null,

  initialize: function (func, opts) {
    this._tileFunction = func
    L.GridLayer.prototype.initialize.call(this, null, opts)
  },

  createTile: function (tilePoint, done) {
    const img = L.DomUtil.create('img', 'leaflet-tile')

    const map = this._map
    const crs = map.options.crs
    const tileSize = this.options.tileSize
    const zoom = tilePoint.z
    const nwPoint = tilePoint.multiplyBy(tileSize)
    const sePoint = nwPoint.add(new L.Point(tileSize, tileSize))
    const nw = crs.project(map.unproject(nwPoint, zoom))
    const se = crs.project(map.unproject(sePoint, zoom))

    const view = {
      bbox: [nw.x, se.y, se.x, nw.y].join(','),
      width: tileSize,
      height: tileSize,
      zoom: zoom,
      tile: {
        row: this.options.tms ? this._tileNumBounds.max.y - tilePoint.y : tilePoint.y,
        column: tilePoint.x
      }
    }

    this._tileFunction(view)
      .then(url => {
        img.src = url
        done(null, img)
      })

    return img
  }
})

L.gridLayer.functional = function (gridFunction, options) {
  return new L.GridLayer.Functional(gridFunction, options)
}

というものを作って適当にimportすれば

L.gridLayer.functional(async view => {
  const url = 'http://example.com/tiles/{z}/{y}/{x}.jpg'.replace('{z}/{y}/{x}', `${view.zoom}/${view.tile.row}/${view.tile.column}`)
  return await this.someGoodFunction(url)
}).addTo(this.map)

という感じで呼び出すことができる。