0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PMTilesをNginxから配信してOpenLayersで表示する ― 防災Web地図 Step 4

0
Posted at

PMTilesをNginxから配信してOpenLayersで表示する ― 防災Web地図 Step 4

はじめに

ここまでの処理で、データそのものはWeb地図へ載せられるところまで来ています。

  • Step 1:e-Statの3次メッシュを全国分まとめ、1kmメッシュのGeoParquetを作成
  • Step 2:洪水・土砂災害などの公式GISデータを正規化し、1kmメッシュへ集計
  • Step 3:表示用の属性だけを取り出し、TippecanoeでPMTilesを作成

Step 4では、Step 3のPMTilesをNginxから配信し、OpenLayersで表示します。

この段階ではFastAPIを接続しません。固定された防災レイヤーは静的ファイルとして配信し、地図表示だけで完結させます。検索や統計、メッシュの詳細APIは次のStep 5で追加します。

APIを後回しにしたのは、構成を単純にするためです。地図が表示されないときに、PMTilesの問題なのか、NginxのRange設定なのか、OpenLayersの設定なのかを切り分けやすくなります。

Step 4の全体像

ChatGPT Image 2026年8月7日 08_47_46.png

Step 4の役割は、次の4点に絞っています。

1. Step 3の成果物をもう一度確認する
2. PMTilesを公開ディレクトリへ安全に配置する
3. NginxからRange Requestに対応して配信する
4. OpenLayersで表示し、ブラウザ操作まで確認する

入力するStep 3成果物

Step 3の成果物を、次の場所へ配置します。

workspace/artifacts/step3/
├─ tiles/
│  ├─ flood_maximum_1km__20260806_r001.pmtiles
│  └─ landslide_designated_1km__20260806_r001.pmtiles
├─ pmtiles_catalog_fragment.json
├─ manifest.json
└─ validation_report.json

PMTilesだけをコピーして使う構成にはしていません。manifest.jsonvalidation_report.jsonが合格していることを確認し、カタログに記録されたSHA-256、ファイルサイズ、レイヤー名、属性、ズーム範囲も照合します。

Step 3のPythonモジュールはimportしません。Step間で受け渡すものは、検査済みのファイルと管理JSONだけです。

作成する成果物

workspace/artifacts/step4/
├─ frontend_dist/
│  ├─ index.html
│  ├─ assets/
│  └─ config/
│     └─ web_catalog.json
├─ web_catalog.json
├─ nginx.conf
├─ range_check_report.json
├─ browser_smoke_report.json
├─ manifest.json
└─ validation_report.json

workspace/published/tiles/
├─ flood_maximum_1km__20260806_r001.pmtiles
└─ landslide_designated_1km__20260806_r001.pmtiles

workspace/published/tilesは、Nginxの/tiles/へ読み取り専用でマウントします。

PMTilesを静的配信する理由

PMTilesは、Z/X/Y形式のタイルを1つのファイルへまとめた読み取り専用アーカイブです。ブラウザはファイル全体をダウンロードせず、必要なバイト範囲だけをHTTP Range Requestで取得します。

ブラウザ
  ↓ Range: bytes=0-16383
Nginx
  ↓ 206 Partial Content
PMTilesの必要部分

全国1kmメッシュや洪水浸水想定区域のように、公開中は内容が変わらないレイヤーであれば、リクエストごとにDuckDBで検索し、MVTを生成する必要はありません。

静的配信へ寄せると、通常の地図表示では次の処理が不要になります。

FastAPIの起動
DuckDBへの接続
動的MVT生成SQL
APIワーカーの増減
サーバー側のタイルキャッシュ

その代わり、更新時には新しいPMTilesを作ります。同じファイル名を上書きせず、成果物版をファイル名へ含めます。

flood_maximum_1km__20260806_r001.pmtiles
flood_maximum_1km__20260901_r001.pmtiles

公開中の参照先はweb_catalog.jsonで切り替えます。

使用する版

今回の実装では、次の版を固定しています。

