地図は再び絵になった — タイルを、要求された瞬間に、筆で描く

2026-09-07

地図は再び絵になった — タイルを、要求された瞬間に、筆で描く

株式会社Eukarya で CTO の井上です。このたび、絵で描かれた背景地図を、リクエストのたびにその場で配信するタイルサービス Re:Earth Papers を、地理空間技術の国際カンファレンス FOSS4G 2026 Hiroshima で正式にリリースしました。タイルサービスとは、地図を小さな正方形の画像に分け、必要な範囲だけを配信する仕組みです。姉妹サービスの Re:Earth Terrain / Re:Earth Buildings と同じくオープンソースで、コードは GitHub(reearth/reearth-papers)で公開しています。

Re:Earth Papers は、地図のスタイルを「処理を箱と線でつないで表すノードグラフ」に、線・面・文字の見た目を指定する描画ルール(シンボライザ)を「筆跡を作る筆エンジン」に置き換え、タイルを 要求された瞬間に、その場で描く背景地図配信サービスです。事前に生成したタイルを配るのではなく、水彩・墨・鉛筆・木版のような絵で描かれた背景地図を、地形と同じ「オンデマンド生成」の枠組みで届けます。

本記事は、その内部の仕組みの話です。地図が再び絵になった、と言えるようになるまでに、私たちが作った物を順に紹介します。

sumi で描いた地図。OpenStreetMap のベクトルタイルを、筆エンジン hokusai がその場で墨として描いたもの
sumi で描いた地図。OpenStreetMap のベクトルタイルを、筆エンジン hokusai がその場で墨として描いたもの

Re:Earth Papers が返すもの

仕組みの前に、何を配信しているのかを軽く押さえておきます。papers.reearth.land は、さまざまなタイルセットを1つの棚に並べた画像タイル配信サービスです。無料で利用でき、APIキーも必要ありません。別のウェブサイトから直接読み込むための設定(CORS)も有効なので、そのまま MapLibre や Cesium の背景地図として使えます。

棚の中身は次のとおりです。

  • 通常の描画テーマpapers-light / papers-dark/Protomaps 系など)— OpenStreetMap のベクトルタイルを、私たちの描画エンジンで1枚の画像(ラスタ)として描いたもの
  • 絵柄テーマpaint-sumi / paint-wash / paint-pencil-sketch など)— 同じ OSM データを、筆エンジンで絵として描いたもの
  • データレイヤ — Natural Earth・Overture Maps・ESA WorldCover・NASA Black Marble・Stamen Watercolor など、オープンライセンスの地図データを、タイルのURLや範囲を記した設定ファイル(TileJSON)と、個々のタイルを直接指定する z/x/y 形式の両方で配信
棚の中身。同じ地点・同じズームで並べた 12 テーマ
棚の中身。同じ地点・同じズームで並べた 12 テーマ

Cesium から絵柄テーマを敷くコードは、これだけです。

import * as Cesium from "cesium";

const imagery = new Cesium.UrlTemplateImageryProvider({
  url: "https://papers.reearth.land/styles/paint-sumi/tile/{z}/{x}/{y}.webp",
  tileWidth: 512,
  tileHeight: 512,
  credit: "© OpenStreetMap contributors, Mapterhorn",
});

const viewer = new Cesium.Viewer("cesium");
viewer.imageryLayers.addImageryProvider(imagery);

MapLibre GL JS からも、raster ソースに TileJSON を渡すだけです。

map.addSource("papers", {
  type: "raster",
  url: "https://papers.reearth.land/styles/paint-wash/tilejson.json",
  tileSize: 512,
});
map.addLayer({ id: "papers-layer", type: "raster", source: "papers" });

これから話す仕組みは、この 2 つの短いコードの背後で動いているものです。

現在の地図スタイルは、ルールのリスト

