Comments in library/ and internal/ that quoted a person, attributed a decision to one, used first person, named fleet agents or carried private host names now state the same technical fact in neutral words: poster-render (including the comments it writes into generated posters), packet-render, the poster and research scripts, PosterWall, DataTable, the legends, the layouts and the gates. The network-policy note states the policy itself. Behaviour is unchanged: a few printed gate messages and reason labels drop an agent name, the poster-render integrity pin follows its comment-only edit, and series.json changes only in its _meta and _why notes. The historical poster design snapshots (library/scripts/posters/snapshots/), which nothing reads, are removed.
65 lines
4.3 KiB
JavaScript
65 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* build-web-boundaries.mjs — the INTERACTIVE-map copies of the boundary files: small enough to parse
|
||
* fast, with every feature, every island and every property kept.
|
||
*
|
||
* node library/scripts/boundaries/build-web-boundaries.mjs
|
||
*
|
||
* WHY. ~3.6 MB of boundary GeoJSON was measured (world-adm0 1.9 MB, gb-IND-ADM1 1.6 MB) costing
|
||
* 5–6 s of MapLibre worker parsing before the first territory draws, on every page of every
|
||
* partner site. A web map drawn at zoom 1–7 needs a fraction of those vertices. The FULL files stay
|
||
* as they are: posters and print use them, and so does every build-time point-in-polygon.
|
||
*
|
||
* HOW (mapshaper, pinned below; fetched with npx, it is not a repo dependency):
|
||
* -explode split each multi-part feature into its parts, so every island is its own shape
|
||
* -simplify visvalingam weighted percentage=P keep-shapes
|
||
* topology-preserving (shared borders stay shared); keep-shapes stops any shape
|
||
* from vanishing, which, after the explode, means no island is ever dropped
|
||
* (without the explode, keep-shapes guards only whole features: 572 parts -> 240)
|
||
* -dissolve KEY copy-fields=... put each feature back together, same properties
|
||
* precision=0.0001 ~11 m, far below a pixel at the zooms these files are drawn at
|
||
*
|
||
* VALIDATED, the run FAILS otherwise: the same feature count, the same keys, the same polygon-part
|
||
* count (no dropped island), and the same property names and values on every feature.
|
||
* MEASURED: the world file goes from 1,866 KB to 383 KB (134 KB gzipped) with all 162 features and all
|
||
* 572 polygon parts; India's from 1,796 KB to 227 KB with all 36 and all 452. A second run writes
|
||
* identical bytes.
|
||
*/
|
||
import { execFileSync } from 'node:child_process'
|
||
import { readFileSync, statSync } from 'node:fs'
|
||
import { resolve, dirname, join } from 'node:path'
|
||
import { fileURLToPath } from 'node:url'
|
||
|
||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||
const MAPSHAPER = 'mapshaper@0.6.121'
|
||
const UPG = 'plugin-designs/my-upg-100-list/data'
|
||
const JOBS = [
|
||
// src, out, dissolve key, the other properties, simplification percentage
|
||
{ src: `${UPG}/adm0/world-adm0.geojson`, out: `${UPG}/adm0/world-adm0-web.geojson`, key: 'iso3', copy: ['shapeGroup', 'shapeName'], pct: '8%' },
|
||
{ src: 'data/boundaries/gb-IND-ADM1-full.geojson', out: 'data/boundaries/gb-IND-ADM1-full-web.geojson', key: 'shapeName', copy: ['iso3', 'shapeGroup', 'shapeISO'], pct: '10%' },
|
||
// UPG-100's curated partition: `listId` is load-bearing (zone-geometry.js), so it must survive.
|
||
{ src: `${UPG}/gb-IND-ADM1.geojson`, out: `${UPG}/gb-IND-ADM1-web.geojson`, key: 'shapeName', copy: ['iso3', 'shapeGroup', 'listId'], pct: '10%' },
|
||
// gb-CHN-ADM1.geojson is 56 KB (12 provinces): already web-sized, no copy made.
|
||
]
|
||
|
||
const fail = (m) => { console.error('build-web-boundaries: FAIL — ' + m); process.exit(1) }
|
||
const parts = (fc) => fc.features.reduce((t, f) => t + (f.geometry.type === 'Polygon' ? 1 : f.geometry.coordinates.length), 0)
|
||
|
||
for (const j of JOBS) {
|
||
const src = join(ROOT, j.src), out = join(ROOT, j.out)
|
||
execFileSync('npx', ['--yes', MAPSHAPER, src, '-explode',
|
||
'-simplify', 'visvalingam', 'weighted', `percentage=${j.pct}`, 'keep-shapes',
|
||
'-dissolve', j.key, `copy-fields=${j.copy.join(',')}`,
|
||
'-o', out, 'format=geojson', 'precision=0.0001'], { stdio: ['ignore', 'ignore', 'pipe'] })
|
||
const a = JSON.parse(readFileSync(src, 'utf8')), b = JSON.parse(readFileSync(out, 'utf8'))
|
||
if (a.features.length !== b.features.length) fail(`${j.out}: ${b.features.length} features, source has ${a.features.length}`)
|
||
const byKey = new Map(b.features.map((f) => [f.properties[j.key], f]))
|
||
for (const f of a.features) {
|
||
const g = byKey.get(f.properties[j.key])
|
||
if (!g) fail(`${j.out}: lost ${j.key}=${f.properties[j.key]}`)
|
||
for (const [k, v] of Object.entries(f.properties)) if (g.properties[k] !== v) fail(`${j.out}: ${f.properties[j.key]}.${k} is ${JSON.stringify(g.properties[k])}, source ${JSON.stringify(v)}`)
|
||
}
|
||
if (parts(a) !== parts(b)) fail(`${j.out}: ${parts(b)} polygon parts, source has ${parts(a)} (an island was dropped)`)
|
||
const kb = (p) => Math.round(statSync(p).size / 1024)
|
||
console.log(`${j.out}: ${kb(src)} KB -> ${kb(out)} KB · ${b.features.length} features · ${parts(b)} parts · properties identical`)
|
||
}
|