@ -25,11 +25,19 @@
import fs from 'node:fs' ;
const ORIGEN = process . argv [ 2 ] ? ? 'scripts/mapa/BlankMap- Americas .svg';
const ORIGEN = process . argv [ 2 ] ? ? 'scripts/mapa/BlankMap- World .svg';
const DESTINO = 'src/lib/mapa/iberoamerica.ts' ;
/** Los veinte, en el orden en que se leen de norte a sur. */
/ * *
* Los veintidós de Iberoamérica : los veinte de América más España y Portugal .
*
* Estaban fuera en la primera versión , hecha sobre un mapa de las Américas .
* Iberoamérica es también la Península — y España está en los datos : el bolero
* se adoptó ahí — , así que el origen pasa a ser el mapa del mundo .
* /
const PAISES = [
'es' ,
'pt' ,
'mx' ,
'gt' ,
'sv' ,
@ -55,6 +63,38 @@ const PAISES = [
/** Un trazado más pequeño que esto no se ve, y sí se nota en el peso. */
const MINIMO = 2 ;
/ * *
* El nombre en español de cada uno .
*
* Va aquí y no se lee de la tabla ` pais ` : el mapa dibuja los veintidós desde el
* primer día y la tabla solo tiene los países que ha traído alguna semilla . Un
* país sin nombre en la lista de al lado no se puede ni pulsar .
* /
const NOMBRES = {
es : 'España' ,
pt : 'Portugal' ,
mx : 'México' ,
gt : 'Guatemala' ,
sv : 'El Salvador' ,
hn : 'Honduras' ,
ni : 'Nicaragua' ,
cr : 'Costa Rica' ,
pa : 'Panamá' ,
cu : 'Cuba' ,
do : 'República Dominicana' ,
pr : 'Puerto Rico' ,
ve : 'Venezuela' ,
co : 'Colombia' ,
ec : 'Ecuador' ,
pe : 'Perú' ,
bo : 'Bolivia' ,
py : 'Paraguay' ,
cl : 'Chile' ,
ar : 'Argentina' ,
uy : 'Uruguay' ,
br : 'Brasil'
} ;
const svg = fs . readFileSync ( ORIGEN , 'utf8' ) ;
/ *
@ -70,23 +110,99 @@ for (const [, cierre, etiqueta, atributos, suelta] of svg.matchAll(
) ) {
if ( etiqueta === 'g' ) {
if ( cierre ) pila . pop ( ) ;
else pila . push ( atributos . match ( / id="([^"]+)"/) ? . [ 1 ] ? ? null ) ;
else pila . push ( atributos . match ( / (?:^|\s) id="([^"]+)"/) ? . [ 1 ] ? ? null ) ;
continue ;
}
if ( cierre ) continue ;
const propio = atributos . match ( / id="([^"]+)"/) ? . [ 1 ] ? ? null ;
const propio = atributos . match ( / (?:^|\s) id="([^"]+)"/) ? . [ 1 ] ? ? null ;
const codigo = [ propio , ... [ ... pila ] . reverse ( ) ] . find ( ( c ) => c && PAISES . includes ( c ) ) ;
const d = atributos . match ( /d="([^"]+)"/ ) ? . [ 1 ] ;
/ *
* El límite del atributo importa : sin él , ` d="…" ` encaja dentro de
* ` id="Mexico_mainland" ` y el trazado que se extrae es el nombre del
* país . Salía un mapa vacío sin que nada fallara .
* /
const d = atributos . match ( /(?:^|\s)d="([^"]+)"/ ) ? . [ 1 ] ;
if ( codigo && d ) trozos [ codigo ] . push ( d ) ;
void suelta ;
}
/ * *
* Recorre un trazado y devuelve sus puntos , ya en absolutas .
*
* Hace falta porque el mapa del mundo está escrito en relativas — ` m 1244,328 c
* 0.1 , - 0.12 … ` — y ahí los números no son coordenadas sino desplazamientos:
* sumarlos como si lo fueran daba un rectángulo sin sentido y el encuadre salía
* infinito .
*
* No convierte el trazado , solo lo mide : el ` d ` se guarda tal cual , que dibuja
* igual y ocupa menos .
* /
function puntosDe ( d ) {
const piezas = d . match ( /[a-zA-Z]|-?\d*\.?\d+(?:e[-+]?\d+)?/gi ) ? ? [ ] ;
const puntos = [ ] ;
let x = 0 ;
let y = 0 ;
let inicioX = 0 ;
let inicioY = 0 ;
let comando = 'M' ;
let i = 0 ;
/** Cuántos números lleva cada comando. */
const cuantos = { M : 2 , L : 2 , T : 2 , H : 1 , V : 1 , C : 6 , S : 4 , Q : 4 , A : 7 , Z : 0 } ;
while ( i < piezas . length ) {
if ( /[a-zA-Z]/ . test ( piezas [ i ] ) ) {
comando = piezas [ i ] ;
i ++ ;
if ( comando . toUpperCase ( ) === 'Z' ) {
x = inicioX ;
y = inicioY ;
continue ;
}
}
const letra = comando . toUpperCase ( ) ;
const relativo = comando !== letra ;
const n = cuantos [ letra ] ;
if ( n === undefined ) break ;
const args = piezas . slice ( i , i + n ) . map ( Number ) ;
i += n ;
if ( args . length < n ) break ;
if ( letra === 'H' ) x = relativo ? x + args [ 0 ] : args [ 0 ] ;
else if ( letra === 'V' ) y = relativo ? y + args [ 0 ] : args [ 0 ] ;
else if ( letra === 'A' ) {
// De la elipse solo interesa dónde acaba.
x = relativo ? x + args [ 5 ] : args [ 5 ] ;
y = relativo ? y + args [ 6 ] : args [ 6 ] ;
} else {
// El punto final es la última pareja; los de control quedan fuera del
// cálculo, y para medir un país la diferencia no se ve.
x = relativo ? x + args [ n - 2 ] : args [ n - 2 ] ;
y = relativo ? y + args [ n - 1 ] : args [ n - 1 ] ;
}
if ( letra === 'M' ) {
inicioX = x ;
inicioY = y ;
// Una `m` con más parejas sigue como `l`; una `M`, como `L`.
comando = relativo ? 'l' : 'L' ;
}
puntos . push ( [ x , y ] ) ;
}
return puntos ;
}
/** 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 ) ;
const puntos = puntosDe ( d ) ;
const xs = puntos. map ( ( p ) => p [ 0 ] ) ;
const ys = puntos. map ( ( p ) => p [ 1 ] ) ;
if ( xs . length === 0 ) return { ancho : 0 , alto : 0 , xs , ys } ;
return {
ancho : Math . max ( ... xs ) - Math . min ( ... xs ) ,
@ -98,8 +214,21 @@ function tamano(d) {
const unaDecimal = ( d ) => d . replace ( /-?\d+\.\d+/g , ( n ) => String ( Math . round ( Number ( n ) * 10 ) / 10 ) ) ;
/ * *
* Une varios trazados en uno .
*
* El primer salto de cada uno pasa a ser absoluto . Dentro de un ` <path> ` la
* posición de partida es el origen , así que ` m 1244,328 ` y ` M 1244,328 ` son lo
* mismo ; pero al pegar el segundo detrás del primero , su ` m ` se sumaba a donde
* había acabado el anterior y México acababa a cuarenta mil unidades de su
* sitio . El encuadre salía de cuarenta y dos mil por cincuenta y ocho mil .
* /
const unir = ( trazados ) => trazados . map ( ( d ) => d . replace ( /^\s*m/ , 'M' ) ) . join ( ' ' ) ;
const contorno = { } ;
const centro = { } ;
/** El trazado mayor de cada país: es con el que se encuadra el inserto. */
const principal = { } ;
let descartados = 0 ;
for ( const codigo of PAISES ) {
@ -109,7 +238,7 @@ for (const codigo of PAISES) {
if ( ! vale ) descartados ++ ;
return vale ;
} ) ;
contorno [ codigo ] = unaDecimal ( buenos. join ( ' ' ) ) ;
contorno [ codigo ] = unaDecimal ( unir( buenos ) ) ;
/ *
* Un punto donde agarrar el país .
@ -127,27 +256,48 @@ for (const codigo of PAISES) {
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 ) ;
principal [ codigo ] = mayor ;
}
/* 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 ) ;
/ * *
* Dos encuadres , no uno .
*
* Iberoamérica son dos orillas y en medio hay un océano : con un solo encuadre ,
* la mitad del dibujo es Atlántico vacío y todo lo demás queda diminuto . Se
* calcula uno para América y otro para la Península , y el mapa pinta el segundo
* en un recuadro — la misma solución que usa cualquier mapa de España con
* Canarias — .
* /
const IBERIA = [ 'es' , 'pt' ] ;
const MARGEN = 10 ;
function encuadrar ( codigos , soloPrincipal = false ) {
const xs = [ ] ;
const ys = [ ] ;
for ( const c of codigos ) {
const t = tamano ( soloPrincipal ? principal [ c ] : contorno [ c ] ) ;
xs . push ( ... t . xs ) ;
ys . push ( ... t . ys ) ;
}
const x0 = Math . min ( ... xs ) - MARGEN ;
const y0 = Math . min ( ... ys ) - MARGEN ;
return [ x0 , y0 , Math . max ( ... xs ) - x0 + MARGEN , Math . max ( ... ys ) - y0 + MARGEN ]
. map ( ( n ) => n . toFixed ( 1 ) )
. join ( ' ' ) ;
}
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 encuadre = encuadrar ( PAISES . filter ( ( c ) => ! IBERIA . includes ( c ) ) ) ;
/ *
* El inserto encuadra solo la parte continental . Con Canarias dentro , el
* rectángulo se estiraba mil kilómetros al suroeste y la Península quedaba del
* tamaño de una mota . Las islas se siguen dibujando ; lo que cambia es el
* encuadre .
* /
const encuadreIberia = encuadrar ( IBERIA , true ) ;
const cuerpo = PAISES . map (
( c ) =>
` \t { codigo: ' ${ c . toUpperCase ( ) } ', centro: [ ${ centro [ c ] . join ( ', ' ) } ], d: ' ${ contorno [ c ] } ' } `
` \t { codigo: ' ${ c . toUpperCase ( ) } ', nombre: '${ NOMBRES [ c ] } ', centro: [${ centro [ c ] . join ( ', ' ) } ], d: ' ${ contorno [ c ] } ' } `
) . join ( ',\n' ) ;
fs . writeFileSync (
@ -168,15 +318,23 @@ fs.writeFileSync(
export interface PaisDelMapa {
/** ISO 3166-1 alfa-2, como en la tabla \`pais\`. */
codigo : string ;
/** En español. Lo lleva el mapa porque dibuja países que aún no están en la base. */
nombre : 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 . */
/** El encuadre de la orilla americana . */
export const ENCUADRE = '${encuadre}' ;
/** El de la Península, que va en su propio recuadro. */
export const ENCUADRE _IBERIA = '${encuadreIberia}' ;
/** Quiénes van en ese recuadro. */
export const IBERIA = [ 'ES' , 'PT' ] ;
export const PAISES : PaisDelMapa [ ] = [
$ { cuerpo }
] ;
@ -187,4 +345,5 @@ 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 ( ` — península ${ encuadreIberia } ` ) ;
console . log ( ` tamaño ${ kb } KB ` ) ;