Web で地図を描くとき、スタイルは 描画する順番に並べたレイヤのリストとして書きます。ウェブ地図の描画規則である MapLibre GL Style Spec も、Mapbox の系譜も、CartoCSS も、SLD も、書き方の細部は違えど、根っこは同じ考え方です。「この線は 2px の緑」「この面は薄いベージュ」「この名前は 12pt の太字」というように、描画ルールへ与える設定値を並べ、下から上に順番に描いていく。これが、地図をタイル配信で描くための共通言語でした。

このモデルの強みは明快で、プロパティに式を書ける ようになった点でした。ズームやフィーチャの属性で色や幅や大きさを連続的に変えられる、というのは、描画ルールの列にとって非常に大きな進歩です。MapLibre の式で使う interpolate / step / match は、そのまま地図表現の基本的な語彙になりました。

QGIS や SLD は、この延長線上に、画像の重ね合わせ方式である Porter-Duff 合成、面の境界から内側へ色を変える Shapeburst、記号をランダムに敷き詰める Random marker fill、元の形状から別の形状を作る Geometry generator、各種の描画効果(paint effects)など、さらに豊かな表現を備えています。私たちは、GIS のスタイルが表現に乏しいとは考えていません。むしろ、計算式を使える描画ルールの列としては、既に十分に強力なところまで来ています。

MapLibre のスタイルは、描画する順番に並べたレイヤの配列。下から上へ、順に重ねていく
MapLibre のスタイルは、描画する順番に並べたレイヤの配列。下から上へ、順に重ねていく

一方で、この共通言語では、絵は描けません。理由は 2 つあります。ひとつは、ある工程の出力を、名前をつけて後の工程の入力にする 仕組みがないこと。ぼかしたラスタをマスクに使い、同じラスタをブレンドの下敷きに使う、といった構造は、順序リストでは書けません。もうひとつは、筆を一度置いた跡にあたる点(dab、以下「筆点」)を重ねる筆——圧・水・混色を持った筆点を、形状に沿って連続して置く仕組み——が、従来の描画ルールには存在しないこと。

このふたつが揃わないと、絵で描かれた地図はタイル配信の中に入ってきません。

地図は、もともと絵だった

天保国絵図(周防国)を眺めていると、山ごとに筆の使い方が違うことに気づきます。ある山は水を多く含ませた筆で、稜線を柔らかく。ある山は乾いた筆で、藪を短い線で刻むように。誰かが、その山のために筆を選んでいます。地図が「情報のリスト」ではなく だった頃、これは当たり前のことでした。

天保国絵図(周防国)。山ごとに筆の使い方が違う — 山口県文書館蔵・パブリックドメイン
天保国絵図(周防国)。山ごとに筆の使い方が違う — 山口県文書館蔵・パブリックドメイン

現代のウェブ地図配信の枠組みは、この手つきを持ち込めるようには作られていません。理由は、上で触れたとおり——スタイルが「ルールのリスト」に単純化され、絵を描くための道具が、そのリストの中には入っていないからです。

例外はあります。Stamen Watercolor が 2012 年に見せたのは、ラスタタイルとして焼き込まれた 1 つのテーマとして、地図が水彩で描けること でした。この記事の後半で述べるとおり、ラスタタイルという枠自体は、絵を描くのに十分でした。足りていなかったのは、リクエストのたびに描いても、絵として成立する ためのソフトウェア側の道具です。

本記事の残りは、私たちがそこに揃えた 2 つの道具——筆エンジン hokusai と、ノードグラフレンダラ ezu——の話です。

絵を描くには、まず筆が要る — hokusai

絵を描くためのソフトウェアには、筆エンジン(ブラシエンジン)という仕組みがあります。マウスやペンタブレットの動きを受け取り、キャンバスに筆点を積み重ねて筆跡を作ります。圧力・速度・傾き・乱数といった複数の入力を、

fix15(1.0 を 32768 として小数を整数で扱う固定小数点形式。浮動小数点と違って、環境によって丸め方が変わらない)と呼ばれる数値に変換し、連続的な描画へ反映します。