Python          3.13
Node.js         24
OpenLayers      9.2.4
ol-pmtiles      2.0.2
pmtiles         4.4.1
Vite            8.1.0
Nginx           1.30.4
Playwright      1.62.0

OpenLayersは互換性を確認した版に固定する

ol-pmtilesの公式ドキュメントでは、OpenLayersをnpmから追加し、ビルドツールと組み合わせる方法が案内されています。本実装もES ModulesとViteを使い、CDNのグローバル変数へ依存しません。

一方、2026年4月には、PMTilesVectorSourceがOpenLayers 10系で動作しないというIssueが公開されています。Issueは2026年8月6日時点でopenのままです。ol-pmtiles 2.0.2のpeer dependencyはOpenLayers 9以上ですが、Step 4では表示経路の再現性を優先し、OpenLayers 9.2.4へ固定しました。10系へ上げるときは、Vite buildだけで判断せず、PMTilesを使ったPlaywrightのE2E検査まで実行します。

Viteは、2026年6月23日に公開された8.1.0を固定しました。依存関係へlatestを指定せず、package.jsonへ完全な版番号を記録します。OpenLayers、ol-pmtilespmtiles、Viteのいずれかを更新したときは、次の項目をまとめて確認します。

PMTilesヘッダーの取得
MVTのtileloadend
危険度別スタイル
地物クリックとポップアップ
背景地図切替
レイヤー表示・不透明度変更
Playwright smoke test
{
  "dependencies": {
    "ol": "9.2.4",
    "ol-pmtiles": "2.0.2",
    "pmtiles": "4.4.1"
  },
  "devDependencies": {
    "vite": "8.1.0"
  }
}

フォルダー構成

参考のために、ZIPファイルを置いておきますので、ダウンロードして活用してください。

japan_disaster_pmtiles_step4_r001/
├─ README.md
├─ VALIDATION.md
├─ japan_disaster_pmtiles_step4_qiita_r001.md
├─ docker-compose.yml
├─ Makefile
├─ .env.example
├─ pyproject.toml
│
├─ tools/
│  ├─ Dockerfile
│  └─ requirements.txt
│
├─ browser/
│  ├─ Dockerfile
│  └─ requirements.txt
│
├─ frontend/
│  ├─ package.json
│  ├─ vite.config.js
│  ├─ index.html
│  ├─ public/config/
│  ├─ src/
│  │  ├─ main.js
│  │  ├─ style.css
│  │  ├─ map/
│  │  │  ├─ app.js
│  │  │  ├─ base_layers.js
│  │  │  ├─ catalog.js
│  │  │  ├─ layers.js
│  │  │  ├─ popup.js
│  │  │  └─ styles.js
│  │  ├─ ui/
│  │  │  ├─ layer_panel.js
│  │  │  ├─ legend.js
│  │  │  └─ status.js
│  │  └─ util/
│  │     └─ formatters.js
│  └─ tests/
│
├─ nginx/
│  └─ default.conf
│
├─ config/
│  ├─ web_map.demo.json
│  └─ web_map.example.json
│
├─ contracts/
│  ├─ web_map.schema.json
│  ├─ web_catalog.schema.json
│  ├─ range_check_report.schema.json
│  ├─ browser_smoke_report.schema.json
│  ├─ validation_report.schema.json
│  └─ artifact_manifest.schema.json
│
├─ src/static_web_step4/
│  ├─ upstream.py
│  ├─ publisher.py
│  ├─ catalog.py
│  ├─ range_check.py
│  ├─ browser_smoke.py
│  ├─ validation.py
│  ├─ manifest.py
│  ├─ pipeline.py
│  └─ ...
│
├─ tests/
├─ docs/
├─ scripts/
└─ workspace/

Docker Composeの構成

サービスは4つです。

tools
  Step 3成果物検査
  PMTiles公開配置
  Webカタログ作成
  Range検査
  manifest作成

frontend-build
  npm install / npm ci
  JavaScript単体テスト
  Vite build

web
  Nginx
  frontend_dist配信
  PMTiles配信

browser-smoke
  Playwright Chromium
  OpenLayers表示確認

docker-compose.ymlの要点です。

