やり方
近づいてもくっきり見える地球のタイル
地球に大きく近づいてもくっきり見えるように、小さな地図タイルの山 (ピラミッド) を使います。NASA の実寸の地図 (1 ピクセル 500 m) から作り、デモと一緒に配信し、npm パッケージには入れません。
パッケージの画像 (dist/textures/) は横 4096 ピクセルまでです。そのため、リアルな地球は 3 倍ほどに近づくとぼやけます。1 枚の大きな画像では解決しません。16384 × 8192 の画像は、スマホのグラフィックスメモリで約 700 MB になります。そこで、タイルのピラミッドを使います。地図を 512 ピクセルの四角に切り、いくつかの大きさで用意します。画面は、見える分のタイルだけを、必要な大きさで読み込みます。
このページでは、タイルの作り方と置き場所を説明します。パッケージにタイルは入っていません。入っているのは、読み込む部分と、タイルの場所を指定する tiles オプションだけです。
ピラミッドの中身
地図はどれも 2:1 の正距円筒図法です。横が経度 -180 から 180、縦が緯度 90 から -90 です。レベル L の横幅は 1024 × 2^L ピクセルで、512 ピクセルの WebP タイルが横に 2^(L+1) 枚、縦に 2^L 枚並びます。列 0 が -180 度、行 0 が 90 度です。タイルは <layer>/<level>/<column>/<row>.webp で、manifest.json に、すべての層・レベル・タイル数・サイズが書いてあります。
| 層 | 元の画像 | レベル | 最大レベル |
|---|---|---|---|
day | NASA の Blue Marble の次世代版 (地形と海底地形つき)、2004 年 9 月 (day-*.webp と同じ月): レベル 0 から 4 は 21600 × 10800 の画像から、レベル 5 は実寸の 500 m の地図 (86400 × 43200、8 枚のパネル) から | 0 から 5 | 32768 × 16384 |
night | NASA の Black Marble の 2016 年カラー地図: レベル 0 から 3 は 3 km の画像 (13500 × 6750) から、レベル 4 は 500 m の地図 (86400 × 43200、8 枚のパネル) から | 0 から 4 | 16384 × 8192 |
レベルは、それを満たせる元の画像からだけ作り、引き伸ばしません。スクリプトは、昼の地図はレベル 5、夜の地図はレベル 4 で止めます (maxLevel)。500 m の地図はあと 1 レベル分を満たせますが、それは後回しです。雲は、パッケージの 2048 の画像のままです。NASA の大きな雲の画像は今は配信されていませんが、雲はもともとやわらかい絵なので困りません。
| レベル | 昼のタイル数 | 昼のサイズ | 夜のタイル数 | 夜のサイズ |
|---|---|---|---|---|
| 0 (1024) | 2 | 0.06 MB | 2 | 0.02 MB |
| 1 (2048) | 8 | 0.18 MB | 8 | 0.06 MB |
| 2 (4096) | 32 | 0.60 MB | 32 | 0.22 MB |
| 3 (8192) | 128 | 1.98 MB | 128 | 0.79 MB |
| 4 (16384) | 512 | 6.40 MB | 512 | 2.30 MB |
| 5 (32768) | 2048 | 22.78 MB | ||
| 合計 | 2730 | 32.0 MB | 682 | 3.4 MB |
ピラミッド全体で 3,412 ファイル、35.4 MB です (そのうち昼のレベル 5 と夜のレベル 4 が 2,560 ファイル、25.1 MB です)。画面が全部を読み込むことはありません。タイル 1 枚は、グラフィックスメモリで約 1 MB です。画面いっぱいでも、ちょうどよいレベルのタイルを数十枚読むだけです。
使い方
リアルな地球の tiles 属性 (または earth.tiles = "...") に、タイルを配信しているフォルダを、最後にスラッシュをつけて渡します。
<chikyu-earth-realistic center="139.7,35.7" texture-base="/textures/" tiles="/tiles/"></chikyu-earth-realistic>要素は manifest.json を 1 回だけ読みます。そのあとは、ズームで必要になるまで何も読み込みません。パッケージの画像は、タイルの下に残ります。遠くから見たときと、タイルが使えないときの代わりです (昼は横 4096、夜は 2048)。要素は次のように動きます。
- ズームとキャンバスの大きさからレベルを選びます。 画面の 1 ピクセルにテクセル (画像の 1 点) がほぼ 1 つ入るレベルを選びます (次のレベルに上がるまで、テクセルは 4 分の 1 だけ広くてかまいません)。数えるのは、円盤の半径のデバイスピクセル数です。ズーム、ボックスの大きさ、ピクセル比で大きくなります。パッケージの 4096 の画像がそれだけくっきりしているうちは、タイルは読みません。それを超えると、昼はレベル 3 (8192)、次にレベル 4 (16384)、その次にレベル 5 (32768) です。夜のタイルはレベル 2 (4096) から始まり、レベル 4 (16384) までです。
- 見える範囲のタイルだけを読みます。 ボックスの中に、地球の手前側のタイルの一部が入っていれば「見えている」ことにします。端が入ってくる少し前から読みます。ボックスの真ん中に近いものが先です。同時に読むのは 6 枚までです。日付変更線と両極は、特別な扱いがいりません。タイルは、球の上のある場所で調べます。経度 180 度は一番最後の列です。
- パッケージの画像の上に描きます。 タイル 1 枚につき球面の小さな一片で、細かいレベルが粗いレベルの上に重なります。太陽、夜側、都市の明かりは、地球のほかの部分と同じです。夜側では、昼のタイルと組になる夜のタイルも読みますが、太陽が低くて夜の絵が見える場所だけです。
- 少ししか持ちません。 見えなくなったタイル、または選んだレベルでなくなったタイルは、4 秒だけ残します (ドラッグで戻っても読み直しません)。そのあと解放し、GPU 上のテクスチャも捨てます。48 枚を超えたら、古いものからすぐに捨てます。グラフィックスメモリは、多くても約 70 MB です。見える範囲のタイルがそれより多くなるときは、収まるまでレベルを下げます。4096 の画像を持たない端末 (
max-textureが 4096 未満、または 2 GB 以下のスマホ) では、タイルを使いません。 - 必要なときだけ描きます。 タイルが届くと 1 回の描画を頼みます。そのときの分はまとめ、1 秒 30 回までにします。1 回の描画で球面に載せる新しいタイルは 8 枚までです。止まっているとき、画面の外、隠れたタブでは、何も描きません。WebGL のコンテキストを失っても、持っていたタイルのまま復帰し、読み直しはしません。
- 黙って元に戻ります。 オプションがないとき、manifest が読めないとき、タイルが取れないとき (30 秒後にもう一度頼みます) は、これまでどおりパッケージの画像で描き、読む人には何も知らせません。
earth.stats.tilesで状態が分かります (ready、level、held、shown、loading、failed、requested)。セットを読み終えると、要素はchikyu-tilesを 1 回だけ出します。
宇宙の表示で
<chikyu-space> も同じ tiles 属性 (とプロパティ) を取り、同じタイルを地球に重ねます。カメラは遠近法で、地面の数 km 上に来ることもあります。そのためレベルは、円盤の半径ではなく、カメラの真下の地面 1 単位が何ピクセルになるかで選びます。ISS の 3,000 km 上から高さ 600 ピクセルの画面を見ると、1 ピクセルは地面の約 3 km で、レベル 3 (8192) が要ります。150 km 上からは数百 m で、一番細かいレベル 5 が要ります。地球全体が見えているときはタイルは要らず、何も読みません。読むのはカメラの真下のタイルだけです。上限 48 枚と 4 秒の保持は同じで、タイルは地球と一緒に回り、同じ太陽で照らされ、止まっているときは何も描きません。カメラはタイルの計算に TilePerspective (地球自身の軸でのカメラの位置、地面の点が画面のどこに落ちるか、画面のある場所の下がどの地面か) として渡します。TileCamera の perspective に入れて、tilesInView と pointOnEarth に渡します。space.stats.tiles で状態が分かり、セットを読み終えると要素は chikyu-tiles を 1 回出します。
<chikyu-space texture-base="/textures/" tiles="/tiles/"></chikyu-space>どこまで近づけるか
パッケージの画像がくっきり見えるのは約 5 倍までで、デモの上限も 5 でした。上限は、昼のタイルの一番細かいレベルが、一番大きな画像 (横 4096) の何倍の幅かで決まります。ピラミッドがレベル 4 (横 16384) までだったときは 4 倍で、20 倍でした。今はレベル 5 (横 32768、一番大きな画像の 8 倍) まであるので、リアルな地球は 8 倍遠くまで、つまり 40 倍まで近づけます。40 倍では、420 ピクセルの地球の円盤は半径 7,800 ピクセルになり、ふつうの画面でテクセル 1 つは CSS ピクセルで最大 1.5 ほどです。まだ細部は見えますが、元の画像より細かいものはありません (Blue Marble は 1 ピクセルが 500 m です。レベル 5 は 1 ピクセルが約 1.2 km なので、元の画像にはピラミッドよりあと 1 レベル分の細かさがあります)。要素は、manifest.json から昼の一番細かいレベルを読み、タイルが見つかると自分でズームの上限を 5 からその倍率に上げ (レベル 4 までのセットなら 20、レベル 5 までなら 40)、見つからないときは 5 のままにします (要素自身の zoom-max 属性に書き込みます。要素に付けたバーはそれを読みます。自分で付けた zoom-max はそのままです)。ほかに何も要りません。
<chikyu-earth-realistic texture-base="/textures/" tiles="/tiles/"></chikyu-earth-realistic>雲はパッケージの 2048 の画像のままです。雲はやわらかい絵で、NASA の細かい雲の画像は今は配信されていません。都市の明かりはレベル 4 (横 16384) までです。昼の地図の半分の細かさですが、それ以上は見るものがあまりありません。
作り方
python3 -m venv .cache/venv && .cache/venv/bin/pip install pillow
.cache/venv/bin/python -I scripts/make-earth-tiles.py build2 行目は pnpm tiles でも動きます。これは、2 枚の全体の画像だけから、昼のレベル 0 から 4 と夜のレベル 0 から 3 (公開済みのパッケージが読む 852 ファイル) を作ります。昼のレベル 5 と夜のレベル 4 は 500 m のパネルから作り、EARTH_TILES_ALL=yes のときだけです (EARTH_TILES_ALL=yes pnpm tiles)。Pages サイトに公開させるスイッチ (下を見てください) と同じもので、ないときはパネルをダウンロードもしません。スクリプトは、元の画像を .cache/earth-tiles/ (git には入れません) に 1 回だけダウンロードします。SHA-256 がスクリプトに書いた値と違うファイルは使いません。結果は .cache/earth-tiles-out/ に出ます。EARTH_TILES_ALL=yes のときは、時間は約 3〜4 分で、初回は約 690 MB をダウンロードします (全体の画像 2 枚で 36 MB、500 m の地図の 16 枚のパネルで 652 MB)。ファイルはすべて残して照合するので、2 回目からはダウンロードしません。ダウンロードに失敗したときは、3 回まで試します (5 秒後、20 秒後) が、それでもだめならスクリプトは止まります。元の画像ごとの URL と SHA-256 は scripts/earth-tiles-sources.json にあります。各レベルは、タイルごとではなく、元の画像全体から一度に縮小します。そのため、隣り合うタイルの境目がそろいます。縮小の前に、元の画像の反対側の端を両側に足します。だから、日付変更線 (-180 度と 180 度は同じ線です) に継ぎ目が出ません。両極 (一番上と一番下の行) の先には何もないので、そこで縮小は止まります。
500 m の地図は 86400 × 43200 ピクセルで、そのまま画像にすると 11 GB になります。そのため、昼のレベル 5 と夜のレベル 4 は、パネル 1 枚ずつ縮小します。パネルの周りに、隣のパネルの端の行と列を足します (日付変更線の向こう側は反対端のパネル、両極の先は何も足しません)。それを、そのレベルで受け持つ正方形 (昼のレベル 5 は 8192 × 8192、夜のレベル 4 は 4096 × 4096 ちょうどで、512 ピクセルのタイルの整数枚分) に縮小して切ります。パネルは、端を取るために 1 回、自分のタイルのために 1 回、合わせて 2 回読み込むので、一度にメモリに載るのは約 4 GB です。結果は、地図全体を一度に縮小したものと、1 ピクセルまで同じです。
pnpm test:tiles は CI の 1 ステップでもあり、Pillow が入っている環境では pnpm test にも含まれます。256 × 128 の作り物の地図を 32 ピクセルのタイルに切り、レベルごとのタイル数とサイズ、タイルの境目に継ぎ目がないこと、日付変更線を真ん中に回して切った絵と同じになることを調べます。さらに、512 × 256 の作り物の地図を 8 枚のパネルとして切り、どのタイルも、地図全体から切ったタイルと 1 ピクセルまで同じになること (つまり、パネルの継ぎ目、日付変更線、両極が正確なこと)、地図より幅の広いレベルは作らないことも調べます。
このタイルより近く
ピラミッドは昼のレベル 5(約 1.2 km 四方、元の画像は 500 m)で止まります。それより近い景色では、tiles-finer="gibs" とすると、ブラウザが NASA の画像配信(Global Imagery Browse Services)から、陸地のもっと細かい画像を読みます。ページがこれを付けない限り、読みません。付けたページは、そのサービスに頼ることになります。
<chikyu-earth-realistic texture-base="/textures/" tiles="/tiles/" tiles-finer="gibs"></chikyu-earth-realistic><chikyu-space> も同じ属性を取ります。既定の画像は、NASA の Landsat の年間の自然色モザイク(レイヤー Landsat_WELD_CorrectedReflectance_TrueColor_Global_Annual、Global Web-Enabled Landsat Data、1 ピクセル 30 m で、レベル 5 の約 40 倍の細かさ)です。1 年分を合わせたモザイクなので雲がなく、1 枚の画像なので、日を探すこともしません。これでないもの。 陸地だけです。海、南極、北のはずれには画像がありません(2026-11-13 に、グリーンランドの北緯 72 度とスバールバルの北緯 78 度では何も見つかりませんでした)。そこはパッケージ自身の画像のままです。古い画像です。一番新しい年は 2000 年で(このレイヤーには 1984 年、1985 年、1988 年から 1990 年、1998 年、1999 年もあります)、都市はその頃の姿です。また、別々の Landsat のシーンをつないだものなので、ところどころ、シーンの境目に少し色の違う直線の縁が見えます。tiles-finer-date に年(1990-06-01 や 1990-12-01 は 1990 年)を書くと、そのレイヤーの別の年を使います。ない年は、一番新しい年になります。
真っ黒な画素はサービスの「データなし」の印、ほとんど黒の画素は Landsat の海なので、どちらも透明にして、パッケージ自身の画像を透かします。海はパッケージの青い海のままで、海岸は Landsat の画像自身で切られます。色には 0.76、0.66、0.55(赤、緑、青)を掛けて、パッケージ自身の画像の暗くて暖かい色に近づけます。これは、陸地の 6 か所で、レベル 5 の平均色をこのレイヤーの平均色で割った値の中央値です。タイルが全部真っ黒なときは画像がないものとして表示せず、一部だけ黒いときは表示して、その黒い部分を透明にします。
tiles-finer-layer="today" はもう 1 つの選び方で、MODIS Terra の日次の自然色画像(MODIS_Terra_CorrectedReflectance_TrueColor、250 m)です。「きのうの地球を宇宙から見た姿」です。1 つの衛星の 1 日分の観測で、雲は残したままで、観測と観測のあいだにすきまがあるので、地面が雲でおおわれていることがよくあります。地図ではなく、ある 1 日の画像です。日は、既定では、画像のある一番新しい日です。表示している瞬間の UTC の前日(その日の観測は、まだ全部は揃っていません)から、1 日ずつさかのぼって探します。ページは、景色の真ん中にある 1 枚のタイルを、新しい日から順に、1 日につき 1 回、1 件ずつ、最大 10 日前まで取りに行き、画像が返ってきた最初の日を使います。答えはセッションのあいだ覚えているので、景色を動かしても、また取りに行くことはありません。10 日のどの日にも画像がなければ、何も言わずに、パッケージ自身のタイルのままにします。レイヤーの最初の日(Terra は 2000-02-24)より前の日と、レイヤーの一覧で抜けている日は、取りに行きません。capabilities ファイルは取りに行きません。このレイヤーでは、tiles-finer-date は latest(既定、上の説明)、previous-day(その 1 日だけ)、shown(表示している瞬間の UTC の日)、または yyyy-mm-dd の 1 日です。ほかのレイヤーの id も使えます(VIIRS_SNPP_CorrectedReflectance_TrueColorは、書いた時点では 2026-11-02 までの日しかありません。BlueMarble_ShadedRelief_Bathymetry はレベル 5 と同じ細かさの静止画で、代わりに使うだけです)。landsat は既定のレイヤーの短い名前です。
下にあるタイルより近い景色になるまで何も取りに行かず、日次のレイヤーにないと分かっている日(最初の日より前、または 2026-11-13 に読んだ一覧で抜けている日)は取りに行きません。タイルが無い、真っ黒、または 2 秒たっても来ないときは、そのままにして、粗い方の絵を残し、そのタイルが画面にある間はもう取りに行きません。tiles-finer-timeout(ミリ秒、ページが何も書かないときは 2000)で、遅い回線のときにタイルを待つ時間を長くできます。上限は同じです。タイル 48 枚、72 MB、同時に 6 件まで、止まっているときは何もしません。届いたタイルは 3 分の 1 秒で重なります。動きを少なくしてあるときは、すぐに表示します。そのタイルが画面にある間、要素のクレジットに NASA の求める文を出し、Landsat のモザイクのときは、そのデータについての 1 行も出します。
読みに行くのは、見ている人のブラウザです。このパッケージは中継しないので、NASA のサーバにはそのリクエストが見えます。
@johnmorrisdotca/chikyu-earth/door の gibsTiles({ time }) は、同じソースをページが自分で作るときの関数です。data="own" のリアルな地球は、これを使いません。
置き場所
git には入れません。タイル画像を履歴に入れると、リポジトリがずっと重くなるからです。npm パッケージにも入れません。
デモのサイト:
pages.ymlが CI でタイルを作り、pnpm siteがタイルをsite/tiles/にコピーします。作業は 2 つのキャッシュで実行をまたいで残します。1 つは作ったタイルで、scripts/make-earth-tiles.pyとscripts/earth-tiles-sources.jsonが変わらない間は使い回すので、ほとんどの実行では何も作りません。もう 1 つは NASA の元ファイル 688 MB で、scripts/earth-tiles-sources.jsonが変わらない間は使い回すので、切り出しスクリプトだけを変えたときはダウンロードしません。つまり、688 MB を取るのは、元の画像が変わった後の最初の実行だけです。タイルは GitHub Pages がデモと一緒に配信します。場所は https://johnmorrisdotca.github.io/chikyu-earth/tiles/ で、access-control-allow-origin: *つきです。公開リポジトリでは無料で、サイトが 3,412 ファイル、35.4 MB 増えます (Pages のサイトの上限は 1 GB です)。昼のレベル 4 と夜のレベル 3 より上のレベルは、リポジトリ変数
EARTH_TILES_ALLがyesのときだけ公開します (pnpm siteも同じ名前の環境変数を読みます。ないときは、これまでどおり 852 ファイル、10.3 MB です)。manifest のレベルを読む読み込み部分より古いもの (chikyu-earth 0.43 まで) は、レベル 5 のある manifest を読むとレベル 5 を求め、その 4 分の 1 ごとにレベル 4 のタイルを取ってきてしまい、絵が間違います。そのため、このタイルを読むページ (ウェブサイトのヒーローなど) が、新しい読み込み部分の入ったリリースになってから変数を設定し、Pages のワークフローをもう一度動かしてください。自分のサイト: スクリプトを動かすか、デモからフォルダをコピーして、静的ファイルとして配信します。場所は
tilesオプションで読み込む部分に教えます。オプションがないとき、またはタイルが取れないときは、リアルな地球はパッケージの画像で描き続けます。
元の画像と利用条件は NOTICE.md にあります。
この日本語は、まだ日本語を母語とする方の確認を受けていません。訂正を歓迎します。