Photoshop、CLIP STUDIO、Krita、MyPaint、Procreate、Painter——絵を描く人にはそれぞれ好みの筆エンジンがあります。中でも、MyPaint / Krita で使われている libmypaint は、オープンソースの筆エンジンとして最も広く使われている実装です。

私たちが必要としていたのは、この libmypaint を、ウェブ地図配信の中で動かすこと でした。そのために作ったのが、hokusai という、Rust だけで実装した筆エンジンです。

hokusai の設計上の最優先事項は、独自路線の絵作りではなく、libmypaint と画素単位で同じ結果を再現することです。同じ fix15 の数値、同じ 64×64 の赤・緑・青・透明度(RGBA)からなるタイル構造、同じ筆跡計算を使います。libmypaint 用に作られた .myb 形式のブラシデータを、変換せずそのまま読み込み、ビット単位でほぼ同じ出力を返します。

実測では、libmypaint v1.6.1 の標準ブラシ 196 個のうち 188 個(約96%)で、画素値の平均絶対差(MAD)が 0.5 以下となり、残りの 8 個も MAD 4 以下に収まっています。この一致度を継続的に検証する試験プログラム hokusai-compat をリポジトリに同梱しており、cargo xtask brush-pack-report を実行すれば、誰でも同じ計測を再現できます。

libmypaint v1.6.1 と hokusai の出力を並べたもの。hokusai-compat の回帰試験の実物
libmypaint v1.6.1 と hokusai の出力を並べたもの。hokusai-compat の回帰試験の実物

なぜ libmypaint の再実装が要るのか。理由は、ブラウザや Cloudflare Workers で実行できる WebAssembly(WASM)として動かすためです。libmypaint はネイティブの共有ライブラリで、Cloudflare Workers のような WASM 実行環境の中には持ち込めません。かといって、既存の Web Canvas の合成モードで筆を再現するのは無理があります——libmypaint の筆は、圧力・速度・傾き・乱数といった複数の入力を受け取り、fix15 の数値を使って筆点を積み重ねる専用エンジンだからです。

これで、絵を描く道具の1つ目——「筆点を打つ筆」——を、ブラウザでも、Cloudflare Workers 上の WebAssembly でも同じように使えます。次に必要なのは、その筆を、何にどう当てるか を書くための言葉です。

絵を描くもう一つの道具 — ノードグラフ、そして ezu

絵を描くソフトウェアには、筆エンジンの上に、もう1つの大きな概念があります。ノードグラフです。

Blender や Nuke、Houdini、Substance Designer など、CG・映像制作に使われる多くのソフトウェアでは、処理を箱と線でつなぐノードグラフが使われています。画を作るときの共通言語がノードグラフであることは、CG・VFX(視覚効果)の現場では、もう何十年も前からの前提です。理由は明快で、名前つきの中間結果を、複数の後段で使い回す という書き味を、順序リストでは表現しづらいからです。

この作り方は、CG の世界では プロシージャル生成(procedural generation)と呼ばれます。完成した絵そのものをデータとして持つのではなく、どう作るかの手順をグラフとして持ち、パラメータを変えて何度でも作り直せる、という考え方です。Houdini の地形や都市も、Substance Designer の素材(プロシージャルテクスチャ)も、この方法で作られています。

地図の語彙に置き換えると、これは驚くほど馴染みのある話です——スタイルはもともと、絵そのものではなく、絵の作り方の記述だからです。違いは、その手順をどこまで複雑に組めるかにあります。

Blender のジオメトリノード。上のビューポートの結果を、下のノードグラフが作っている — Simon Thommes / CC BY 4.0(トリミングして使用)
Blender のジオメトリノード。上のビューポートの結果を、下のノードグラフが作っている — Simon Thommes / CC BY 4.0(トリミングして使用)