services:
  tools:
    build:
      context: .
      dockerfile: tools/Dockerfile
    working_dir: /app
    environment:
      PYTHONPATH: /app/src
      TZ: Asia/Tokyo
    volumes:
      - ./:/app

  frontend-build:
    image: node:24-alpine
    working_dir: /app/frontend
    volumes:
      - ./:/app
    command: ["sh", "/app/scripts/build_frontend_in_container.sh"]

  web:
    image: nginx:1.30.4-alpine
    ports:
      - "${WEB_PORT:-8080}:80"
    volumes:
      - ./workspace/artifacts/step4/frontend_dist:/usr/share/nginx/html:ro
      - ./workspace/published/tiles:/usr/share/nginx/html/tiles:ro
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro

  browser-smoke:
    profiles: ["browser-test"]
    build:
      context: .
      dockerfile: browser/Dockerfile

フロントエンドのビルドスクリプトは、frontendをコンテナ内の/tmpへコピーしてから依存関係を導入します。ホスト側へnode_modulesを作らず、ビルドが終われば一時領域ごと破棄します。

package-lock.jsonを同梱した場合はnpm ciを使います。今回の配布物には、作成環境からnpm registryへ接続できなかったためlock fileを入れていません。直接依存は完全な版番号で固定していますが、本番へ持ち込む前に接続可能な環境でnpm install --package-lock-onlyを実行し、lock fileを版管理へ追加するのが確実です。

1. Step 3成果物を検査する

prepareでは、次を確認してからPMTilesを公開領域へ移します。

manifest.stepがstep3
manifest.statusがpassed
validation_report.statusがpassed
manifest・catalog・validation reportの成果物版が一致
PMTilesのSHA-256がcatalogと一致
PMTilesのファイルサイズがcatalogと一致
PMTiles v3のヘッダーを確認
画面設定で使うtileset_idが存在
色分けに使う属性が存在
ポップアップに使う属性が存在
feature_id属性が存在

画面設定だけ別の版へ差し替えたり、PMTilesだけを手作業で入れ替えたりした場合は、この段階で停止します。

検査部分の中心です。

feature_id_field = str(
    entry.get("feature_id_field", "feature_id")
)

required_fields = {
    feature_id_field,
    str(preference.style["property"]),
    *(
        str(item["field"])
        for item in preference.popup_fields
    ),
}

missing_fields = sorted(
    required_fields - set(layer_fields)
)

if missing_fields:
    raise UpstreamArtifactError(
        "画面設定で使う属性がStep 3 catalogにありません: "
        f"{tileset_id}: {missing_fields}"
    )

2. PMTilesを検査後に一括で公開へ切り替える

PMTilesは数百MBから数GBになることがあります。公開ファイル名へ直接コピーすると、コピー途中のファイルをNginxが返す可能性があります。

実装では、公開先と同じディレクトリに一時ファイルを作り、コピーとfsyncが終わってからos.replace()で切り替えます。

with tempfile.NamedTemporaryFile(
    mode="wb",
    dir=target.parent,
    prefix=f".{target.name}.",
    suffix=".tmp",
    delete=False,
) as handle:
    temporary = Path(handle.name)

    with source.open("rb") as source_handle:
        shutil.copyfileobj(
            source_handle,
            handle,
            length=1024 * 1024,
        )

    handle.flush()
    os.fsync(handle.fileno())

os.replace(temporary, target)

コピー後にはSHA-256とファイルサイズを再計算します。同じファイル名で内容が違う場合は、通常はエラーにします。

公開済みファイルを上書きしない
  ↓
Step 3側で新しい成果物版を付ける
  ↓
web_catalog.jsonの参照先を更新する

--overwriteはデモやローカル検証用です。本番運用では版付きファイル名を変える方が安全です。

3. Webカタログを作る

OpenLayers側へPMTilesのファイル名やスタイルを直書きせず、web_catalog.jsonを生成します。

PMTiles URL
MVTレイヤー名
minimum / maximum zoom
bounds / center
初期表示と不透明度
分類属性と色
ポップアップ項目
出典・基準日・利用条件
表示上の注意

