Files
Flight-Ops/scripts/download-map-assets.mjs
T

87 lines
4.2 KiB
JavaScript

#!/usr/bin/env node
// Downloads OpenFreeMap map assets and writes them into public/ so the app is
// self-contained at runtime: all 5 styles (rewritten to local glyphs/sprite),
// the full Noto Sans font set, and the sprite sheet. Vector tiles (/planet) and
// the ne2_shaded raster stay remote — this script never touches tile data.
//
// Run manually (not part of tests/CI): node scripts/download-map-assets.mjs
// Idempotent: overwrites existing files in public/.
import { execFileSync } from 'node:child_process';
import { mkdirSync, mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { STYLES, rewriteStyle } from '../lib/mapStyles.ts';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const publicDir = path.join(root, 'public');
// The OFM CDN returns 403 to non-browser user agents.
const UA = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36';
async function download(url, destFile) {
const res = await fetch(url, { headers: { 'user-agent': UA } });
if (!res.ok) throw new Error(`GET ${url} → HTTP ${res.status}`);
writeFileSync(destFile, Buffer.from(await res.arrayBuffer()));
return statSync(destFile).size;
}
function dirSize(dir) {
let total = 0;
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, entry.name);
total += entry.isDirectory() ? dirSize(p) : statSync(p).size;
}
return total;
}
function extractTarball(tarball, destDir, stripComponents) {
mkdirSync(destDir, { recursive: true });
execFileSync('tar', ['-xzf', tarball, `--strip-components=${stripComponents}`, '-C', destDir], { stdio: 'inherit' });
}
const tmp = mkdtempSync(path.join(os.tmpdir(), 'ofm-assets-'));
try {
// 1. Styles: fetch each raw OFM style JSON, rewrite glyphs/sprite to local paths,
// add OSM attribution to sources, write to public/styles/{id}.json.
const stylesDir = path.join(publicDir, 'styles');
mkdirSync(stylesDir, { recursive: true });
for (const { id } of STYLES) {
const url = `https://tiles.openfreemap.org/styles/${id}`;
const res = await fetch(url, { headers: { 'user-agent': UA } });
if (!res.ok) throw new Error(`GET ${url} → HTTP ${res.status}`);
const raw = JSON.parse(await res.text());
writeFileSync(path.join(stylesDir, `${id}.json`), JSON.stringify(rewriteStyle(raw)));
console.log(`style ${id}: written (${JSON.stringify(rewriteStyle(raw)).length} bytes)`);
}
// 2. Fonts: tarball layout is ofm/{Noto Sans X}/{range}.pbf → strip the leading
// "ofm/" so fontstack paths in styles (/fonts/Noto Sans X/...) resolve directly.
const fontsTar = path.join(tmp, 'ofm.tar.gz');
console.log(`downloading fonts tarball…`);
await download('https://assets.openfreemap.com/fonts/ofm.tar.gz', fontsTar);
extractTarball(fontsTar, path.join(publicDir, 'fonts'), 1);
// 3. Sprites: tarball layout is ofm_f384/ofm{.json,.png,@2x.*} → strip the leading
// "ofm_f384/" so files sit flat in public/sprites/ (sprite url is /sprites/ofm).
const spritesTar = path.join(tmp, 'ofm_f384.tar.gz');
console.log(`downloading sprite tarball…`);
await download('https://assets.openfreemap.com/sprites/ofm_f384.tar.gz', spritesTar);
extractTarball(spritesTar, path.join(publicDir, 'sprites'), 1);
// Sanity checks: fail loudly if the tarball structure changed.
const fonts = readdirSync(path.join(publicDir, 'fonts'));
for (const dir of ['Noto Sans Regular', 'Noto Sans Italic', 'Noto Sans Bold']) {
if (!fonts.includes(dir)) throw new Error(`unexpected font layout: missing "${dir}" in public/fonts`);
}
const sprites = readdirSync(path.join(publicDir, 'sprites'));
for (const f of ['ofm.json', 'ofm.png', 'ofm@2x.png']) {
if (!sprites.includes(f)) throw new Error(`unexpected sprite layout: missing "${f}" in public/sprites`);
}
const fmt = (n) => `${(n / 1048576).toFixed(1)} MB`;
console.log(`done. public/styles ${fmt(dirSize(stylesDir))}, public/fonts ${fmt(dirSize(path.join(publicDir, 'fonts')))}, public/sprites ${fmt(dirSize(path.join(publicDir, 'sprites')))}`);
} finally {
rmSync(tmp, { recursive: true, force: true });
}