例えば Nuke で映画の映像を合成するとき、ある描画レイヤをぼかした結果を、切り抜き範囲としても、ぼかしの下敷きとしても、色補正の対象範囲としても使うことがあります。これを順序リストで書くなら、同じぼかし処理を何度も繰り返すか、途中の結果をキャンバスに残す設計にせざるを得ません。ノードグラフなら、1つの blur ノードの結果を、3つの後続処理が使うだけです。

この考え方は、GIS の地図スタイルには入ってきませんでした。GIS スタイルは「レイヤの配列」のまま、CG・VFX は「処理グラフ」のまま、それぞれ別の分野で育ちました。Re:Earth Papers の描画は、この間を埋める試みです。担っているのは、ezu(絵図)という Rust 製の描画エンジンです。

ezu が MapLibre GL のような既存の描画エンジンと決定的に違うのは、各処理の入出力の種類が決まった、循環のない処理グラフ(DAG)としてスタイルを扱うことです。MapLibre のスタイルドキュメントを乱暴に要約すれば「レイヤの配列」でした。上から下へ順に読み、その順序どおりにキャンバスに重ねる。ezu のスタイルドキュメントは「名前つきノードのマップ」です。順序はドキュメントの側にはなく、output から各ノードの @ref をたどることで初めて立ち上がります。

{
  "nodes": {
    "bg":      { "op": "solid", "color": "#fbf6e6" },
    "water_f": { "op": "features", "layer": "water" },
    "water":   { "op": "fill-dabs", "features": "@water_f", "color": "#5876a0" },
    "soft":    { "op": "blur", "input": "@water", "sigma": 3 },
    "out":     { "op": "blend", "base": "@bg", "over": "@soft" }
  },
  "output": "@out"
}

outbgsoft の結果を使い、softwater を、waterwater_f を使います。@ で始まる値は、別のノードの結果を参照する記法です。これを出力側から逆向きに読むと、依存関係の連鎖がそのまま処理グラフになります。ezu には現時点で 85 のオペレータ があり、大きく 7 つの分類に整理されています。下の表の分類名は、それぞれドキュメントのページへのリンクになっています——オペレータごとに、適用前と適用後のプレビューを並べてあるので、何をするノードなのかは目で見るのが一番早いと思います。

分類主な役割
sourcefeatures / dem / image / graticule / point-grid地物や標高、画像を読み込む
geometrybuffer / dash / hatch / voronoi / simplify形状そのものを加工する
paintstroke / line / fill-solid / fill-dabs / stamp / text-labels / text-draw / label-placement形状を画像として描く(従来の描画ルールに相当)
rasterblend / blur / noise / warp / hillshade / gradient-linear / color-ramp / stack画像に対して加工を施す
brushbrush-file / brush-solid筆(hokusai)を読み込む
scalarexpr / math / zoomスタイル内での計算・条件分岐
util変換ユーティリティ
上のスニペットを処理グラフとして描いたもの。output から @ref を逆にたどると、依存関係の連鎖がそのままグラフになる
上のスニペットを処理グラフとして描いたもの。output から @ref を逆にたどると、依存関係の連鎖がそのままグラフになる

順序リストとノードグラフの違いは、書き味だけの違いではありません。ある工程の出力を、複数の後段が入力に取れる ようになる、という構造の違いです。海岸線を一度画像にしておき、それを切り抜き範囲としても、輪郭線としても、各地点から海岸線までの距離を表すデータの入力としても使う——順序リストでは書き換えが必要な発想が、グラフでは 1 つのノードと、その結果を使う複数の後続処理だけで表現できます。

さらに、この違いは キャッシュの粒度も変えます。ezu は各ノードの出力を、その内容から高速なハッシュ関数 xxh3 で計算した128ビットの識別子を鍵として保存します。鍵は、キャンバスの形状、タイルID、そのノードの設定と素材、入力となるノードの識別子を順につないで組み立てます。これは Merkle 構造と呼ばれる考え方で、入力が変わると、その影響を受ける後続処理の鍵だけが変わり、それ以外の結果は再利用できます。順序リストのように、1枚のキャンバスを順番に書き換える設計では、この細かさでキャッシュできません。