画面固有の設定はconfig/web_map.example.jsonへ書きます。

{
  "tileset_id": "flood_maximum_1km",
  "visible": true,
  "opacity": 0.72,
  "z_index": 20,
  "style": {
    "property": "max_class",
    "default": {
      "fill": "rgba(170,170,170,0.25)",
      "stroke": "rgba(90,90,90,0.55)",
      "stroke_width": 0.5
    },
    "classes": [
      {
        "value": 1,
        "label": "0.5m未満",
        "fill": "rgba(255,245,157,0.70)",
        "stroke": "rgba(160,140,40,0.65)",
        "stroke_width": 0.5
      }
    ]
  },
  "popup_fields": [
    {
      "field": "mesh_code",
      "label": "3次メッシュコード",
      "format": "text"
    },
    {
      "field": "affected_area_ratio",
      "label": "影響面積率",
      "format": "percent",
      "digits": 1
    }
  ]
}

PMTilesを作り直さなくても、色、初期表示、凡例、ポップアップ項目を変更できます。

web_catalog.jsonは公開レイヤーを切り替える入口なので、Nginxでは長期キャッシュせずno-storeを返します。一方、版付きPMTilesは1年間のimmutableキャッシュを設定します。

4. Viteで静的ファイルを作る

フロントエンドはViteでビルドし、成果物を次へ出力します。

workspace/artifacts/step4/frontend_dist/

コンテナ内の実行は次の順です。

cd /app/frontend

if [ -f package-lock.json ]; then
    npm ci
else
    npm install
fi

npm test
npm run build

Viteのpreviewは確認用サーバーなので、公開には使いません。ビルド後のHTML、JavaScript、CSSはNginxから配信します。

5. NginxでRange Requestを返す

PMTilesの配信設定で重要なのは次の点です。

Range Requestを無効にしない
application/vnd.pmtilesを返す
HTTPレベルのgzipを適用しない
ETagを返す
版付きURLを長期キャッシュする
CORSでRange関連ヘッダーを公開する

実装したlocation /tiles/です。

location ^~ /tiles/ {
    types {
        application/vnd.pmtiles pmtiles;
    }
    default_type application/octet-stream;

    gzip off;
    gzip_static off;
    max_ranges 1;

    add_header Accept-Ranges "bytes" always;
    add_header Access-Control-Allow-Origin "*" always;
    add_header Access-Control-Allow-Methods
        "GET, HEAD, OPTIONS"
        always;
    add_header Access-Control-Allow-Headers
        "Range, If-Match, If-Range, If-None-Match"
        always;
    add_header Access-Control-Expose-Headers
        "ETag, Accept-Ranges, Content-Range, Content-Length"
        always;
    add_header Cache-Control
        "public, max-age=31536000, immutable"
        always;

    if ($request_method = OPTIONS) {
        return 204;
    }

    try_files $uri =404;
}

gzip offの意味

これは、MVT内部のgzip圧縮を解除する設定ではありません。

NginxがPMTilesファイル全体へContent-Encoding: gzipを付けると、クライアントが指定したバイト位置と配信データの位置がずれる可能性があります。そのため、PMTilesのlocationではHTTPレベルの圧縮を止めています。

max_ranges 1

Nginxのmax_rangesは、1リクエストで許可するバイト範囲の数を制限します。

max_ranges 1
  単一Rangeを許可

max_ranges 0
  Range処理を無効化

PMTiles配信で0を指定するとRange Requestが使えなくなるため、今回の設定では1としています。

Accept-Ranges: bytes

Rangeリクエストへ206 Partial Contentを返していても、Nginxの版や構成によってはAccept-Rangesが応答へ付かない場合があります。配信機能をブラウザや監視処理から判断しやすいよう、PMTilesのlocationでは次を明示します。

add_header Accept-Ranges "bytes" always;

MIME Type

PMTilesには次のMIME Typeを設定します。

application/vnd.pmtiles

実装では、Range検査時にもContent-Typeを確認します。

6. OpenLayersからPMTilesを読む

