The served gb-IND-ADM1.geojson is the 100-list's curated partition (Mizoram and Nagaland excluded, listId-tagged), so a map colouring every state had data for 36 and shapes for 34. data/boundaries/ gb-IND-ADM1-full.geojson is all 36 from geoBoundaries gbOpen (boundaryID IND-ADM1-1811400, DataMeet / Election Commission of India, CC BY 2.5 IN), with source and licence in gb-IND-ADM1-full.meta.json; generate-tree stages both, and the dev server serves data/ directly. Reproducible with library/scripts/boundaries/source-full-adm1.mjs (coordinates rounded to 3 decimals: 1.84 MB). The curated file is untouched.
61 lines
3.7 KiB
JavaScript
61 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* source-full-adm1.mjs — a COMPLETE ADM1 layer for one country, beside (never replacing) the curated file.
|
|
*
|
|
* WHY: the shipped boundaries/gb-IND-ADM1.geojson is UPG-100's 100-list PARTITION (34 of India's 36 units;
|
|
* Mizoram and Nagaland excluded, and listId-tagged, see plugin-designs/my-upg-100-list/data/
|
|
* source-boundaries.mjs). A map that colours EVERY state (Cities with Internet's NFHS-5 layer) had data for
|
|
* 36 states and shapes for 34. This writes data/boundaries/gb-<ISO3>-ADM1-full.geojson, all units, from
|
|
* geoBoundaries gbOpen, with the source and licence in a .meta.json beside it; generate-tree stages both.
|
|
*
|
|
* Schema { iso3, shapeGroup, shapeName, shapeISO } (the curated file's, minus the curation's listId).
|
|
* Coordinates rounded to 3 decimals (~110 m, far finer than a state choropleth needs) and consecutive
|
|
* duplicate points dropped, to keep the served file small.
|
|
*
|
|
* USAGE node library/scripts/boundaries/source-full-adm1.mjs [ISO3=IND] (network, build time only)
|
|
*/
|
|
import { writeFileSync, mkdirSync } from 'node:fs'
|
|
import { resolve, dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../..')
|
|
const ISO3 = (process.argv[2] || 'IND').toUpperCase()
|
|
const API = `https://www.geoboundaries.org/api/current/gbOpen/${ISO3}/ADM1/`
|
|
|
|
const meta = await (await fetch(API)).json()
|
|
const url = meta.simplifiedGeometryGeoJSON || meta.gjDownloadURL
|
|
const src = await (await fetch(url)).json()
|
|
|
|
const r = (n) => Math.round(n * 1000) / 1000
|
|
const ring = (pts) => {
|
|
const out = []
|
|
for (const [x, y] of pts) { const p = [r(x), r(y)]; const q = out[out.length - 1]; if (!q || q[0] !== p[0] || q[1] !== p[1]) out.push(p) }
|
|
if (out.length && (out[0][0] !== out[out.length - 1][0] || out[0][1] !== out[out.length - 1][1])) out.push(out[0].slice())
|
|
return out.length >= 4 ? out : null
|
|
}
|
|
const poly = (rings) => { const rs = rings.map(ring).filter(Boolean); return rs.length ? rs : null }
|
|
const geom = (g) => {
|
|
if (g.type === 'Polygon') { const p = poly(g.coordinates); return p && { type: 'Polygon', coordinates: p } }
|
|
const ps = g.coordinates.map(poly).filter(Boolean)
|
|
return ps.length ? { type: 'MultiPolygon', coordinates: ps } : null
|
|
}
|
|
const features = []
|
|
for (const f of src.features) {
|
|
const g = geom(f.geometry)
|
|
if (!g) throw new Error(`${f.properties.shapeName}: geometry vanished when rounded`)
|
|
features.push({ type: 'Feature', properties: { iso3: ISO3, shapeGroup: f.properties.shapeGroup || ISO3, shapeName: f.properties.shapeName, shapeISO: f.properties.shapeISO || '' }, geometry: g })
|
|
}
|
|
if (String(features.length) !== String(meta.admUnitCount)) throw new Error(`expected ${meta.admUnitCount} units, got ${features.length}`)
|
|
|
|
const dir = resolve(ROOT, 'data/boundaries'); mkdirSync(dir, { recursive: true })
|
|
const out = resolve(dir, `gb-${ISO3}-ADM1-full.geojson`)
|
|
writeFileSync(out, JSON.stringify({ type: 'FeatureCollection', features }))
|
|
writeFileSync(resolve(dir, `gb-${ISO3}-ADM1-full.meta.json`), JSON.stringify({
|
|
file: `gb-${ISO3}-ADM1-full.geojson`, units: features.length,
|
|
source: 'geoBoundaries gbOpen (www.geoboundaries.org)', boundaryID: meta.boundaryID,
|
|
boundarySource: meta.boundarySource, licence: meta.boundaryLicense, licenceSource: meta.licenseSource,
|
|
yearRepresented: meta.boundaryYearRepresented, sourceUpdated: meta.sourceDataUpdateDate, fetchedFrom: url,
|
|
processing: 'coordinates rounded to 3 decimals, consecutive duplicate points dropped (library/scripts/boundaries/source-full-adm1.mjs)',
|
|
note: 'Complete ADM1. The curated gb-' + ISO3 + '-ADM1.geojson (the 100-list partition, listId-tagged) is a different file and is not replaced.',
|
|
}, null, 2) + '\n')
|
|
console.log(`${out}: ${features.length} units`)
|