ノードごとに鍵を持つというこの設計は、次の節でもう一度出てきます——タイルの継ぎ目を消すために置き場所を世界座標で決めると、そのノードは鍵からタイルID を外せるようになるからです。

ezu の paint 系ノード——特に fill-dabslinestamp——は、上で紹介した hokusai を筆エンジンとして組み込み、形状から得た点列に沿って筆点を打っていきます。ブラウザで試せるデモでは、筆圧やペンの傾きに対応する端末を使うと、libmypaint のブラシがそのままブラウザで動く様子を確認できます。

タイルの継ぎ目を、世界座標で消す

タイル配信の悩ましさは、独立に描いたはずのタイルが、隣同士で違って見える ところにあります。順序リストの描画ルールでも、fill-pattern の模様の位置がタイル境界でずれることは、経験のある方が多いと思います。筆点を重ねる筆で描くと、この問題はもっと目立ちます。

ezu は、この縫い目の問題を 設計原則 で解いています。筆点の位置、ノイズ、ラベル配置に使う乱数を、タイル座標ではなく 世界座標から決めるのです。QGIS の Random marker fill にシード固定オプションがあるのに近い発想を、地球上のすべての場所に適用しています。

同じ地面を含む2枚のタイルは、同じ位置に同じ筆点を打ちます。ズーム間で切り替わっても、隣のタイルにまたがっても、紙目とハッチの位相が一致します。副次的な効果として、世界座標を基準に配置するノードは キャッシュの鍵からタイルIDを省けるようになり、隣接するタイルどうしで保存済みの結果を共有できます。

左:乱数をタイル座標から決めた場合。ハッチの位相がタイルごとに切り替わる。右:世界座標から決めた場合。境界をまたいで一致する
左:乱数をタイル座標から決めた場合。ハッチの位相がタイルごとに切り替わる。右:世界座標から決めた場合。境界をまたいで一致する

ezu serve の、結果を確認しながら編集できる画面でタイル境界の格子を表示すると、これは目視でも確かめられます。筆点は境界をまたいで途切れず、ラベルは二重に出ることがなく、陰影も階段状になりません。同じ入力なら必ず同じタイルを返す処理として書くというのが、ここでの合言葉です。

既存の MapLibre スタイルは、そのまま — ラベル配置まで

この仕組みの変更は、既存の MapLibre スタイル資産と切り離す方向には進めませんでした。私たちは MapLibre との連続性を、設計上の第一制約 に置いています。

まず、式の書き方は完全に MapLibre と同じです。ezu の各ノードで、地物ごとに値を変えたい場所は、すべて *-expr フィールドになっています。ここに MapLibre の式をそのまま書けます。

"landuse": {
  "op": "fill-solid",
  "features": "@landuse_f",
  "fill-expr": ["match", ["get", "kind"],
                "park", "#a6c084",
                "forest", "#8fae74",
                "#e8e4d8"]
}

この式を評価するのは、私たちが別リポジトリ reearth/maplibre-expr-rs として公開している、Rust だけで実装した式の解析・評価エンジンです。maplibre-expr-rs は、MapLibre 公式の試験データに対して100%仕様どおりの結果を返します。型検査の規則に加え、collator、地理関数、旧形式のフィルターの変換まで含め、同じ式から同じ結果を得られます。