PMTilesレイヤーはPMTilesVectorSourceから作ります。

import VectorTileLayer from "ol/layer/VectorTile.js";
import {PMTilesVectorSource} from "ol-pmtiles";

const source = new PMTilesVectorSource({
  url: definition.url,
  attributions: definition.attribution
    ? [definition.attribution]
    : undefined,
});

const minimumZoom = Number(definition.minimum_zoom);
const maximumZoom = Number(definition.maximum_zoom);

const layer = new VectorTileLayer({
  source,
  visible: Boolean(definition.visible),
  opacity: Number(definition.opacity),
  minZoom: Math.max(0, minimumZoom - 0.001),
  maxZoom: maximumZoom + 0.001,
  style: buildStyleFunction(definition.style),
  zIndex: Number(definition.z_index ?? 10),
});

OpenLayersのレイヤーは、minZoomより大きく、maxZoom以下の範囲で表示されます。PMTilesの端のズームでも表示できるよう、Layer側へ0.001だけ余裕を持たせています。

7. 危険度別Styleを使い回す

地物ごとに毎回new Style()を実行すると、パンやズーム時の負荷が増えます。危険度クラスごとにStyleを一度だけ作り、同じクラスでは同じStyleを返します。

export function buildStyleFunction(styleDefinition) {
  const defaultStyle = createStyle(styleDefinition.default);
  const styleByValue = new Map(
    styleDefinition.classes.map((item) => [
      classificationKey(item.value),
      createStyle(item),
    ]),
  );

  return (feature) => {
    const key = classificationKey(
      feature.get(styleDefinition.property),
    );
    return styleByValue.get(key) ?? defaultStyle;
  };
}

MVTの属性が数値として返る場合と文字列として返る場合の両方を扱えるよう、比較キーは文字列へそろえています。

8. 画面に入れた機能

Step 4の画面には、次の機能を入れました。

地理院地図の標準・淡色・航空写真切替
防災レイヤーの表示切替
レイヤーごとの不透明度調整
危険度区分の凡例
地物クリックのポップアップ
出典・基準日の表示
区域外を安全と扱わない注意書き
スケール表示

背景地図、レイヤー、色区分、ポップアップ項目はJSONから作ります。洪水、土砂災害、津波で属性が違っても、OpenLayersの初期化コードを直接書き換えずに追加できます。

9. ポップアップでinnerHTMLを使わない

MVT属性は外部データから作られます。文字列をそのままinnerHTMLへ入れると、HTMLとして解釈される可能性があります。

実装ではDOM要素を作り、textContentへ設定しています。

const term = document.createElement("dt");
term.textContent = field.label;

const detail = document.createElement("dd");
detail.textContent = formatValue(
  properties[field.field],
  field,
);

ポップアップへ入れる属性はweb_catalog.jsonで指定します。PMTilesには地図表示とクリックに必要な最小限の属性だけを入れ、長い説明文や詳細統計はStep 5のAPIへ分けます。

10. 初期表示を1kmメッシュのズームへ合わせる

全国boundsをそのままfit()すると、ズーム4~5程度になります。一方、今回の1kmメッシュPMTilesはズーム10から配信します。

そのままでは、画面を開いても防災メッシュが見えません。

実装では、最初の表示レイヤーへfit()した後、ズームがminimum_zoom未満であれば、カタログに記録した中心へ移動します。

const currentZoom = view.getZoom() ?? 0;

if (currentZoom < Number(first.minimum_zoom)) {
  view.setCenter(fromLonLat(first.center.slice(0, 2)));
  view.setZoom(Number(first.minimum_zoom));
}

全国表示では、Step 2で別途作る10km・20km集計レイヤーへ切り替えるのが本来の構成です。Step 4のデモでは、1kmメッシュが見える位置まで拡大して表示を確認します。

11. Range Requestを自動検査する

小さなデモファイルでは、サーバーがPMTiles全体を200 OKで返していても、地図が表示できてしまうことがあります。全国データへ置き換えた途端に遅くなるため、Range応答はブラウザ表示とは別に検査します。

Pythonから次のリクエストを送ります。

