87 lines
4.2 KiB
JavaScript
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 });
|
|
}
|