次に、既存の MapLibre スタイルを ezu の処理グラフへ変換するツール ezu translate があります。順番に並んだレイヤは、ezu 上では画像合成を担う blend ノードの連なりへ変換されます。下から上へ描く方式(painter's algorithm)の各段階を blend と捉え直せば、順序リストはグラフ表現の特殊な形にすぎません。その考え方を、そのまま実装しています。

どこまで一致するかも、文書で数値を確認できます。68 レイヤの Protomaps テーマを ezu で描き、maplibre-gl-js の出力と画素単位で比較したとき、画像の構造的な類似度を表す SSIM は 0.80〜0.87に収まっています。文字レイヤ 11 枚を含み、149 ノードのグラフに膨らんだ後の数字です。SSIM は完全一致が 1.0 で、1 に近いほど構造が揃っていることを表します。残りの差の大半は、アンチエイリアスのかかり方と、このあと述べるラベル配置の違いから来ています——地物の位置や色そのものがずれているわけではありません。

同じ z14 のタイルを、maplibre-gl-js(GPU)と ezu(CPU)で描いたもの。手で調整したところは一つもない
同じ z14 のタイルを、maplibre-gl-js(GPU)と ezu(CPU)で描いたもの。手で調整したところは一つもない

つまり、Re:Earth Papers の通常の描画テーマは、MapLibre で開発したスタイルドキュメントが、そのままエッジで走っている状態です。既存の資産を捨ててはいません。

ラベルは、8 近傍を見ながら「合意」で置く

MapLibre スタイルの中で、タイル配信と最も相性が悪いのが ラベル配置 です。MapLibre は、ラベルを表示フレームごとに置き直します。画面に表示されている範囲の中心に近いタイルを優先し、ラベルを徐々に表示・非表示にします。text-overlap: cooperative は隣接するラベルを避けるために動的に振る舞います。この設計は、操作できる地図クライアントには合っていますが、タイル配信という文脈では成立しません——同じタイルを 2 つのクライアントが要求したとき、返る絵が違ってはいけないからです。

ezu のラベル配置は、次のように動きます。

  1. 候補を、周囲 8 タイルから集める。あるタイルの中に見える文字ラベルは、隣のタイルの地物に由来していることがあります。ezu の label-placement は、対象タイルと周囲 8 タイルから候補を集め、重複を取り除きます。
  2. 順序を決める。MapLibre と同じく、symbol-sort-key の昇順に並べ、同点の場合はタイル内の地物の順序で決めます。
  3. 置ける候補から順に配置する。全レイヤの候補を、全レイヤで共有する1つの衝突判定表に対して順番に置きます。あるレイヤの地点情報(POI)が、下のレイヤの道路名を押しのけるという MapLibre の振る舞いも、この共有の衝突判定表によって再現できます。
候補集約 → 並べ替え → 順に配置。入力集合と順序が両側で同じなら、隣り合うタイルは同じ判定に到達する
候補集約 → 並べ替え → 順に配置。入力集合と順序が両側で同じなら、隣り合うタイルは同じ判定に到達する

順序が全体で確定していて、入力集合がタイル境界の両側から見て同じである以上、2 つのタイルは同じ判定に到達します。ラベルが描かれるか捨てられるかは、両側の合意で決まります。

引き換えに落としたものは、正直に書いておきます。画面に表示されている範囲の中心を優先する仕組みはありません。表示フレームごとのフェードもありません。text-overlap: cooperativenever として扱います。動的なラベル配置を望む方には物足りない場面もあると思いますが、同じ入力なら必ず同じタイルを返すことの見返りとして、この交換を選びました。

Cloudflare Workers 上、要求のたびに — 姉妹サービスと同じ枠

ここまで話してきた ezu と hokusai は、実行時には Cloudflare Workers の中で WebAssembly(WASM)として動きます。Papers は、Re:Earth が開発してきたオンデマンドのタイル配信基盤を使う3つ目のサービスで、次の 2 つと同じ骨格を共有しています。

  • Re:Earth Terrain — Mapterhorn の数値標高モデル(DEM)から、地形メッシュ(quantized mesh / Terrain-RGB / Terrarium)を、利用者に近い配信拠点(CDNのエッジ)で組み立てて返します。詳細は 事前生成をやめた地形タイル を。
  • Re:Earth Buildings — Overture Maps の建物フットプリント(約 26 億棟)から、Re:Earth Terrain の地面標高を焼き込んで、3D Tiles をその場で組み立てて返します。
  • Re:Earth Papers — 絵で描かれた背景地図と通常の背景地図を、リクエストのたびに ezu と hokusai で描いて返します(本記事)。
地形・建物・描画。タイルを構成する 3 つの重い要素を、順にリクエストの中へ置いてきた
地形・建物・描画。タイルを構成する 3 つの重い要素を、順にリクエストの中へ置いてきた

この 3 つを並べたときに見えてくるのは、地形・建物・描画 という、地図タイルを構成する 3 つの重い要素を、順にリクエストの中に置いてきた、という道筋です。Terrain と Buildings で「地形と建物」をリクエストの中に置き終えた後で、Papers が「描画」を同じ場所に置きました。

3 層のキャッシュ

「リクエストのたびに描く」ためには、当然ながらキャッシュが要ります。タイルは、次の 3 層でキャッシュしています。

  1. ブラウザCache-Control: immutable, max-age=1y)— 利用者の端末内で完結する層。
  2. Cloudflare のエッジキャッシュ(Cache API、拠点ごと)— リクエスト URL に ?__v= を付けた鍵で保存し、利用者に近い配信拠点(PoP)から返す層。
  3. Cloudflare R2(保存先はバージョン、データの日付、テーマ、言語、z/x/y から決める)— 世界全体で1つのコピーを保持する最下層。Workers の実行環境が破棄・再作成されても、別の配信拠点から要求されても、ここから復元できる。