response = client.get(
    url,
    headers={"Range": "bytes=0-16383"},
)

確認項目は次のとおりです。

HTTP statusが206
Content-Rangeの開始が0
Content-Rangeの終了が要求範囲内
Content-Rangeの総サイズがcatalog.size_bytesと一致
Content-Lengthが取得したバイト数と一致
Accept-Rangesにbytesを含む
Content-Typeがapplication/vnd.pmtiles
ETagが存在
Content-Encodingが付いていない

結果はrange_check_report.jsonへ残します。

12. Playwrightで画面を確認する

Range応答が正しくても、JavaScriptの設定ミスで地図が空になることがあります。そこで、Chromiumを使ったスモークテストを用意しました。

確認内容は次のとおりです。

Webカタログを読み込める
OpenLayersが描画される
最初の防災レイヤーへ移動できる
PMTilesのtileloadendが1回以上発生する
tileloaderrorが0件
表示切替チェックボックスが存在する
不透明度スライダーがレイヤーへ反映される
console errorがない
page errorがない
スクリーンショットを保存できる

フロントエンドは、検査用の状態をwindow.__STEP4_DIAGNOSTICS__へ記録します。通常の利用者向けAPIではなく、表示経路のどこで止まったかをPlaywrightから判断するためのものです。

デモを実行する

最初に、架空の小さなPMTilesで一連の処理を確認します。

cp .env.example .env
make demo

デモでは次を実行します。

Step 3互換の架空PMTilesを2レイヤー生成
Step 3管理JSONを生成
上流成果物を検査
PMTilesを公開領域へ配置
web_catalog.jsonを生成
Viteでfrontend_distを作成
Nginxを起動
Range応答を検査
ChromiumでOpenLayersを確認
Step 4 manifestとvalidation reportを作成

起動後は次を開きます。

http://localhost:8080

停止は次です。

make down

デモ成果物を削除する場合は次を実行します。

make clean-demo

デモデータは表示と配信の確認専用です。実在する危険区域ではなく、防災判断には使用できません。

実データで実行する

Step 3成果物を次へ置きます。

workspace/artifacts/step3/

.envを作成します。

cp .env.example .env

主な設定です。

STEP3_ARTIFACT_DIR=workspace/artifacts/step3
STEP4_ARTIFACT_VERSION=20260806_r001
STEP4_CONFIG=config/web_map.example.json
WEB_PORT=8080
STEP4_BASE_URL=http://localhost:8080

まとめて実行する場合は次です。

make step4

処理を分けて確認する場合は、次の順番で実行します。

make build
make tools
make prepare
make frontend
make up
make wait
make range
make browser
make finalize
make validate
make inspect

更新時の流れ

公開済みPMTilesは同じ名前で上書きしません。

1. Step 3で新しい版付きPMTilesを作る
2. pmtiles verifyと代表タイルdecodeに合格させる
3. Step 4のprepareを実行する
4. 新しいweb_catalog.jsonを作る
5. Vite buildを実行する
6. NginxでRange応答を確認する
7. Playwrightで地図を確認する
8. Step 4 manifestを確定する
9. 旧PMTilesをロールバック期間中残す

問題があった場合は、web_catalog.jsonが参照するファイル名を旧版へ戻します。PMTilesを上書きしていなければ、ロールバックは参照先の切り替えだけで済みます。

S3やR2へ移す場合

Step 4ではDocker上のNginxを使いますが、PMTilesとfrontend_distは静的ファイルなので、後からS3、Cloudflare R2、Google Cloud Storageなどへ移せます。

移行先でも、次を確認します。

HTTP Range Request
206 Partial Content
Content-Range
Content-Length
ETag
CORS
application/vnd.pmtiles
Content-Encodingが付いていない

ストレージへアップロードできたことだけでなく、実際にブラウザと同じRange Requestを送り、応答を検査します。

よくあるエラー

PMTilesが404になる

確認箇所は次です。

workspace/published/tilesのファイル名
web_catalog.jsonのurl
Nginxのvolume mount
Nginxのtry_files

200 OKでファイル全体が返る

