すべてを球の上に描く、ブラウザだけで動く地図エンジン(自前の地図サーバーを持たない)
@ortho-earth/core · @ortho-earth/globe · WebGPU と WebGL2 · 実スケールの地形 · worker の流れ作業
ortho-map(v1:d3 の正射投影・層ごとに worker 1 本・WebGL2 のみ)の説明を置き換えたものです。
v1 は 2026 年 9 月に撤去しました。正射の地球・OffscreenCanvas・GeoPBF/AltPBF/Gint という考え方は引き継ぎ、実装は引き継いでいません。
部品は下にあるものほど硬く作っています(検定の関門・型定義・版で守る)。上のアプリは殻で、自由に変えてかまいません。 新しい仕事は、まず「どの層か」を言ってから書きます。
createGlobe() — 起動・世界帯・地域に依らないガジェット・SDK の型・関門
全体をつなぎとめる決まりは一つ:core と globe は国の名前を書かない。
機械の関門(verify:regionless)が globe のコードの地域の語を数え、増えたら落とします。
import の向きは一方通行で、japan → globe → core → ephem と japan → jp → core。globe と jp は互いを import しません。
両者が出会うのは、アプリが渡す申告の中だけです。
メインスレッドが持つのはカメラ・入力・DOM だけです。解読・組み立て・描画はすべて worker で動きます。
worker はすべて一つの入口(worker.ts)から起動し、Worker の name で役割を指名します。
そのためバンドラが作る worker のビルドは一つで、共有する部品(GeoPBF の核・loaders.gl・シェーダ)は worker の数だけ複製されません。
draw {cam} を送る| 役割 | すること |
|---|---|
render | canvas の持ち主。地球・地形・基図のシーン・画像タイル・gint・注記・オーバーレイを、一つのフレーム・一つのカメラで描く |
ortho:tile / ortho:scene | タイルの取得と組み立て/シーンの結合(基図の流れ作業) |
mesh / meshdecoder | 大量の 3D メッシュ(建物)を流す — 解読・重複除去・接地・LOD・OPFS/IndexedDB への保存 |
model | glTF/GLB・押し出し・3D Tiles と I3S の中身。日影と可視域の計算も |
gintbake | gint の層を焼く(LOD の順位・辺)— 描画スレッドの外で |
ortho:height | 標高タイルの解読 |
rastertiles / imagequad | 手元の GeoPackage/MBTiles、または四隅で貼った画像から、画像タイルを配る |
columnar | 列チャンク層(GeoParquet・チャンクに切った GeoPBF)を読んで詰める(map.addColumnar) |
vtdraw / vtextrude | vector source の MapLibre の fill/line/circle/symbol と fill-extrusion |
iiif / copc | 基準点で合わせた IIIF の地図画像をタイルに焼く/COPC の点群を Range で読む |
decoder:* / encoder:* / geopbf:* | GeoPBF の形式変換(落としたファイル・書き出し) |
地域パックは自前の入口から自分の worker を起こします(日本の統計 worker=@ortho-earth/jp の estat-worker.ts)。ホストはそれを知りません。
crossOriginIsolated は必須ではありません。あれば GeoPBF のバッファがコピー無しで worker へ渡り(SharedArrayBuffer)、
無ければ一度だけコピーされます。どちらでも全機能が動くことを関門(verify:nocoi)で確かめています。
core には同じ顔(set・draw・dispose)を持つ描画器が二つあります。WebGPU 版と WebGL2 版です。
起動時にどちらかを選びます。
| 条件 | 土台 |
|---|---|
?gl2=1 | WebGL2(手動の逃げ道) |
navigator.gpu が無い | WebGL2 に直行 |
| Android | WebGL2(一部のモバイル GPU ドライバで、黙って画面が黒くなる不具合があるため) |
Safari/WebKit で、glOverlay: true を宣言した頁 | WebGL2(そこでは WebGPU の上に重ねた WebGL2 のオーバーレイが合成されない) |
| このタブで前回 WebGPU の起動に失敗した | WebGL2(1 回目は次の起動だけ、2 回目からはタブを閉じるまで。?gpu=1 で再試行) |
| それ以外 | WebGPU |
WebGPU には三枚の落ち網があります。初期化に失敗したら worker の中で WebGL2 へ切り替える。 黙った失敗(初期化は通ったのに画面に何も出ない)は最初のフレームを読み戻して見つける。20 秒たってもフレームが来なければ WebGL2 で起動し直す。
画質のための仕組みが二つあります。遷移時の AA(WebGPU。WebGL2 の AA はコンテキスト生成時に固定):カメラが動いている間は MSAA 1× で描き、 約 0.5 秒止まったら 4× で一枚だけ描く。動的解像度:フレームが重くなると canvas の解像度を段階的に下げ、線の太さは画面上で変えない(両方の土台)。
文字も GPU で(2.1 から):地名・道路名・アイコン・道路標識は SDF のグリフアトラスに焼き、WebGPU が地図と同じフレームで描きます。
真上から見ている間は style の層の位置に描くので、注記の上に線を置く style も MapLibre と同じ重なりになります。
WebGL2 では自動で Canvas2D の文字に戻り、?text=2d を付ければ WebGPU でも Canvas2D を選べます。
カメラは小さな素のオブジェクト(center・zoom・pitch・bearing)で、投影は 4×4 の行列一つです。
正射と透視は一続き:正射は、目を無限に遠ざけた透視です。投影は壁ではなくスライダーです。
ellipsoid の設定や ?ell=1/?ell=0 で変えられます。距離や面積の計測は常に楕円体で行います。flyTo・easeTo・fitBounds)は時間で評価する計画です。台本の再生は 1 歩 35 ms を上限とする仮想時計で進むので、遅い機械で録画してもずれません。
視点は URL にあります — #zoom/lat/lon[/45t][/-30r][/15roll][/l=…][/c=テーマ][/t=UTC][/s=速さ]。アドレス欄はいつでも「いま見ているもの」へのリンクです。
標高は誇張せずに描きます。高さは AltPBF から:三つの大きさ(1°・10°・90°)のタイルを worker で解読し、 二つの標高アトラスに詰めます — カメラの周りの細かいセルの近景アトラスと、地平線までの粗いセルの遠景アトラス。 シェーダは両方から一続きの高さの場を読みます。
raster-dem(Terrarium・Mapbox・地理院の形式)で、組み込みの標高を置き換えられます:map.setTerrain(…)。おおよそ z8 より下では、地球儀が自分で世界を描きます — Natural Earth 10 m の国・湖・川・海洋境界と、 全球の標高と気候の場から毎フレーム計算する段彩(画像タイルは一枚もダウンロードしない)。世界のデータが担えるのは正直ここまでです。
そこから上は地域が引き継ぎます。入場するズームは地域の申告が決めます(日本は z6.5 から地理院のベクタ基図)。
ベクタタイルは tile と scene の worker を通ります。傾けた 3D の視点では、ズームアウトしても細かいタイルが地面を覆えている間は、粗いタイルに差し替えません。
タイルの形式は一つの登録簿に載るプラグインです。MVT は同梱、MLT(MapLibre Tile)は @ortho-earth/tile-formats が持ち、
ビルド時に #tile-formats を @ortho-earth/core/tileformats-mlt(登録する側)へ alias すると使えます。解読器は最初の MLT タイルで読まれるので、MLT に出会わない地図は何も払いません。
vector source は MapLibre と同じく "encoding": "mlt" と書き、PMTiles はヘッダで形式が分かります。
画像タイル(XYZ・WMS・WMTS・PMTiles・手元の GeoPackage/MBTiles・四隅で貼る一枚の画像)は、まず地面のアトラスに合成し、
地形のシェーダがそのアトラスを画素ごとに引きます。だから画像は山の上に平らに乗るのではなく、斜面に沿います。
MapLibre の style.json も同じ流れに変換して読みます(map.setStyle)。
タイルは描くため、Gint は知るためにあります。Gint の層は GeoPBF の幾何を丸ごと GPU に持つので、 どの地物も選び・強調し・表と突き合わせられます。
Gint の強さは、最初の 1 枚の前に一周の下ごしらえ(共有弧・順位・度アンカー・焼き)を払って得ています。見るだけのファイルには別の道があります
(#90)。map.addColumnar(src) は GeoParquet の row group、
または空間順に切った GeoPBF のチャンクを worker で GPU 向けの配列(チャンク原点相対の単位球 xyz・earcut の三角形・格子に丸めた 2〜3 段の LOD)に詰め、
地球と同じフレームのオーバーレイで描きます。属性は列のまま。paint と filter(Gint と同じ語彙)は worker が列から Gint と同じ fid 表に評価するので、
色替えで幾何は作り直しません。落とした .parquet は常にこの道、.geopbf/.fgb は columnarBytes(4 MB)を超えたらこの道です。ただし読み手が先頭チャンクで測る
meta.share が「長い共有弧の塗り分け」(admin1・行政地図)を示す時は、それを得意とする Gint に残します。塗りは頂点ごとに地形に沿い、シーンの深度で隠れます。
共有境界は 2 回描かれ、編集は Gint に戻します。
一つのメッシュの核が、あらゆる 3D データを流します。核は建物がどの国のものかを知りません。地域が申告するのはデータの在り処だけです。
| データ | 入り方 |
|---|---|
| 都市の建物(日本の PLATEAU・オランダの 3D Basisvoorziening(PDOK)) | 地域パックが申告する区ごとの読み込み |
| 任意の 3D Tiles | map.add3DTiles(url) — 画面上の誤差で辿る・b3dm/i3dm/pnts/cmpt/glb |
| I3S のシーンレイヤ | map.addI3S(url) — loaders.gl で解読 |
| glTF/GLB の模型 | ファイルを落とす・?g= のリンク・map.model(src, { at, heading, scale, slot })。slot ごとに 1 体=複数を並べられる。手綱で置いた後に動かせる(setLngLat / setHeading / setScale / setAltitude=頂点は焼き直さず束の剛体変換で・接地は動かした先の地面)。gizmo(true) で移動・回転・倍率の取っ手=落とした模型には最初から出る |
道はどれも同じです:解読(Draco を含む)→ 地心座標 → 正射の球 → 重複面の除去 → 地形への接地 → LOD → 原点相対の頂点 → 基図の平らな建物の輪郭を下で隠す被覆マスク。 解読した区は GPU がそのまま食える形で OPFS と IndexedDB に置くので、二度目はディスクから GPU へ直行します。
おおよそ z5 より下では空が見えます:実在の星・惑星と月・星座・黄道と天の赤道・夜の側。さらに引くと、実スケールの太陽系へ入っていきます。
これらはすべて一つの時計に従います(map.clock・@ortho-earth/ephem — 太陽系アプリと同じ時計)。
既定は実時間で、止める・早送り・巻き戻し・1800〜2049 年の任意の時刻に合わせることができます。
太陽直下点と恒星時は両アプリで同じ式から出すので、夜の側・星・人工衛星はいつも一致します。
時計は render worker に一つの基準 {sim, wall, rate} として渡り、状態が変わった時にだけ送られます。
import { createGlobe } from "@ortho-earth/globe";
const map = await createGlobe({ target: "#map", view: "#3/20/140" });
map.flyTo({ center: [139.767, 35.681], zoom: 14, pitch: 60 });
| 分野 | 例 |
|---|---|
| カメラ | jumpTo · easeTo · flyTo · fitBounds · cameraForBounds · setMaxBounds |
| スタイルと層 | setStyle · addSource / addLayer · setPaintProperty · setFilter · queryRenderedFeatures |
| 3D と地形 | add3DTiles · addI3S · setTerrain · 押し出し · 模型 |
| 解析 | sunShadow · viewshed · lineOfSight · 断面 · 計測 |
| 時間 | map.clock · map.on("time") |
| 拡張 | map.overlay(module) — 自前の canvas を render worker の中で同じフレーム・同じカメラ・api.time 付きで描く · addProtocol · transformRequest · Marker / Popup |
API の形は、できる所は MapLibre に合わせています。既存のスタイルや手癖がそのまま使えます。
上のすべては、公開パッケージそのものから頁を起動する関門で検めています — 検めたものが、インストールするものです。
2.0 からはライブラリが strict な TypeScript で書かれ、型定義も同梱しています(npm にはコンパイル済みの .js と .d.ts)。1.x の互換層は外しました — MIGRATION.md を参照。