ブラウザ → エッジ(拠点ごと)→ R2(世界に 1 コピー)。全部外れたときだけ、その場で描く
ブラウザ → エッジ(拠点ごと)→ R2(世界に 1 コピー)。全部外れたときだけ、その場で描く

要点は、保存済みの内容を消すのではなく、バージョンを変えて新しい保存先へ切り替えることです。地図表現が変わったら STYLE_VERSION、描画エンジンの更新で絵が変わったら EZU_RECIPE_VERSION、上流データの月次版が差し替わったら mirrorDate——それぞれを独立して変更できます。古いタイルは削除せず、単に 到達不能 な状態で R2 に残ります。

未知のクエリ文字列(?cb=<ランダム> のような、キャッシュを回避するための無作為な値)は 無視されます。同じ絵を持つ1つのタイルに複数の URL が発生することを避ける設計です。

実測 — 通常の地図と絵柄では桁が違う

私たちが実運用に載せた後の実測値です。東京都市域から選んだ z=14〜15 のタイルへ、papers.reearth.land を通じて直接アクセスした結果を挙げます。

初回描画(キャッシュに入っていないタイル):

  • papers-light / papers-dark / protomaps-* — 約 0.9〜2.5 秒(本文では 1 秒前後が多い)
  • paint-pencil-sketch — 約 2.0〜2.7 秒
  • paint-voltage — 約 3.2〜7.0 秒
  • paint-pixel-art — 約 4.5〜7.3 秒
  • paint-sumi — 約 8.8〜13.9 秒
  • paint-wash — 約 10.5〜11.9 秒

エッジキャッシュに保存済みの場合(一度描いたタイルへの2回目のリクエスト):

  • 全テーマ共通で 約 45〜55 ミリ秒
初回描画の実測(対数軸)。赤い縦線がエッジキャッシュに保存済みの場合 — 初回は桁違い、以降は全テーマ共通
初回描画の実測(対数軸)。赤い縦線がエッジキャッシュに保存済みの場合 — 初回は桁違い、以降は全テーマ共通

数字を並べると、絵柄テーマの初回は、.png の背景地図に比べて 1〜2 桁遅い ことがはっきりわかります。これは、hokusai が実際に筆点を打っている時間です。ezu の文書でも "Painterly styles cost more than translated basemaps"(絵柄スタイルは変換された背景地図より描画に時間がかかる)と明記されています。

