deck.glでの拡大地図表示: TileLayerを拡張して実装する

deck.glでの拡大地図表示: TileLayerを拡張して実装する

deck.glは、WebGLを活用して地図やデータ視覚化を行う強力なツールです。 TileLayerを使うことでXYZ形式の地図タイルを簡単に表示できますが、地図タイルの最大ズームレベル以上に拡大して表示する標準機能はありません。

この記事では、地理院タイルを用いた地図表示で、ズームレベル18以上に拡大するための方法を紹介します。

背景・課題

私たちが作成した「 文化財マップ 」では、地理院タイルを使用して衛星画像を表示しています。

地理院タイルは最大ズームレベル18までしか提供されていませんが、今回の要件ではそれ以上に拡大表示を行う必要がありました。 例えば、特定の建物や施設の表示を行う際、1棟1棟の区別をするにはズームレベル19や20での表示が必要です。 この問題を解決するため、TileLayerの拡張によって拡大表示を実現します。

元のコード(deck.gl + React)

import DeckGL from '@deck.gl/react';
import { TileLayer } from "@deck.gl/geo-layers";
import { BitmapLayer } from "@deck.gl/layers";

export const Map = () => {
    const tileLayer = new TileLayer({
        id: "TileLayer",
        data: CHIRIIN_URL,
        minZoom: 5,
        maxZoom: 20, // z=19, 20のタイルは存在しない
        tileSize: 256,
        renderSubLayers: props => new BitmapLayer(props, {
            data: undefined,
            image: props.data,
            bounds: (({ west, south, east, north }: any) => [west, south, east, north])(props.tile.bbox),
        }),
    });
    return (
        <DeckGL layers={[tileLayer]} />
    )
}

どのように拡大表示するか

deck.glでは、zoomOffsetというプロパティを利用してタイルのズームレベルを調整することができます。通常、zoomレベルに応じて適切な解像度のタイルが読み込まれますが、zoomOffsetを使うことでその挙動を変更できます。

  • zoomOffset = 0:標準のズームレベル(例えばzoom 10の時にz=10のタイルを読み込み)
  • zoomOffset = 1:1段階高い解像度のタイルを使用(z=11のタイルを使用し、zoom 10で表示)
  • zoomOffset = -1:低解像度のタイルを使用(z=9のタイルをzoom 10で表示)

これを利用し、zoom 18以上の拡大表示を以下のように設定します:

  • zoomが18以下 → zoomOffset = 0
  • 18 < zoom <= 19 → zoomOffset = -1
  • 19 < zoom → zoomOffset = -2

実装手順:TileLayerのカスタマイズ

実際のコードでTileLayerをどのように拡張するかを見ていきます。 ここでは、deck.glのTileLayerをオーバーライドし、ズームレベルに応じたzoomOffsetを自動的に調整するクラスを作成します。

import DeckGL from '@deck.gl/react';
import { TileLayer } from "@deck.gl/geo-layers";
import { BitmapLayer } from "@deck.gl/layers";

class ZoomRangeTileLayer extends TileLayer {
    static layerName = "ZoomRangeTileLayer";
    updateState({ props, oldProps, context, changeFlags }: UpdateParameters<this>) {
        if (changeFlags.viewportChanged) {
            if (context.viewport.zoom > 19) {
                props = { ...props, minZoom: 20, maxZoom: 20, zoomOffset: -2 };
            } else if (context.viewport.zoom > 18) {
                props = { ...props, minZoom: 19, maxZoom: 19, zoomOffset: -1 };
            } else {
                props = { ...props, minZoom: 5, maxZoom: 18, zoomOffset: 0 };
            }
        }
        super.updateState({ props, oldProps, context, changeFlags });
    }
}

export const Map = () => {
    const layer = new ZoomRangeTileLayer({
        id: "ZoomRangeTileLayer",
        data: CHIRIIN_URL,
        minZoom: 5,
        maxZoom: 18,// Initial stateに応じる
        tileSize: 256,
        renderSubLayers: props => new BitmapLayer(props, {
            data: undefined,
            image: props.data,
            bounds: (({ west, south, east, north }: any) => [west, south, east, north])(props.tile.bbox),
        }),
    });
    return (
        <DeckGL layers={[layer]} />
    )
}

コード解説

TileLayerを継承し、updateStateメソッドでズームレベルに応じたzoomOffsetを設定しています。このメソッドではcontext.viewport.zoomを使用して現在のズームレベルを取得し、それに応じてプロパティを変更しています。

また、deck.glにおいてズームレベルは18, 19, 20などの整数ではなく、連続的に小数で与えられます。そのためズームレベルの条件分岐は不等号で表しています。

終わりに

TileLayerのカスタマイズによって、地理院タイルのズームレベル18以上の拡大表示を実現しました。

今回のコードでは、特定のズームレベルに対する条件分岐を導入しましたが、今後は動的にズームレベルを管理するさらなる拡張が可能です。 deck.glを使った高度な地図表示を検討している方にとって、この記事がお役に立てば幸いです。

今回のコードを利用して作成した 文化財マップ もよろしくお願いします。