Range Requestが無効、または途中のCDN・プロキシがRangeを無視しています。

Rangeヘッダーが送られているか
Nginxでmax_ranges 0になっていないか
CDNがRangeを転送しているか
Content-Encodingが付いていないか

Content-Typeが違う

nginx/default.conftypesを確認します。

types {
    application/vnd.pmtiles pmtiles;
}

地図は出るが防災メッシュが見えない

次を順番に確認します。

レイヤーがvisible=trueか
現在のズームがminimum_zoom以上か
PMTiles catalogのboundsとcenter
MVTのlayer_name
分類に使う属性名
背景地図の下へ隠れていないか

凡例と色が合わない

style.propertyとMVT属性名が一致しているか確認します。数値と文字列の差は実装側で吸収していますが、値そのものが異なる場合は該当クラスへ入りません。

OpenLayers更新後に初期化できない

次の組み合わせを一度に変えず、1つずつ更新します。

ol
ol-pmtiles
pmtiles
Vite

更新後はPlaywrightのスモークテストまで実行します。

テスト

ローカルで構成を確認する場合は次です。

make check

Docker環境を含めて確認する場合は次です。

make test

make checkでは次を確認します。

必須ファイル
Python構文
Pythonのmodule・class・functionのdocstring
JavaScript構文
Shell構文
JSONとJSON Schema
Docker Compose YAML
Markdownコードフェンス
Nginx設定

作成環境で確認できた結果は次のとおりです。

Python単体・統合テスト 14 passed, 1 skipped
Node.js単体テスト     7 passed
Python構文            OK
JavaScript構文        OK
Shell構文             OK
JSON / YAML            OK
Nginx設定構文          OK
ローカルRange応答      206 / MIME / headers OK

スキップした1件は、PythonのPMTiles生成ライブラリを使うデモ統合テストです。Dockerのtoolsイメージには必要な依存関係を含めています。

ローカルのNginx 1.26.3では、一時サーバーを起動し、20KBの検査用ファイルへRange: bytes=0-16383を送りました。206 Partial Contentapplication/vnd.pmtilesContent-RangeAccept-Ranges: bytes、ETag、長期キャッシュ、CORS公開ヘッダーを、Step 4のrun_range_check()で確認しています。この検査はNginx設定とHTTP応答を対象とし、Step 3の実PMTilesやOpenLayers描画は含みません。

この作成環境にはDockerデーモンがなく、外部npm registryへの接続も完了しなかったため、Viteの本番ビルド、Nginx 1.30.4コンテナと実PMTilesを使ったRange配信、Playwright Chromiumによる画面確認までは実行していません。利用環境では、まず次を実行してください。

make demo
make test

区域外を安全と表示しない

1kmメッシュ集計にレコードがない理由は、危険区域がない場合だけではありません。

対象データが未整備
選択したシナリオの対象外
元データの範囲外
処理条件で除外
想定を超える現象

そのため、画面では「安全」と断定せず、次の注意を表示します。

区域外であることは、安全を保証するものではありません。

出典、基準日、シナリオ、利用条件、解釈上の注意はStep 3のカタログから引き継ぎます。

まとめ

Step 4では、PMTilesを置いてOpenLayersから読むだけでなく、公開前後の確認までを処理へ組み込みました。

Step 4の実装まとめ

Step 3の版とSHA-256を確認する
表示属性とfeature_idを確認する
PMTilesをコピー途中で公開しない
画面設定をWebカタログへ分ける
OpenLayersの依存版を固定する
NginxでRange Requestを返す
PMTiles専用MIME Typeを設定する
HTTPレベルのgzipを止める
206応答を自動検査する
Playwrightでtileloadendまで確認する
公開ファイルと検査結果をmanifestへ残す

固定レイヤーは、この時点でFastAPIなしでも表示できます。

次のStep 5では、Step 1とStep 2の属性成果物をDuckDBへ登録し、メッシュ詳細、データセット一覧、統計情報をFastAPIから返します。OpenLayersで地物をクリックしたときだけAPIを呼ぶ構成へ進めます。

参考資料


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?