この性能特性に合わせて、キャッシュを設計しています。「初回は 5〜10 秒級、以降は 50 ミリ秒」なら、R2 に世界共通のキャッシュを置く効果は大きくなります。一度描けば、世界中で共有される。逆に、絵柄テーマを事前生成しておくべきかと問えば、答えは NO です。複数の絵柄があり、テーマを変更するたびに全世界のタイルを再生成しても、実際にはほとんど要求されない大量のタイルが残り、費用の多くが無駄になります。

何にかかっているかの内訳

参考までに、開発機(Apple M1)で ezu 単体を回したときの、内訳の実測です。68 レイヤの Protomaps テーマを 512 px で描いたとき、処理の種類ごとの実行時間の割合は以下のようになります。

処理実行時間の割合
stroke(道路線の縁取り、123 ノード)69.5%
stack(合成)12.4%
fill-solid9.8%
text-labelstext-drawlabel-placement5.2%
処理の種類ごとの実行時間の割合。道路線の縁取りだけで約 7 割、文字は 5% 程度
処理の種類ごとの実行時間の割合。道路線の縁取りだけで約 7 割、文字は 5% 程度

意外なのは、文字と道路線の縁取りにかかる時間の比が、想像と逆なところです。地図描画では文字が重いというのが経験的な常識ですが、実測すると 道路線の縁取りだけで約7割を占めていました。縁取り用の stroke は1レイヤあたり複数のノードに分かれ(下地・本体・トンネル用など)、それが道路の種類ごとに繰り返されます。文字は5%程度に収まる一方、縁取りは重点的に最適化しないと減りません。

これは、ezu の記事というよりは MapLibre と同じ描画モデル一般の話です。参考までに、ezu は Rust の並列処理ライブラリ Rayon を使った 並列評価器を備えています。6つのノードを並行して処理できる水彩スタイルでは、実行時間が 6.3 秒から 1.3 秒に短縮されるといった測定結果を、文書で公開しています。

地図は、再び絵になった

タイルが事前生成の前提を捨てたことで、あなたが求めた、その瞬間に、地図はまた絵になれます。スタイルはノードグラフで書けて、筆は libmypaint 互換として作り直せて、描画はリクエストの中で動きます。同じテーマを、同じ地面に対して、いつ描き直しても同じ絵が返ってきます。

Re:Earth Papers は、絵図の頃にあった手つきを、タイル配信のプロトコルに合流させる試みです。使い方は変わりません——TileJSON か z/x/y の URL を、いつもの地図クライアントに渡すだけです。ぜひ、ブラウザで使えるビューア(papers.reearth.land)で、さまざまなテーマを眺めてみてください。テーマは、これからも増やしていく予定です。

同じ地面を、違う筆で。左から paint-sumi / paint-wash / paint-pencil-sketch / paint-voltage / paint-pixel-art
同じ地面を、違う筆で。左から paint-sumi / paint-wash / paint-pencil-sketch / paint-voltage / paint-pixel-art

リファレンス

Japanese

Eukaryaでは様々な職種で採用を行っています!OSSにコントリビュートしていただける皆様からの応募をお待ちしております!

Eukarya 採用ページ

Eukarya is hiring for various positions! We are looking forward to your application from everyone who can contribute to OSS!

Eukarya Careers

Eukaryaは、Re:Earthと呼ばれるWebGISのSaaSの開発運営・研究開発を行っています。Web上で3Dを含むGIS(地図アプリの公開、データ管理、データ変換等)に関するあらゆる業務を完結できることを目指しています。ソースコードはほとんどOSSとしてGitHubで公開されています。

Re:Earth / ➔ Eukarya / ➔ note / ➔ GitHub

Eukarya is developing and operating a WebGIS SaaS called Re:Earth. We aim to complete all GIS-related tasks including 3D (such as publishing map applications, data management, and data conversion) on the web. Most of the source code is published on GitHub as OSS.

Re:Earth / ➔ Eukarya / ➔ Medium / ➔ GitHub