@cerca.red/mapa 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/dist/capas.d.ts +29 -0
- package/dist/capas.js +489 -0
- package/dist/capas.js.map +1 -0
- package/dist/doble/estiloFalso.d.ts +19 -0
- package/dist/doble/estiloFalso.js +33 -0
- package/dist/doble/estiloFalso.js.map +1 -0
- package/dist/doble/index.d.ts +11 -0
- package/dist/doble/index.js +12 -0
- package/dist/doble/index.js.map +1 -0
- package/dist/doble/mapaFalso.d.ts +75 -0
- package/dist/doble/mapaFalso.js +535 -0
- package/dist/doble/mapaFalso.js.map +1 -0
- package/dist/encuadre.d.ts +64 -0
- package/dist/encuadre.js +83 -0
- package/dist/encuadre.js.map +1 -0
- package/dist/estilo.d.ts +88 -0
- package/dist/estilo.js +23 -0
- package/dist/estilo.js.map +1 -0
- package/dist/eventos.d.ts +127 -0
- package/dist/eventos.js +69 -0
- package/dist/eventos.js.map +1 -0
- package/dist/identificadores.d.ts +109 -0
- package/dist/identificadores.js +104 -0
- package/dist/identificadores.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/manifiesto.d.ts +97 -0
- package/dist/manifiesto.js +156 -0
- package/dist/manifiesto.js.map +1 -0
- package/dist/modelo.d.ts +154 -0
- package/dist/modelo.js +227 -0
- package/dist/modelo.js.map +1 -0
- package/dist/navegador/index.d.ts +70 -0
- package/dist/navegador/index.js +132 -0
- package/dist/navegador/index.js.map +1 -0
- package/dist/navegador/maplibre.d.ts +41 -0
- package/dist/navegador/maplibre.js +463 -0
- package/dist/navegador/maplibre.js.map +1 -0
- package/dist/navegador/protocolo.d.ts +4 -0
- package/dist/navegador/protocolo.js +55 -0
- package/dist/navegador/protocolo.js.map +1 -0
- package/dist/navegador/webgl.d.ts +24 -0
- package/dist/navegador/webgl.js +47 -0
- package/dist/navegador/webgl.js.map +1 -0
- package/dist/navegador/worker.d.ts +33 -0
- package/dist/navegador/worker.js +47 -0
- package/dist/navegador/worker.js.map +1 -0
- package/dist/paleta.d.ts +53 -0
- package/dist/paleta.js +53 -0
- package/dist/paleta.js.map +1 -0
- package/dist/proyecciones.d.ts +160 -0
- package/dist/proyecciones.js +2 -0
- package/dist/proyecciones.js.map +1 -0
- package/dist/puerto.d.ts +244 -0
- package/dist/puerto.js +2 -0
- package/dist/puerto.js.map +1 -0
- package/dist/sdk.d.ts +111 -0
- package/dist/sdk.js +552 -0
- package/dist/sdk.js.map +1 -0
- package/dist/sincronizacion.d.ts +40 -0
- package/dist/sincronizacion.js +144 -0
- package/dist/sincronizacion.js.map +1 -0
- package/dist/worker-de-maplibre.d.ts +36 -0
- package/dist/worker-de-maplibre.js +37 -0
- package/dist/worker-de-maplibre.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { Map as MapaDeMapLibre, } from "maplibre-gl";
|
|
2
|
+
import { fijarUrlDelWorker } from "./worker.js";
|
|
3
|
+
/** El dato que viaja con un movimiento de cámara para poder reconocerlo después. */
|
|
4
|
+
const MARCA_DE_ORIGEN = "origenCerca";
|
|
5
|
+
/**
|
|
6
|
+
* El estilo con el que nace el mapa: vacío.
|
|
7
|
+
*
|
|
8
|
+
* MapLibre exige un estilo en el constructor, pero el dueño del estilo es el
|
|
9
|
+
* SDK: lo construye al arrancar y lo vuelve a construir en cada cambio de tema
|
|
10
|
+
* o de idioma. Darle aquí el estilo de verdad y que el SDK lo pusiera otra vez
|
|
11
|
+
* significa cargarlo dos veces, y MapLibre lo avisa por consola —«Style is not
|
|
12
|
+
* done loading, rebuilding from scratch»— mientras descarga dos veces los
|
|
13
|
+
* glifos.
|
|
14
|
+
*
|
|
15
|
+
* Un estilo vacío carga al instante y deja un solo dueño.
|
|
16
|
+
*/
|
|
17
|
+
const ESTILO_VACIO = {
|
|
18
|
+
version: 8,
|
|
19
|
+
sources: {},
|
|
20
|
+
layers: [],
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* El control de MapLibre que lleva al documento de licencias.
|
|
24
|
+
*
|
|
25
|
+
* Un `IControl` y no un elemento suelto puesto por la superficie: así el
|
|
26
|
+
* enlace vive dentro del área que MapLibre ya reserva para sus controles
|
|
27
|
+
* (`bottom-right`, la misma esquina que la atribución) y se destruye solo
|
|
28
|
+
* cuando el mapa se destruye, sin que quien use `packages/mapa` tenga que
|
|
29
|
+
* acordarse de quitarlo.
|
|
30
|
+
*/
|
|
31
|
+
class ControlDeLicencias {
|
|
32
|
+
#url;
|
|
33
|
+
#contenedor;
|
|
34
|
+
constructor(url) {
|
|
35
|
+
this.#url = url;
|
|
36
|
+
}
|
|
37
|
+
onAdd() {
|
|
38
|
+
const contenedor = document.createElement("div");
|
|
39
|
+
contenedor.className = "maplibregl-ctrl";
|
|
40
|
+
const enlace = document.createElement("a");
|
|
41
|
+
enlace.href = this.#url;
|
|
42
|
+
enlace.target = "_blank";
|
|
43
|
+
enlace.rel = "noopener noreferrer";
|
|
44
|
+
enlace.textContent = "Licencias";
|
|
45
|
+
enlace.style.cssText =
|
|
46
|
+
"display:block;padding:0 4px;font:11px/16px sans-serif;" +
|
|
47
|
+
"color:#333;text-decoration:none;background:rgba(255,255,255,.5);";
|
|
48
|
+
contenedor.append(enlace);
|
|
49
|
+
this.#contenedor = contenedor;
|
|
50
|
+
return contenedor;
|
|
51
|
+
}
|
|
52
|
+
onRemove() {
|
|
53
|
+
this.#contenedor?.remove();
|
|
54
|
+
this.#contenedor = undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Envuelve un mapa de MapLibre en el puerto.
|
|
59
|
+
*
|
|
60
|
+
* No comprueba WebGL2 —eso lo hace `crearMapa` antes de llegar aquí— porque
|
|
61
|
+
* esta función también se usa desde el laboratorio con un mapa ya construido.
|
|
62
|
+
*/
|
|
63
|
+
export function adaptarMapLibre(opciones) {
|
|
64
|
+
// Antes del constructor, no después: `new Map` es lo que pide los workers
|
|
65
|
+
// al pool, y el pool solo lee `config.WORKER_URL` cuando todavía no los ha
|
|
66
|
+
// creado. Puesto una línea más abajo, esto no daría error ni aviso — y no
|
|
67
|
+
// haría nada.
|
|
68
|
+
if (opciones.urlDelWorker !== undefined)
|
|
69
|
+
fijarUrlDelWorker(opciones.urlDelWorker);
|
|
70
|
+
const mapa = new MapaDeMapLibre({
|
|
71
|
+
container: opciones.contenedor,
|
|
72
|
+
style: (opciones.estilo ?? ESTILO_VACIO),
|
|
73
|
+
center: [opciones.centro?.lon ?? -75.6961, opciones.centro?.lat ?? 4.8133],
|
|
74
|
+
zoom: opciones.zoom ?? 13,
|
|
75
|
+
...(opciones.zoomMinimo === undefined
|
|
76
|
+
? {}
|
|
77
|
+
: { minZoom: opciones.zoomMinimo }),
|
|
78
|
+
...(opciones.zoomMaximo === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: { maxZoom: opciones.zoomMaximo }),
|
|
81
|
+
// La atribución la trae el estilo, poblada desde los metadatos del
|
|
82
|
+
// archivo de teselas. `compact` porque a 390 px de ancho la versión larga
|
|
83
|
+
// se come el borde inferior del mapa.
|
|
84
|
+
attributionControl: { compact: true },
|
|
85
|
+
});
|
|
86
|
+
if (opciones.urlDeLicencias !== undefined) {
|
|
87
|
+
mapa.addControl(new ControlDeLicencias(opciones.urlDeLicencias), "bottom-right");
|
|
88
|
+
}
|
|
89
|
+
return new AdaptadorDeMapLibre(mapa);
|
|
90
|
+
}
|
|
91
|
+
class AdaptadorDeMapLibre {
|
|
92
|
+
#mapa;
|
|
93
|
+
#porDesuscribir = [];
|
|
94
|
+
#esperandoEstilo = [];
|
|
95
|
+
#destruido = false;
|
|
96
|
+
#estiloParseado = false;
|
|
97
|
+
constructor(mapa) {
|
|
98
|
+
this.#mapa = mapa;
|
|
99
|
+
this.#vigilarLaCargaDelEstilo();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* **`isStyleLoaded()` es `false` dentro de todos los `styledata`.** Por eso
|
|
103
|
+
* no sirve para decidir si ya se pueden añadir capas.
|
|
104
|
+
*
|
|
105
|
+
* El patrón que uno escribiría —escuchar `styledata` y montar solo cuando
|
|
106
|
+
* `isStyleLoaded()` sea cierto— **no monta nunca**. Y no falla: lo que se ve
|
|
107
|
+
* es el mapa base perfecto, ni un solo alfiler y cero errores en consola,
|
|
108
|
+
* que es la peor forma de romperse.
|
|
109
|
+
*
|
|
110
|
+
* Es lo único que se afirma aquí, y es lo único que aguantó. Se midió el
|
|
111
|
+
* 2026-09-07 diez veces con `addInitScript` sobre el taller, en dos páginas
|
|
112
|
+
* distintas y por dos personas: `isStyleLoaded()` dio `false` en **todos**
|
|
113
|
+
* los `styledata` de las diez, y `true` para cuando llegó `idle` en las
|
|
114
|
+
* diez.
|
|
115
|
+
*
|
|
116
|
+
* **Lo que varía, y por eso no se escribe como si fuera un umbral:** cuántos
|
|
117
|
+
* `styledata` llegan (entre uno y cuatro, y cambia entre corridas de la
|
|
118
|
+
* misma página), en qué milisegundo, y si `load` llega antes o después de
|
|
119
|
+
* que el estilo termine — en una página salió `false` en `load` las cinco
|
|
120
|
+
* veces y en la otra `true` las cinco. No depende de la máquina: las diez
|
|
121
|
+
* mediciones son del mismo Mac Mini. Depende de qué más tenga pendiente la
|
|
122
|
+
* página, que mueve `load` respecto de la carga del estilo. Un comentario
|
|
123
|
+
* que fijara cualquiera de esos números invitaría a construir encima de algo
|
|
124
|
+
* que no se repite.
|
|
125
|
+
*
|
|
126
|
+
* Y no lo causa el icono `generic_shield-2char` que el estilo pide sin
|
|
127
|
+
* sprite, aunque lo parezca por el aviso de consola: declarando el sprite,
|
|
128
|
+
* con cero imágenes faltantes, la secuencia es la misma; y sin él,
|
|
129
|
+
* `loaded()` y `areTilesLoaded()` dan `true` igual, o sea que MapLibre da la
|
|
130
|
+
* imagen por perdida y sigue. Queda desmentido por escrito porque una
|
|
131
|
+
* explicación falsa dentro de una guarda que funciona es peor que ninguna:
|
|
132
|
+
* la guarda aguanta, nadie vuelve a leerla hasta que la necesita, y ese día
|
|
133
|
+
* la cree.
|
|
134
|
+
*
|
|
135
|
+
* La señal correcta es el propio evento `styledata`: cuando llega, el estilo
|
|
136
|
+
* está parseado y `addSource` y `addLayer` funcionan. Es lo que vigila esta
|
|
137
|
+
* bandera.
|
|
138
|
+
*/
|
|
139
|
+
#vigilarLaCargaDelEstilo() {
|
|
140
|
+
const suscripcion = this.#mapa.on("styledata", () => {
|
|
141
|
+
this.#estiloParseado = true;
|
|
142
|
+
for (const resolver of this.#esperandoEstilo.splice(0))
|
|
143
|
+
resolver();
|
|
144
|
+
});
|
|
145
|
+
this.#porDesuscribir.push(() => {
|
|
146
|
+
suscripcion.unsubscribe();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
estaListo() {
|
|
150
|
+
return this.#estiloParseado;
|
|
151
|
+
}
|
|
152
|
+
async cuandoListo() {
|
|
153
|
+
if (this.estaListo())
|
|
154
|
+
return;
|
|
155
|
+
await new Promise((resolver) => this.#esperandoEstilo.push(resolver));
|
|
156
|
+
}
|
|
157
|
+
destruir() {
|
|
158
|
+
if (this.#destruido)
|
|
159
|
+
return;
|
|
160
|
+
this.#destruido = true;
|
|
161
|
+
for (const desuscribir of this.#porDesuscribir.splice(0))
|
|
162
|
+
desuscribir();
|
|
163
|
+
this.#mapa.remove();
|
|
164
|
+
}
|
|
165
|
+
redimensionar() {
|
|
166
|
+
this.#mapa.resize();
|
|
167
|
+
}
|
|
168
|
+
ponerEstilo(estilo) {
|
|
169
|
+
// El estilo nuevo todavía no está parseado: hasta que llegue su
|
|
170
|
+
// `styledata`, añadir una fuente fallaría con «Style is not done
|
|
171
|
+
// loading».
|
|
172
|
+
this.#estiloParseado = false;
|
|
173
|
+
this.#mapa.setStyle(estilo);
|
|
174
|
+
}
|
|
175
|
+
agregarFuenteGeoJSON(id, datos, opciones = {}) {
|
|
176
|
+
this.#mapa.addSource(id, {
|
|
177
|
+
type: "geojson",
|
|
178
|
+
data: datos,
|
|
179
|
+
...(opciones.agrupar === true ? { cluster: true } : {}),
|
|
180
|
+
...(opciones.radioDeGrupoEnPx === undefined
|
|
181
|
+
? {}
|
|
182
|
+
: { clusterRadius: opciones.radioDeGrupoEnPx }),
|
|
183
|
+
...(opciones.zoomMaximoDeGrupo === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: { clusterMaxZoom: opciones.zoomMaximoDeGrupo }),
|
|
186
|
+
// Sin esto, el estado de entidad no encuentra a quién aplicarse: los
|
|
187
|
+
// identificadores de GeoJSON son cadenas y MapLibre los quiere
|
|
188
|
+
// numéricos salvo que se promueva una propiedad.
|
|
189
|
+
...(opciones.promoverId === undefined
|
|
190
|
+
? {}
|
|
191
|
+
: { promoteId: opciones.promoverId }),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
ponerDatosDeFuente(id, datos) {
|
|
195
|
+
const fuente = this.#mapa.getSource(id);
|
|
196
|
+
// `getSource` devuelve `Source`, que no declara `setData`: la comprobación
|
|
197
|
+
// es estructural a propósito, porque comparar `fuente.type` contra
|
|
198
|
+
// "geojson" pasaría igual con una fuente de otro tipo mal declarada.
|
|
199
|
+
if (fuente === undefined || !("setData" in fuente))
|
|
200
|
+
return;
|
|
201
|
+
// En MapLibre 6 esto perdió `waitForCompletion` y ya no devuelve `this`,
|
|
202
|
+
// así que no dice cuándo aterrizó el dato. Quien necesite encuadrar justo
|
|
203
|
+
// después escucha `fuente:datos`.
|
|
204
|
+
void fuente.setData(datos);
|
|
205
|
+
}
|
|
206
|
+
tieneFuente(id) {
|
|
207
|
+
return this.#mapa.getSource(id) !== undefined;
|
|
208
|
+
}
|
|
209
|
+
agregarCapa(capa, antesDe) {
|
|
210
|
+
this.#mapa.addLayer(capa, antesDe);
|
|
211
|
+
}
|
|
212
|
+
quitarCapa(id) {
|
|
213
|
+
// `removeLayer` de una capa que no existe **lanza** en MapLibre. Se
|
|
214
|
+
// comprueba antes porque quitar lo que ya no está es una operación
|
|
215
|
+
// legítima cuando se está desmontando.
|
|
216
|
+
if (this.#mapa.getLayer(id) !== undefined)
|
|
217
|
+
this.#mapa.removeLayer(id);
|
|
218
|
+
}
|
|
219
|
+
tieneCapa(id) {
|
|
220
|
+
return this.#mapa.getLayer(id) !== undefined;
|
|
221
|
+
}
|
|
222
|
+
ponerEstadoDeEntidad(fuente, id, estado) {
|
|
223
|
+
if (!this.tieneFuente(fuente))
|
|
224
|
+
return;
|
|
225
|
+
this.#mapa.setFeatureState({ source: fuente, id }, estado);
|
|
226
|
+
}
|
|
227
|
+
limpiarEstadosDeEntidad(fuente) {
|
|
228
|
+
if (!this.tieneFuente(fuente))
|
|
229
|
+
return;
|
|
230
|
+
this.#mapa.removeFeatureState({ source: fuente });
|
|
231
|
+
}
|
|
232
|
+
agregarImagen(id, imagen) {
|
|
233
|
+
if (this.#mapa.hasImage(id))
|
|
234
|
+
return;
|
|
235
|
+
this.#mapa.addImage(id, {
|
|
236
|
+
width: imagen.ancho,
|
|
237
|
+
height: imagen.alto,
|
|
238
|
+
data: new Uint8Array(imagen.datos),
|
|
239
|
+
}, {
|
|
240
|
+
...(imagen.sdf === undefined ? {} : { sdf: imagen.sdf }),
|
|
241
|
+
...(imagen.escalaDePixeles === undefined
|
|
242
|
+
? {}
|
|
243
|
+
: { pixelRatio: imagen.escalaDePixeles }),
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
tieneImagen(id) {
|
|
247
|
+
return this.#mapa.hasImage(id);
|
|
248
|
+
}
|
|
249
|
+
vista() {
|
|
250
|
+
const centro = this.#mapa.getCenter();
|
|
251
|
+
const caja = this.#mapa.getBounds();
|
|
252
|
+
return {
|
|
253
|
+
centro: { lon: centro.lng, lat: centro.lat },
|
|
254
|
+
zoom: this.#mapa.getZoom(),
|
|
255
|
+
rumbo: this.#mapa.getBearing(),
|
|
256
|
+
inclinacion: this.#mapa.getPitch(),
|
|
257
|
+
caja: [caja.getWest(), caja.getSouth(), caja.getEast(), caja.getNorth()],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
moverCamara(destino, opciones) {
|
|
261
|
+
const camara = {
|
|
262
|
+
...(destino.centro === undefined
|
|
263
|
+
? {}
|
|
264
|
+
: {
|
|
265
|
+
center: [destino.centro.lon, destino.centro.lat],
|
|
266
|
+
}),
|
|
267
|
+
...(destino.zoom === undefined ? {} : { zoom: destino.zoom }),
|
|
268
|
+
...(destino.rumbo === undefined ? {} : { bearing: destino.rumbo }),
|
|
269
|
+
...(destino.inclinacion === undefined
|
|
270
|
+
? {}
|
|
271
|
+
: { pitch: destino.inclinacion }),
|
|
272
|
+
...(opciones.duracionEnMs === undefined
|
|
273
|
+
? {}
|
|
274
|
+
: { duration: opciones.duracionEnMs }),
|
|
275
|
+
};
|
|
276
|
+
const dato = datoDeEvento(opciones);
|
|
277
|
+
if (opciones.modo === "salto")
|
|
278
|
+
this.#mapa.jumpTo(camara, dato);
|
|
279
|
+
else if (opciones.modo === "vuelo")
|
|
280
|
+
this.#mapa.flyTo(camara, dato);
|
|
281
|
+
else
|
|
282
|
+
this.#mapa.easeTo(camara, dato);
|
|
283
|
+
}
|
|
284
|
+
encuadrarCaja(caja, opciones) {
|
|
285
|
+
const limites = [
|
|
286
|
+
[caja[0], caja[1]],
|
|
287
|
+
[caja[2], caja[3]],
|
|
288
|
+
];
|
|
289
|
+
this.#mapa.fitBounds(limites, {
|
|
290
|
+
...(opciones.margenes === undefined
|
|
291
|
+
? {}
|
|
292
|
+
: { padding: aRelleno(opciones.margenes) }),
|
|
293
|
+
...(opciones.zoomMaximo === undefined
|
|
294
|
+
? {}
|
|
295
|
+
: { maxZoom: opciones.zoomMaximo }),
|
|
296
|
+
...(opciones.duracionEnMs === undefined
|
|
297
|
+
? {}
|
|
298
|
+
: { duration: opciones.duracionEnMs }),
|
|
299
|
+
linear: opciones.modo === "suave",
|
|
300
|
+
}, datoDeEvento(opciones));
|
|
301
|
+
}
|
|
302
|
+
ponerMargenes(margenes) {
|
|
303
|
+
this.#mapa.setPadding(aRelleno(margenes));
|
|
304
|
+
}
|
|
305
|
+
ponerCajaMaxima(caja) {
|
|
306
|
+
this.#mapa.setMaxBounds(caja === undefined
|
|
307
|
+
? null
|
|
308
|
+
: [
|
|
309
|
+
[caja[0], caja[1]],
|
|
310
|
+
[caja[2], caja[3]],
|
|
311
|
+
]);
|
|
312
|
+
}
|
|
313
|
+
proyectar(coordenada) {
|
|
314
|
+
const punto = this.#mapa.project([coordenada.lon, coordenada.lat]);
|
|
315
|
+
return { x: punto.x, y: punto.y };
|
|
316
|
+
}
|
|
317
|
+
desproyectar(punto) {
|
|
318
|
+
const coordenada = this.#mapa.unproject([punto.x, punto.y]);
|
|
319
|
+
return { lon: coordenada.lng, lat: coordenada.lat };
|
|
320
|
+
}
|
|
321
|
+
entidadesEn(punto, capas) {
|
|
322
|
+
// Una capa que no está en el estilo hace que la consulta entera **lance**.
|
|
323
|
+
// Pasa de verdad: entre `setStyle` y el evento de estilo cargado, ninguna
|
|
324
|
+
// capa de Cerca existe todavía.
|
|
325
|
+
const existentes = capas.filter((capa) => this.tieneCapa(capa));
|
|
326
|
+
if (existentes.length === 0)
|
|
327
|
+
return [];
|
|
328
|
+
return this.#mapa
|
|
329
|
+
.queryRenderedFeatures([punto.x, punto.y], { layers: existentes })
|
|
330
|
+
.map(aEntidadDibujada);
|
|
331
|
+
}
|
|
332
|
+
al(tipo, manejador) {
|
|
333
|
+
const avisar = manejador;
|
|
334
|
+
switch (tipo) {
|
|
335
|
+
case "estilo:listo":
|
|
336
|
+
// Sin condición: cuando `styledata` llega, el estilo está parseado y
|
|
337
|
+
// se le pueden añadir fuentes y capas. Filtrarlo por
|
|
338
|
+
// `isStyleLoaded()` —que dentro de este evento siempre es `false`—
|
|
339
|
+
// deja al SDK sin montar para siempre; la medición está en
|
|
340
|
+
// `#vigilarLaCargaDelEstilo`.
|
|
341
|
+
return this.#suscribir("styledata", () => avisar(undefined));
|
|
342
|
+
case "fuente:datos":
|
|
343
|
+
return this.#suscribir("sourcedata", (evento) => {
|
|
344
|
+
const dato = evento;
|
|
345
|
+
if (dato.isSourceLoaded !== true || dato.sourceId === undefined)
|
|
346
|
+
return;
|
|
347
|
+
avisar({ fuente: dato.sourceId });
|
|
348
|
+
});
|
|
349
|
+
case "movimiento":
|
|
350
|
+
return this.#suscribir("move", (evento) => {
|
|
351
|
+
avisar({ origen: origenDe(evento), vista: this.vista() });
|
|
352
|
+
});
|
|
353
|
+
case "movimiento:fin":
|
|
354
|
+
return this.#suscribir("moveend", (evento) => {
|
|
355
|
+
avisar({ origen: origenDe(evento), vista: this.vista() });
|
|
356
|
+
});
|
|
357
|
+
case "zoom":
|
|
358
|
+
return this.#suscribir("zoomend", (evento) => {
|
|
359
|
+
avisar({ origen: origenDe(evento), vista: this.vista() });
|
|
360
|
+
});
|
|
361
|
+
case "clic":
|
|
362
|
+
return this.#suscribir("click", (evento) => {
|
|
363
|
+
avisar(aEventoDePuntero(evento));
|
|
364
|
+
});
|
|
365
|
+
case "error":
|
|
366
|
+
return this.#suscribir("error", (evento) => {
|
|
367
|
+
const dato = evento;
|
|
368
|
+
avisar({
|
|
369
|
+
error: dato.error ?? new Error("error del mapa sin detalle"),
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
default:
|
|
373
|
+
return () => { };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
alDeCapa(tipo, capas, manejador) {
|
|
377
|
+
const avisar = manejador;
|
|
378
|
+
const escuchar = (puntero) => {
|
|
379
|
+
const base = aEventoDePuntero(puntero);
|
|
380
|
+
if (tipo === "fuera") {
|
|
381
|
+
avisar(base);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
avisar({
|
|
385
|
+
...base,
|
|
386
|
+
entidades: (puntero.features ?? []).map(aEntidadDibujada),
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
// Una capa por suscripción, y no la sobrecarga que acepta la lista
|
|
390
|
+
// entera, porque el SDK vuelve a montar sus capas en cada cambio de
|
|
391
|
+
// estilo mientras estas suscripciones se hacen una sola vez: con la
|
|
392
|
+
// lista, una capa que todavía no existe se lleva por delante a las demás.
|
|
393
|
+
const suscripciones = [];
|
|
394
|
+
for (const capa of capas) {
|
|
395
|
+
if (tipo === "clic")
|
|
396
|
+
suscripciones.push(this.#mapa.on("click", capa, escuchar));
|
|
397
|
+
else if (tipo === "encima") {
|
|
398
|
+
suscripciones.push(this.#mapa.on("mousemove", capa, escuchar));
|
|
399
|
+
}
|
|
400
|
+
else
|
|
401
|
+
suscripciones.push(this.#mapa.on("mouseleave", capa, escuchar));
|
|
402
|
+
}
|
|
403
|
+
const soltar = () => {
|
|
404
|
+
for (const suscripcion of suscripciones.splice(0))
|
|
405
|
+
suscripcion.unsubscribe();
|
|
406
|
+
};
|
|
407
|
+
this.#porDesuscribir.push(soltar);
|
|
408
|
+
return soltar;
|
|
409
|
+
}
|
|
410
|
+
#suscribir(nativo, escuchador) {
|
|
411
|
+
// El tipo del evento depende del nombre, y aquí el nombre es una unión:
|
|
412
|
+
// TypeScript no puede escoger la sobrecarga sin resolverla primero. Cada
|
|
413
|
+
// llamador de arriba sí sabe qué evento le llega y lo estrecha; este
|
|
414
|
+
// reenvío es el único sitio donde hace falta cruzar por `unknown`, y está
|
|
415
|
+
// acotado a los siete nombres del parámetro.
|
|
416
|
+
const suscripcion = this.#mapa.on(nativo, escuchador);
|
|
417
|
+
const soltar = () => {
|
|
418
|
+
suscripcion.unsubscribe();
|
|
419
|
+
};
|
|
420
|
+
this.#porDesuscribir.push(soltar);
|
|
421
|
+
return soltar;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* La marca que viaja con un movimiento hecho por código.
|
|
426
|
+
*
|
|
427
|
+
* MapLibre reenvía este objeto dentro del evento resultante, así que no hace
|
|
428
|
+
* falta llevar un contador ni adivinar por `originalEvent`: el movimiento que
|
|
429
|
+
* lleva la marca lo hizo el SDK, y el que no, el usuario.
|
|
430
|
+
*/
|
|
431
|
+
function datoDeEvento(opciones) {
|
|
432
|
+
return opciones.origen === "codigo" ? { [MARCA_DE_ORIGEN]: "codigo" } : {};
|
|
433
|
+
}
|
|
434
|
+
function origenDe(evento) {
|
|
435
|
+
const dato = evento;
|
|
436
|
+
return dato?.[MARCA_DE_ORIGEN] === "codigo" ? "codigo" : "usuario";
|
|
437
|
+
}
|
|
438
|
+
function aEventoDePuntero(evento) {
|
|
439
|
+
return {
|
|
440
|
+
coordenada: { lon: evento.lngLat.lng, lat: evento.lngLat.lat },
|
|
441
|
+
punto: { x: evento.point.x, y: evento.point.y },
|
|
442
|
+
// `originalEvent` está cuando lo disparó un dedo o un ratón. Un evento
|
|
443
|
+
// sintetizado por el recorrido con teclado no lo trae, y quien lo escucha
|
|
444
|
+
// necesita poder decidir distinto.
|
|
445
|
+
desdePuntero: evento.originalEvent !== undefined,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
function aEntidadDibujada(entidad) {
|
|
449
|
+
return {
|
|
450
|
+
capa: entidad.layer.id,
|
|
451
|
+
id: entidad.id,
|
|
452
|
+
propiedades: entidad.properties ?? {},
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function aRelleno(margenes) {
|
|
456
|
+
return {
|
|
457
|
+
top: margenes.arriba,
|
|
458
|
+
bottom: margenes.abajo,
|
|
459
|
+
left: margenes.izquierda,
|
|
460
|
+
right: margenes.derecha,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
//# sourceMappingURL=maplibre.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"maplibre.js","sourceRoot":"","sources":["../../src/navegador/maplibre.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type GeoJSONSource,\n type IControl,\n type LngLatBoundsLike,\n Map as MapaDeMapLibre,\n type MapGeoJSONFeature,\n type MapLayerMouseEvent,\n type MapMouseEvent,\n type StyleSpecification,\n type Subscription,\n} from \"maplibre-gl\";\n\nimport type { Caja, ColeccionDeEntidades, Coordenada } from \"@cerca.red/geo\";\n\nimport type { CapaDelEstilo, EspecificacionDeEstilo } from \"../estilo.ts\";\nimport type {\n Desuscribir,\n DestinoDeCamara,\n EntidadDibujada,\n EventosDeCapa,\n EventosNativos,\n ImagenDeSimbolo,\n MapaNativo,\n Margenes,\n OpcionesDeEncuadre,\n OpcionesDeFuente,\n OpcionesDeMovimiento,\n PuntoEnPantalla,\n VistaDelMapa,\n} from \"../puerto.ts\";\nimport { fijarUrlDelWorker } from \"./worker.ts\";\n\n/**\n * El adaptador: la única zona del SDK que sabe qué es MapLibre.\n *\n * Todo lo que la versión 6 cambió respecto de la 5 vive aquí dentro, y por eso\n * el resto del paquete no se enteró:\n *\n * - **`map.transform` ya no existe.** La caja visible sale de `getBounds()`.\n * - **`setData()` perdió la espera y el retorno encadenable.** Ya no dice\n * cuándo aterrizó el dato; quien lo necesite escucha el evento de fuente.\n * - **El evento de datos se partió en dos tipos**, uno de fuente y otro de\n * estilo. Aquí se separan en `fuente:datos` y `estilo:listo`, que es lo que\n * el SDK pide.\n * - **Exige WebGL2 sin alternativa.** Se comprueba antes de construir nada.\n *\n * Cuando salga la 7, este archivo es la lista de lo que hay que revisar.\n */\n\n/**\n * Lo que `setData` acepta, sacado de la firma del propio MapLibre.\n *\n * Se deriva en vez de importarse de `geojson` para no añadir una dependencia\n * de tipos a un paquete que ya arrastra la suya: si MapLibre cambia lo que\n * acepta, esto cambia con él y el error sale aquí.\n */\ntype DatoDeFuenteGeoJSON = Parameters<GeoJSONSource[\"setData\"]>[0];\n\n/** El dato que viaja con un movimiento de cámara para poder reconocerlo después. */\nconst MARCA_DE_ORIGEN = \"origenCerca\";\n\ninterface DatoDeEvento {\n readonly [MARCA_DE_ORIGEN]?: \"codigo\";\n}\n\n/**\n * El estilo con el que nace el mapa: vacío.\n *\n * MapLibre exige un estilo en el constructor, pero el dueño del estilo es el\n * SDK: lo construye al arrancar y lo vuelve a construir en cada cambio de tema\n * o de idioma. Darle aquí el estilo de verdad y que el SDK lo pusiera otra vez\n * significa cargarlo dos veces, y MapLibre lo avisa por consola —«Style is not\n * done loading, rebuilding from scratch»— mientras descarga dos veces los\n * glifos.\n *\n * Un estilo vacío carga al instante y deja un solo dueño.\n */\nconst ESTILO_VACIO: EspecificacionDeEstilo = {\n version: 8,\n sources: {},\n layers: [],\n};\n\nexport interface OpcionesDelAdaptador {\n readonly contenedor: HTMLElement;\n /** Si no se da, el mapa nace vacío y el estilo lo pone el SDK. */\n readonly estilo?: EspecificacionDeEstilo;\n readonly centro?: Coordenada;\n readonly zoom?: number;\n readonly zoomMinimo?: number;\n readonly zoomMaximo?: number;\n /**\n * De dónde bajar `maplibre-gl-worker.mjs`, para los bundlers que reescriben\n * `import.meta.url` y dejan a MapLibre deduciendo mal esa URL.\n *\n * Se aplica **antes** de construir el mapa, y tiene que ser así: el pool de\n * workers se crea dentro del constructor y no vuelve a leer la URL mientras\n * quede un mapa vivo. El porqué entero está en `worker.ts`.\n *\n * Sin esta opción no se toca nada, que es lo correcto bajo Vite: ahí la\n * deducción por defecto funciona.\n */\n readonly urlDelWorker?: string;\n /**\n * Adónde lleva el enlace de licencias de la esquina inferior derecha.\n *\n * El control de atribución de MapLibre ya muestra el `© OpenStreetMap` que\n * viene en los metadatos del `.pmtiles` — eso es obligación legal y no se\n * toca. Este es un control aparte, más pequeño, para el documento que junta\n * esa atribución con el resto de la cadena de licencias (el perfil de\n * teselas, los proveedores). Sin esta opción no se añade nada.\n */\n readonly urlDeLicencias?: string;\n}\n\n/**\n * El control de MapLibre que lleva al documento de licencias.\n *\n * Un `IControl` y no un elemento suelto puesto por la superficie: así el\n * enlace vive dentro del área que MapLibre ya reserva para sus controles\n * (`bottom-right`, la misma esquina que la atribución) y se destruye solo\n * cuando el mapa se destruye, sin que quien use `packages/mapa` tenga que\n * acordarse de quitarlo.\n */\nclass ControlDeLicencias implements IControl {\n readonly #url: string;\n #contenedor: HTMLElement | undefined;\n\n constructor(url: string) {\n this.#url = url;\n }\n\n onAdd(): HTMLElement {\n const contenedor = document.createElement(\"div\");\n contenedor.className = \"maplibregl-ctrl\";\n\n const enlace = document.createElement(\"a\");\n enlace.href = this.#url;\n enlace.target = \"_blank\";\n enlace.rel = \"noopener noreferrer\";\n enlace.textContent = \"Licencias\";\n enlace.style.cssText =\n \"display:block;padding:0 4px;font:11px/16px sans-serif;\" +\n \"color:#333;text-decoration:none;background:rgba(255,255,255,.5);\";\n\n contenedor.append(enlace);\n this.#contenedor = contenedor;\n return contenedor;\n }\n\n onRemove(): void {\n this.#contenedor?.remove();\n this.#contenedor = undefined;\n }\n}\n\n/**\n * Envuelve un mapa de MapLibre en el puerto.\n *\n * No comprueba WebGL2 —eso lo hace `crearMapa` antes de llegar aquí— porque\n * esta función también se usa desde el laboratorio con un mapa ya construido.\n */\nexport function adaptarMapLibre(opciones: OpcionesDelAdaptador): MapaNativo {\n // Antes del constructor, no después: `new Map` es lo que pide los workers\n // al pool, y el pool solo lee `config.WORKER_URL` cuando todavía no los ha\n // creado. Puesto una línea más abajo, esto no daría error ni aviso — y no\n // haría nada.\n if (opciones.urlDelWorker !== undefined)\n fijarUrlDelWorker(opciones.urlDelWorker);\n\n const mapa = new MapaDeMapLibre({\n container: opciones.contenedor,\n style: (opciones.estilo ?? ESTILO_VACIO) as unknown as StyleSpecification,\n center: [opciones.centro?.lon ?? -75.6961, opciones.centro?.lat ?? 4.8133],\n zoom: opciones.zoom ?? 13,\n ...(opciones.zoomMinimo === undefined\n ? {}\n : { minZoom: opciones.zoomMinimo }),\n ...(opciones.zoomMaximo === undefined\n ? {}\n : { maxZoom: opciones.zoomMaximo }),\n // La atribución la trae el estilo, poblada desde los metadatos del\n // archivo de teselas. `compact` porque a 390 px de ancho la versión larga\n // se come el borde inferior del mapa.\n attributionControl: { compact: true },\n });\n\n if (opciones.urlDeLicencias !== undefined) {\n mapa.addControl(\n new ControlDeLicencias(opciones.urlDeLicencias),\n \"bottom-right\",\n );\n }\n\n return new AdaptadorDeMapLibre(mapa);\n}\n\nclass AdaptadorDeMapLibre implements MapaNativo {\n readonly #mapa: MapaDeMapLibre;\n readonly #porDesuscribir: Desuscribir[] = [];\n readonly #esperandoEstilo: Array<() => void> = [];\n #destruido = false;\n #estiloParseado = false;\n\n constructor(mapa: MapaDeMapLibre) {\n this.#mapa = mapa;\n this.#vigilarLaCargaDelEstilo();\n }\n\n /**\n * **`isStyleLoaded()` es `false` dentro de todos los `styledata`.** Por eso\n * no sirve para decidir si ya se pueden añadir capas.\n *\n * El patrón que uno escribiría —escuchar `styledata` y montar solo cuando\n * `isStyleLoaded()` sea cierto— **no monta nunca**. Y no falla: lo que se ve\n * es el mapa base perfecto, ni un solo alfiler y cero errores en consola,\n * que es la peor forma de romperse.\n *\n * Es lo único que se afirma aquí, y es lo único que aguantó. Se midió el\n * 2026-09-07 diez veces con `addInitScript` sobre el taller, en dos páginas\n * distintas y por dos personas: `isStyleLoaded()` dio `false` en **todos**\n * los `styledata` de las diez, y `true` para cuando llegó `idle` en las\n * diez.\n *\n * **Lo que varía, y por eso no se escribe como si fuera un umbral:** cuántos\n * `styledata` llegan (entre uno y cuatro, y cambia entre corridas de la\n * misma página), en qué milisegundo, y si `load` llega antes o después de\n * que el estilo termine — en una página salió `false` en `load` las cinco\n * veces y en la otra `true` las cinco. No depende de la máquina: las diez\n * mediciones son del mismo Mac Mini. Depende de qué más tenga pendiente la\n * página, que mueve `load` respecto de la carga del estilo. Un comentario\n * que fijara cualquiera de esos números invitaría a construir encima de algo\n * que no se repite.\n *\n * Y no lo causa el icono `generic_shield-2char` que el estilo pide sin\n * sprite, aunque lo parezca por el aviso de consola: declarando el sprite,\n * con cero imágenes faltantes, la secuencia es la misma; y sin él,\n * `loaded()` y `areTilesLoaded()` dan `true` igual, o sea que MapLibre da la\n * imagen por perdida y sigue. Queda desmentido por escrito porque una\n * explicación falsa dentro de una guarda que funciona es peor que ninguna:\n * la guarda aguanta, nadie vuelve a leerla hasta que la necesita, y ese día\n * la cree.\n *\n * La señal correcta es el propio evento `styledata`: cuando llega, el estilo\n * está parseado y `addSource` y `addLayer` funcionan. Es lo que vigila esta\n * bandera.\n */\n #vigilarLaCargaDelEstilo(): void {\n const suscripcion = this.#mapa.on(\"styledata\", () => {\n this.#estiloParseado = true;\n for (const resolver of this.#esperandoEstilo.splice(0)) resolver();\n });\n this.#porDesuscribir.push(() => {\n suscripcion.unsubscribe();\n });\n }\n\n estaListo(): boolean {\n return this.#estiloParseado;\n }\n\n async cuandoListo(): Promise<void> {\n if (this.estaListo()) return;\n await new Promise<void>((resolver) => this.#esperandoEstilo.push(resolver));\n }\n\n destruir(): void {\n if (this.#destruido) return;\n this.#destruido = true;\n for (const desuscribir of this.#porDesuscribir.splice(0)) desuscribir();\n this.#mapa.remove();\n }\n\n redimensionar(): void {\n this.#mapa.resize();\n }\n\n ponerEstilo(estilo: EspecificacionDeEstilo): void {\n // El estilo nuevo todavía no está parseado: hasta que llegue su\n // `styledata`, añadir una fuente fallaría con «Style is not done\n // loading».\n this.#estiloParseado = false;\n this.#mapa.setStyle(estilo as unknown as StyleSpecification);\n }\n\n agregarFuenteGeoJSON(\n id: string,\n datos: ColeccionDeEntidades,\n opciones: OpcionesDeFuente = {},\n ): void {\n this.#mapa.addSource(id, {\n type: \"geojson\",\n data: datos as unknown as DatoDeFuenteGeoJSON,\n ...(opciones.agrupar === true ? { cluster: true } : {}),\n ...(opciones.radioDeGrupoEnPx === undefined\n ? {}\n : { clusterRadius: opciones.radioDeGrupoEnPx }),\n ...(opciones.zoomMaximoDeGrupo === undefined\n ? {}\n : { clusterMaxZoom: opciones.zoomMaximoDeGrupo }),\n // Sin esto, el estado de entidad no encuentra a quién aplicarse: los\n // identificadores de GeoJSON son cadenas y MapLibre los quiere\n // numéricos salvo que se promueva una propiedad.\n ...(opciones.promoverId === undefined\n ? {}\n : { promoteId: opciones.promoverId }),\n });\n }\n\n ponerDatosDeFuente(id: string, datos: ColeccionDeEntidades): void {\n const fuente = this.#mapa.getSource(id);\n // `getSource` devuelve `Source`, que no declara `setData`: la comprobación\n // es estructural a propósito, porque comparar `fuente.type` contra\n // \"geojson\" pasaría igual con una fuente de otro tipo mal declarada.\n if (fuente === undefined || !(\"setData\" in fuente)) return;\n // En MapLibre 6 esto perdió `waitForCompletion` y ya no devuelve `this`,\n // así que no dice cuándo aterrizó el dato. Quien necesite encuadrar justo\n // después escucha `fuente:datos`.\n void (fuente as GeoJSONSource).setData(\n datos as unknown as DatoDeFuenteGeoJSON,\n );\n }\n\n tieneFuente(id: string): boolean {\n return this.#mapa.getSource(id) !== undefined;\n }\n\n agregarCapa(capa: CapaDelEstilo, antesDe?: string): void {\n this.#mapa.addLayer(\n capa as unknown as Parameters<MapaDeMapLibre[\"addLayer\"]>[0],\n antesDe,\n );\n }\n\n quitarCapa(id: string): void {\n // `removeLayer` de una capa que no existe **lanza** en MapLibre. Se\n // comprueba antes porque quitar lo que ya no está es una operación\n // legítima cuando se está desmontando.\n if (this.#mapa.getLayer(id) !== undefined) this.#mapa.removeLayer(id);\n }\n\n tieneCapa(id: string): boolean {\n return this.#mapa.getLayer(id) !== undefined;\n }\n\n ponerEstadoDeEntidad(\n fuente: string,\n id: string | number,\n estado: Record<string, unknown>,\n ): void {\n if (!this.tieneFuente(fuente)) return;\n this.#mapa.setFeatureState({ source: fuente, id }, estado);\n }\n\n limpiarEstadosDeEntidad(fuente: string): void {\n if (!this.tieneFuente(fuente)) return;\n this.#mapa.removeFeatureState({ source: fuente });\n }\n\n agregarImagen(id: string, imagen: ImagenDeSimbolo): void {\n if (this.#mapa.hasImage(id)) return;\n this.#mapa.addImage(\n id,\n {\n width: imagen.ancho,\n height: imagen.alto,\n data: new Uint8Array(imagen.datos),\n },\n {\n ...(imagen.sdf === undefined ? {} : { sdf: imagen.sdf }),\n ...(imagen.escalaDePixeles === undefined\n ? {}\n : { pixelRatio: imagen.escalaDePixeles }),\n },\n );\n }\n\n tieneImagen(id: string): boolean {\n return this.#mapa.hasImage(id);\n }\n\n vista(): VistaDelMapa {\n const centro = this.#mapa.getCenter();\n const caja = this.#mapa.getBounds();\n return {\n centro: { lon: centro.lng, lat: centro.lat },\n zoom: this.#mapa.getZoom(),\n rumbo: this.#mapa.getBearing(),\n inclinacion: this.#mapa.getPitch(),\n caja: [caja.getWest(), caja.getSouth(), caja.getEast(), caja.getNorth()],\n };\n }\n\n moverCamara(destino: DestinoDeCamara, opciones: OpcionesDeMovimiento): void {\n const camara = {\n ...(destino.centro === undefined\n ? {}\n : {\n center: [destino.centro.lon, destino.centro.lat] as [\n number,\n number,\n ],\n }),\n ...(destino.zoom === undefined ? {} : { zoom: destino.zoom }),\n ...(destino.rumbo === undefined ? {} : { bearing: destino.rumbo }),\n ...(destino.inclinacion === undefined\n ? {}\n : { pitch: destino.inclinacion }),\n ...(opciones.duracionEnMs === undefined\n ? {}\n : { duration: opciones.duracionEnMs }),\n };\n\n const dato = datoDeEvento(opciones);\n if (opciones.modo === \"salto\") this.#mapa.jumpTo(camara, dato);\n else if (opciones.modo === \"vuelo\") this.#mapa.flyTo(camara, dato);\n else this.#mapa.easeTo(camara, dato);\n }\n\n encuadrarCaja(caja: Caja, opciones: OpcionesDeEncuadre): void {\n const limites: LngLatBoundsLike = [\n [caja[0], caja[1]],\n [caja[2], caja[3]],\n ];\n this.#mapa.fitBounds(\n limites,\n {\n ...(opciones.margenes === undefined\n ? {}\n : { padding: aRelleno(opciones.margenes) }),\n ...(opciones.zoomMaximo === undefined\n ? {}\n : { maxZoom: opciones.zoomMaximo }),\n ...(opciones.duracionEnMs === undefined\n ? {}\n : { duration: opciones.duracionEnMs }),\n linear: opciones.modo === \"suave\",\n },\n datoDeEvento(opciones),\n );\n }\n\n ponerMargenes(margenes: Margenes): void {\n this.#mapa.setPadding(aRelleno(margenes));\n }\n\n ponerCajaMaxima(caja: Caja | undefined): void {\n this.#mapa.setMaxBounds(\n caja === undefined\n ? null\n : ([\n [caja[0], caja[1]],\n [caja[2], caja[3]],\n ] as LngLatBoundsLike),\n );\n }\n\n proyectar(coordenada: Coordenada): PuntoEnPantalla {\n const punto = this.#mapa.project([coordenada.lon, coordenada.lat]);\n return { x: punto.x, y: punto.y };\n }\n\n desproyectar(punto: PuntoEnPantalla): Coordenada {\n const coordenada = this.#mapa.unproject([punto.x, punto.y]);\n return { lon: coordenada.lng, lat: coordenada.lat };\n }\n\n entidadesEn(\n punto: PuntoEnPantalla,\n capas: readonly string[],\n ): readonly EntidadDibujada[] {\n // Una capa que no está en el estilo hace que la consulta entera **lance**.\n // Pasa de verdad: entre `setStyle` y el evento de estilo cargado, ninguna\n // capa de Cerca existe todavía.\n const existentes = capas.filter((capa) => this.tieneCapa(capa));\n if (existentes.length === 0) return [];\n\n return this.#mapa\n .queryRenderedFeatures([punto.x, punto.y], { layers: existentes })\n .map(aEntidadDibujada);\n }\n\n al<T extends keyof EventosNativos>(\n tipo: T,\n manejador: (evento: EventosNativos[T]) => void,\n ): Desuscribir {\n const avisar = manejador as (evento: unknown) => void;\n\n switch (tipo) {\n case \"estilo:listo\":\n // Sin condición: cuando `styledata` llega, el estilo está parseado y\n // se le pueden añadir fuentes y capas. Filtrarlo por\n // `isStyleLoaded()` —que dentro de este evento siempre es `false`—\n // deja al SDK sin montar para siempre; la medición está en\n // `#vigilarLaCargaDelEstilo`.\n return this.#suscribir(\"styledata\", () => avisar(undefined));\n\n case \"fuente:datos\":\n return this.#suscribir(\"sourcedata\", (evento: unknown) => {\n const dato = evento as {\n sourceId?: string;\n isSourceLoaded?: boolean;\n };\n if (dato.isSourceLoaded !== true || dato.sourceId === undefined)\n return;\n avisar({ fuente: dato.sourceId });\n });\n\n case \"movimiento\":\n return this.#suscribir(\"move\", (evento: unknown) => {\n avisar({ origen: origenDe(evento), vista: this.vista() });\n });\n\n case \"movimiento:fin\":\n return this.#suscribir(\"moveend\", (evento: unknown) => {\n avisar({ origen: origenDe(evento), vista: this.vista() });\n });\n\n case \"zoom\":\n return this.#suscribir(\"zoomend\", (evento: unknown) => {\n avisar({ origen: origenDe(evento), vista: this.vista() });\n });\n\n case \"clic\":\n return this.#suscribir(\"click\", (evento: unknown) => {\n avisar(aEventoDePuntero(evento as MapMouseEvent));\n });\n\n case \"error\":\n return this.#suscribir(\"error\", (evento: unknown) => {\n const dato = evento as { error?: Error };\n avisar({\n error: dato.error ?? new Error(\"error del mapa sin detalle\"),\n });\n });\n\n default:\n return () => {};\n }\n }\n\n alDeCapa<T extends keyof EventosDeCapa>(\n tipo: T,\n capas: readonly string[],\n manejador: (evento: EventosDeCapa[T]) => void,\n ): Desuscribir {\n const avisar = manejador as (evento: unknown) => void;\n\n const escuchar = (puntero: MapLayerMouseEvent) => {\n const base = aEventoDePuntero(puntero);\n if (tipo === \"fuera\") {\n avisar(base);\n return;\n }\n avisar({\n ...base,\n entidades: (puntero.features ?? []).map(aEntidadDibujada),\n });\n };\n\n // Una capa por suscripción, y no la sobrecarga que acepta la lista\n // entera, porque el SDK vuelve a montar sus capas en cada cambio de\n // estilo mientras estas suscripciones se hacen una sola vez: con la\n // lista, una capa que todavía no existe se lleva por delante a las demás.\n const suscripciones: Subscription[] = [];\n for (const capa of capas) {\n if (tipo === \"clic\")\n suscripciones.push(this.#mapa.on(\"click\", capa, escuchar));\n else if (tipo === \"encima\") {\n suscripciones.push(this.#mapa.on(\"mousemove\", capa, escuchar));\n } else suscripciones.push(this.#mapa.on(\"mouseleave\", capa, escuchar));\n }\n\n const soltar = () => {\n for (const suscripcion of suscripciones.splice(0))\n suscripcion.unsubscribe();\n };\n this.#porDesuscribir.push(soltar);\n return soltar;\n }\n\n #suscribir(\n nativo:\n | \"styledata\"\n | \"sourcedata\"\n | \"move\"\n | \"moveend\"\n | \"zoomend\"\n | \"click\"\n | \"error\",\n escuchador: (evento: unknown) => void,\n ): Desuscribir {\n // El tipo del evento depende del nombre, y aquí el nombre es una unión:\n // TypeScript no puede escoger la sobrecarga sin resolverla primero. Cada\n // llamador de arriba sí sabe qué evento le llega y lo estrecha; este\n // reenvío es el único sitio donde hace falta cruzar por `unknown`, y está\n // acotado a los siete nombres del parámetro.\n const suscripcion = this.#mapa.on(\n nativo as \"click\",\n escuchador as (evento: MapMouseEvent) => void,\n );\n const soltar = () => {\n suscripcion.unsubscribe();\n };\n this.#porDesuscribir.push(soltar);\n return soltar;\n }\n}\n\n/**\n * La marca que viaja con un movimiento hecho por código.\n *\n * MapLibre reenvía este objeto dentro del evento resultante, así que no hace\n * falta llevar un contador ni adivinar por `originalEvent`: el movimiento que\n * lleva la marca lo hizo el SDK, y el que no, el usuario.\n */\nfunction datoDeEvento(opciones: OpcionesDeMovimiento): DatoDeEvento {\n return opciones.origen === \"codigo\" ? { [MARCA_DE_ORIGEN]: \"codigo\" } : {};\n}\n\nfunction origenDe(evento: unknown): \"usuario\" | \"codigo\" {\n const dato = evento as DatoDeEvento | undefined;\n return dato?.[MARCA_DE_ORIGEN] === \"codigo\" ? \"codigo\" : \"usuario\";\n}\n\nfunction aEventoDePuntero(evento: MapMouseEvent): {\n coordenada: Coordenada;\n punto: PuntoEnPantalla;\n desdePuntero: boolean;\n} {\n return {\n coordenada: { lon: evento.lngLat.lng, lat: evento.lngLat.lat },\n punto: { x: evento.point.x, y: evento.point.y },\n // `originalEvent` está cuando lo disparó un dedo o un ratón. Un evento\n // sintetizado por el recorrido con teclado no lo trae, y quien lo escucha\n // necesita poder decidir distinto.\n desdePuntero: evento.originalEvent !== undefined,\n };\n}\n\nfunction aEntidadDibujada(entidad: MapGeoJSONFeature): EntidadDibujada {\n return {\n capa: entidad.layer.id,\n id: entidad.id,\n propiedades: entidad.properties ?? {},\n };\n}\n\nfunction aRelleno(margenes: Margenes): {\n top: number;\n bottom: number;\n left: number;\n right: number;\n} {\n return {\n top: margenes.arriba,\n bottom: margenes.abajo,\n left: margenes.izquierda,\n right: margenes.derecha,\n };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Registra el protocolo si hace falta. Devuelve cómo soltarlo. */
|
|
2
|
+
export declare function registrarProtocoloPmtiles(): () => void;
|
|
3
|
+
/** Cuántos mapas vivos están usando el protocolo. Existe para poder comprobarlo. */
|
|
4
|
+
export declare function cuantosUsanElProtocolo(): number;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { addProtocol, removeProtocol } from "maplibre-gl";
|
|
2
|
+
import { Protocol } from "pmtiles";
|
|
3
|
+
/**
|
|
4
|
+
* El protocolo `pmtiles://`, registrado una sola vez.
|
|
5
|
+
*
|
|
6
|
+
* ## Por qué hace falta contar
|
|
7
|
+
*
|
|
8
|
+
* `addProtocol` es **global del módulo de MapLibre**, no del mapa. Con dos
|
|
9
|
+
* mapas en la misma página —la ficha de un negocio abierta sobre la pantalla
|
|
10
|
+
* de resultados— registrarlo dos veces no falla, pero el segundo `remove` del
|
|
11
|
+
* primero que se desmonte dejaría al otro sin protocolo, y el mapa que queda
|
|
12
|
+
* se pone en blanco sin ningún error.
|
|
13
|
+
*
|
|
14
|
+
* Así que se cuenta: el primero registra, el último quita.
|
|
15
|
+
*
|
|
16
|
+
* ## Por qué `metadata: true` no es opcional
|
|
17
|
+
*
|
|
18
|
+
* Cuesta una petición extra y es **lo que puebla la atribución** desde dentro
|
|
19
|
+
* del archivo. La atribución de OpenStreetMap es obligación legal (CLAUDE.md
|
|
20
|
+
* §11), y la alternativa —teclearla en cada superficie— termina con una
|
|
21
|
+
* superficie que se quedó sin ella y nadie lo notó.
|
|
22
|
+
*
|
|
23
|
+
* Los metadatos del archivo la traen literal:
|
|
24
|
+
* `<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap</a>`.
|
|
25
|
+
*/
|
|
26
|
+
const NOMBRE = "pmtiles";
|
|
27
|
+
let cuantosLoUsan = 0;
|
|
28
|
+
let protocolo;
|
|
29
|
+
/** Registra el protocolo si hace falta. Devuelve cómo soltarlo. */
|
|
30
|
+
export function registrarProtocoloPmtiles() {
|
|
31
|
+
if (cuantosLoUsan === 0) {
|
|
32
|
+
protocolo = new Protocol({ metadata: true });
|
|
33
|
+
addProtocol(NOMBRE, protocolo.tile);
|
|
34
|
+
}
|
|
35
|
+
cuantosLoUsan += 1;
|
|
36
|
+
let yaSoltado = false;
|
|
37
|
+
return () => {
|
|
38
|
+
// Idempotente: un componente de React que se desmonta dos veces en modo
|
|
39
|
+
// estricto llamaría a esto dos veces, y sin la guarda el contador bajaría
|
|
40
|
+
// de más y el siguiente mapa se quedaría sin protocolo.
|
|
41
|
+
if (yaSoltado)
|
|
42
|
+
return;
|
|
43
|
+
yaSoltado = true;
|
|
44
|
+
cuantosLoUsan -= 1;
|
|
45
|
+
if (cuantosLoUsan > 0)
|
|
46
|
+
return;
|
|
47
|
+
removeProtocol(NOMBRE);
|
|
48
|
+
protocolo = undefined;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Cuántos mapas vivos están usando el protocolo. Existe para poder comprobarlo. */
|
|
52
|
+
export function cuantosUsanElProtocolo() {
|
|
53
|
+
return cuantosLoUsan;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=protocolo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocolo.js","sourceRoot":"","sources":["../../src/navegador/protocolo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,MAAM,GAAG,SAAS,CAAC;AAEzB,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB,IAAI,SAA+B,CAAC;AAEpC,mEAAmE;AACnE,MAAM,UAAU,yBAAyB;IACvC,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;QACxB,SAAS,GAAG,IAAI,QAAQ,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,aAAa,IAAI,CAAC,CAAC;IAEnB,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,OAAO,GAAG,EAAE;QACV,wEAAwE;QACxE,0EAA0E;QAC1E,wDAAwD;QACxD,IAAI,SAAS;YAAE,OAAO;QACtB,SAAS,GAAG,IAAI,CAAC;QACjB,aAAa,IAAI,CAAC,CAAC;QACnB,IAAI,aAAa,GAAG,CAAC;YAAE,OAAO;QAC9B,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,SAAS,GAAG,SAAS,CAAC;IACxB,CAAC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,sBAAsB;IACpC,OAAO,aAAa,CAAC;AACvB,CAAC","sourcesContent":["import { addProtocol, removeProtocol } from \"maplibre-gl\";\nimport { Protocol } from \"pmtiles\";\n\n/**\n * El protocolo `pmtiles://`, registrado una sola vez.\n *\n * ## Por qué hace falta contar\n *\n * `addProtocol` es **global del módulo de MapLibre**, no del mapa. Con dos\n * mapas en la misma página —la ficha de un negocio abierta sobre la pantalla\n * de resultados— registrarlo dos veces no falla, pero el segundo `remove` del\n * primero que se desmonte dejaría al otro sin protocolo, y el mapa que queda\n * se pone en blanco sin ningún error.\n *\n * Así que se cuenta: el primero registra, el último quita.\n *\n * ## Por qué `metadata: true` no es opcional\n *\n * Cuesta una petición extra y es **lo que puebla la atribución** desde dentro\n * del archivo. La atribución de OpenStreetMap es obligación legal (CLAUDE.md\n * §11), y la alternativa —teclearla en cada superficie— termina con una\n * superficie que se quedó sin ella y nadie lo notó.\n *\n * Los metadatos del archivo la traen literal:\n * `<a href=\"https://www.openstreetmap.org/copyright\">© OpenStreetMap</a>`.\n */\n\nconst NOMBRE = \"pmtiles\";\n\nlet cuantosLoUsan = 0;\nlet protocolo: Protocol | undefined;\n\n/** Registra el protocolo si hace falta. Devuelve cómo soltarlo. */\nexport function registrarProtocoloPmtiles(): () => void {\n if (cuantosLoUsan === 0) {\n protocolo = new Protocol({ metadata: true });\n addProtocol(NOMBRE, protocolo.tile);\n }\n cuantosLoUsan += 1;\n\n let yaSoltado = false;\n return () => {\n // Idempotente: un componente de React que se desmonta dos veces en modo\n // estricto llamaría a esto dos veces, y sin la guarda el contador bajaría\n // de más y el siguiente mapa se quedaría sin protocolo.\n if (yaSoltado) return;\n yaSoltado = true;\n cuantosLoUsan -= 1;\n if (cuantosLoUsan > 0) return;\n removeProtocol(NOMBRE);\n protocolo = undefined;\n };\n}\n\n/** Cuántos mapas vivos están usando el protocolo. Existe para poder comprobarlo. */\nexport function cuantosUsanElProtocolo(): number {\n return cuantosLoUsan;\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Si este navegador puede dibujar el mapa.
|
|
3
|
+
*
|
|
4
|
+
* ## Por qué se pregunta y no se intenta
|
|
5
|
+
*
|
|
6
|
+
* MapLibre 6 **exige WebGL2**. No hay camino alterno por WebGL1: la versión
|
|
7
|
+
* anterior lo tenía y esta no. Cuando no hay contexto, lo que se ve no es un
|
|
8
|
+
* error — es un canvas en blanco, sin excepción y sin mensaje, que es la peor
|
|
9
|
+
* forma de romperse porque parece que el mapa está cargando.
|
|
10
|
+
*
|
|
11
|
+
* SDD §13 pide la degradación mapa→lista, y esta función es lo que la hace
|
|
12
|
+
* posible: se pregunta **antes** de construir nada, y quien pregunta puede
|
|
13
|
+
* enseñar la lista en vez de un rectángulo vacío.
|
|
14
|
+
*
|
|
15
|
+
* El contexto de prueba se libera en el mismo momento. Un navegador tiene un
|
|
16
|
+
* número pequeño de contextos WebGL simultáneos —del orden de 16—, y dejar
|
|
17
|
+
* uno colgado por cada comprobación es como se agota el presupuesto de la
|
|
18
|
+
* pantalla que sí quería dibujar.
|
|
19
|
+
*/
|
|
20
|
+
export declare function hayWebGL2(): boolean;
|
|
21
|
+
/** El error que lanza `crearMapa` cuando no hay dónde dibujar. */
|
|
22
|
+
export declare class SinWebGL2 extends Error {
|
|
23
|
+
constructor();
|
|
24
|
+
}
|