You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/scripts/mapa/generar.mjs

191 lines
5.7 KiB

Las fichas de género salen del taller y tienen su propia sección Con lo que pediste: portada con mapa de Iberoamérica, buscador y un aside que empieza por Inicio, Los principios, Glosario y Letristas antes de la lista de géneros. El mapa es interactivo de verdad: cada país se pinta según lo que tenga —marcado si es cuna de algún género, tenue si solo lo adoptó, apagado si no hay nada—, y al pulsarlo la lista de abajo se parte en «nació aquí» y «también se canta ahí». Esa diferencia es lo que un mapa cuenta mejor que una lista. Los contornos son de `BlankMap-Americas.svg` de Wikimedia, en dominio público, recortados a los veinte países de Iberoamérica en América. De 640 KB a 36 KB comprimidos, y solo en esta ruta: coordenadas a un decimal y fuera los 134 islotes que a este tamaño miden menos de un píxel. Va por script y no pegado a mano, para poder rehacerlo. Cada país lleva además un punto de agarre calculado —el promedio de los vértices de su costa, no el centro de su rectángulo, que en Cuba cae en el mar—: sin él, Puerto Rico mide cuatro píxeles y una isla alargada no hay quien la acierte. «Letristas» es nueva: las mismas personas que salen repartidas en nueve fichas, aquí juntas, con su papel en cada género y sus obras. La página del capítulo pasa a `$lib/components/Capitulo.svelte` y la usan las dos secciones: son setecientas líneas, y copiarlas habría sido dos sitios donde arreglar cada cosa. Las direcciones viejas redirigen con 301 —`/taller/bolero` a `/generos/bolero`— resolviendo contra el catálogo de capítulos, no contra una lista escrita a mano que alguien tenga que ampliar. Un enlace compartido no puede empezar a dar 404 porque hayamos reordenado el menú. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
/**
* Genera `src/lib/mapa/iberoamerica.ts` a partir del mapa de Wikimedia.
*
* node scripts/mapa/generar.mjs [ruta al svg]
*
* El original es `BlankMap-Americas.svg`, de Lokal_Profil, en dominio público:
* https://commons.wikimedia.org/wiki/File:BlankMap-Americas.svg
*
* De sus cincuenta y cinco países se conservan los veinte de Iberoamérica en
* América, y de cada uno todos sus trazados unidos en uno. Dos recortes bajan
* el archivo de 640 KB a algo que puede viajar en una página:
*
* - **Las coordenadas, a un decimal.** El mapa mide dos mil setecientas
* unidades de ancho y se dibuja a setecientos píxeles: la décima de unidad no
* llega ni a la décima de píxel.
* - **Fuera los islotes.** Chile trae ciento cincuenta y cinco trazados, casi
* todos rocas del sur que a este tamaño ocupan menos de un píxel. Se
* descartan los que no llegan al umbral; las islas que sí se ven —Galápagos,
* Chiloé, las Malvinas— lo pasan de sobra.
*
* Se deja como script y no como un archivo pegado a mano para que se pueda
* rehacer: el día que haga falta añadir un país o cambiar el umbral, esto es
* una línea, y a mano sería volver a empezar.
*/
import fs from 'node:fs';
const ORIGEN = process.argv[2] ?? 'scripts/mapa/BlankMap-Americas.svg';
const DESTINO = 'src/lib/mapa/iberoamerica.ts';
/** Los veinte, en el orden en que se leen de norte a sur. */
const PAISES = [
'mx',
'gt',
'sv',
'hn',
'ni',
'cr',
'pa',
'cu',
'do',
'pr',
've',
'co',
'ec',
'pe',
'bo',
'py',
'cl',
'ar',
'uy',
'br'
];
/** Un trazado más pequeño que esto no se ve, y sí se nota en el peso. */
const MINIMO = 2;
const svg = fs.readFileSync(ORIGEN, 'utf8');
/*
* Se recorren las etiquetas con expresión regular y no con un analizador de
* XML: hace falta saber en qué grupo va cada trazado, y para eso basta con
* llevar la cuenta de los `<g>` abiertos.
*/
const trozos = Object.fromEntries(PAISES.map((c) => [c, []]));
const pila = [];
for (const [, cierre, etiqueta, atributos, suelta] of svg.matchAll(
/<(\/?)(g|path)\b([^>]*?)(\/?)>/g
)) {
if (etiqueta === 'g') {
if (cierre) pila.pop();
else pila.push(atributos.match(/id="([^"]+)"/)?.[1] ?? null);
continue;
}
if (cierre) continue;
const propio = atributos.match(/id="([^"]+)"/)?.[1] ?? null;
const codigo = [propio, ...[...pila].reverse()].find((c) => c && PAISES.includes(c));
const d = atributos.match(/d="([^"]+)"/)?.[1];
if (codigo && d) trozos[codigo].push(d);
void suelta;
}
/** El rectángulo que ocupa un trazado, para saber si merece la pena. */
function tamano(d) {
const numeros = [...d.matchAll(/-?\d+(?:\.\d+)?/g)].map((m) => Number(m[0]));
const xs = numeros.filter((_, i) => i % 2 === 0);
const ys = numeros.filter((_, i) => i % 2 === 1);
if (xs.length === 0) return { ancho: 0, alto: 0, xs, ys };
return {
ancho: Math.max(...xs) - Math.min(...xs),
alto: Math.max(...ys) - Math.min(...ys),
xs,
ys
};
}
const unaDecimal = (d) => d.replace(/-?\d+\.\d+/g, (n) => String(Math.round(Number(n) * 10) / 10));
const contorno = {};
const centro = {};
let descartados = 0;
for (const codigo of PAISES) {
const buenos = trozos[codigo].filter((d) => {
const { ancho, alto } = tamano(d);
const vale = ancho >= MINIMO || alto >= MINIMO;
if (!vale) descartados++;
return vale;
});
contorno[codigo] = unaDecimal(buenos.join(' '));
/*
* Un punto donde agarrar el país.
*
* Es el promedio de los vértices del trazado más grande, no el centro de su
* rectángulo: el de Cuba cae en el mar, porque la isla es alargada y curva.
* Con el promedio de la costa el punto queda dentro, y además da algo que
* pulsar en Puerto Rico o El Salvador, que a esta escala miden cuatro
* píxeles.
*/
const mayor = buenos.reduce(
(a, b) => (tamano(b).ancho * tamano(b).alto > tamano(a).ancho * tamano(a).alto ? b : a),
buenos[0] ?? ''
);
const { xs, ys } = tamano(mayor);
const medio = (ns) => ns.reduce((t, n) => t + n, 0) / ns.length;
centro[codigo] = [medio(xs), medio(ys)].map((n) => Math.round(n * 10) / 10);
}
/* El encuadre sale de lo que queda, no del mapa entero. */
const xs = [];
const ys = [];
for (const d of Object.values(contorno)) {
const t = tamano(d);
xs.push(...t.xs);
ys.push(...t.ys);
}
const MARGEN = 10;
const x0 = Math.min(...xs) - MARGEN;
const y0 = Math.min(...ys) - MARGEN;
const encuadre = [x0, y0, Math.max(...xs) - x0 + MARGEN, Math.max(...ys) - y0 + MARGEN]
.map((n) => n.toFixed(1))
.join(' ');
const cuerpo = PAISES.map(
(c) =>
`\t{ codigo: '${c.toUpperCase()}', centro: [${centro[c].join(', ')}], d: '${contorno[c]}' }`
).join(',\n');
fs.writeFileSync(
DESTINO,
`/**
* El mapa de Iberoamérica, país a país.
*
* **Generado. No se edita a mano:** \`node scripts/mapa/generar.mjs\`, que
* explica de dónde sale y qué se recorta.
*
* Los contornos son de \`BlankMap-Americas.svg\` de Wikimedia Commons, obra de
* Lokal_Profil, en dominio público.
*
* España y Portugal no están: el original es de las Américas. El día que hagan
* falta habrá que traerlos de otro mapa o enseñarlos aparte.
*/
export interface PaisDelMapa {
/** ISO 3166-1 alfa-2, como en la tabla \`pais\`. */
codigo: string;
/** Un punto dentro del país, para colgar de él lo que haya que pulsar. */
centro: [number, number];
/** El contorno, con todas sus islas en un solo trazado. */
d: string;
}
/** El encuadre justo de los veinte países, no el del mapa original. */
export const ENCUADRE = '${encuadre}';
export const PAISES: PaisDelMapa[] = [
${cuerpo}
];
`
);
const kb = (fs.statSync(DESTINO).size / 1024).toFixed(0);
console.log(` países ${PAISES.length}`);
console.log(` descartados ${descartados} trazados por debajo de ${MINIMO} unidades`);
console.log(` encuadre ${encuadre}`);
console.log(` tamaño ${kb} KB`);

Powered by TurnKey Linux.