@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.
Files changed (68) hide show
  1. package/README.md +133 -0
  2. package/dist/capas.d.ts +29 -0
  3. package/dist/capas.js +489 -0
  4. package/dist/capas.js.map +1 -0
  5. package/dist/doble/estiloFalso.d.ts +19 -0
  6. package/dist/doble/estiloFalso.js +33 -0
  7. package/dist/doble/estiloFalso.js.map +1 -0
  8. package/dist/doble/index.d.ts +11 -0
  9. package/dist/doble/index.js +12 -0
  10. package/dist/doble/index.js.map +1 -0
  11. package/dist/doble/mapaFalso.d.ts +75 -0
  12. package/dist/doble/mapaFalso.js +535 -0
  13. package/dist/doble/mapaFalso.js.map +1 -0
  14. package/dist/encuadre.d.ts +64 -0
  15. package/dist/encuadre.js +83 -0
  16. package/dist/encuadre.js.map +1 -0
  17. package/dist/estilo.d.ts +88 -0
  18. package/dist/estilo.js +23 -0
  19. package/dist/estilo.js.map +1 -0
  20. package/dist/eventos.d.ts +127 -0
  21. package/dist/eventos.js +69 -0
  22. package/dist/eventos.js.map +1 -0
  23. package/dist/identificadores.d.ts +109 -0
  24. package/dist/identificadores.js +104 -0
  25. package/dist/identificadores.js.map +1 -0
  26. package/dist/index.d.ts +37 -0
  27. package/dist/index.js +33 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/manifiesto.d.ts +97 -0
  30. package/dist/manifiesto.js +156 -0
  31. package/dist/manifiesto.js.map +1 -0
  32. package/dist/modelo.d.ts +154 -0
  33. package/dist/modelo.js +227 -0
  34. package/dist/modelo.js.map +1 -0
  35. package/dist/navegador/index.d.ts +70 -0
  36. package/dist/navegador/index.js +132 -0
  37. package/dist/navegador/index.js.map +1 -0
  38. package/dist/navegador/maplibre.d.ts +41 -0
  39. package/dist/navegador/maplibre.js +463 -0
  40. package/dist/navegador/maplibre.js.map +1 -0
  41. package/dist/navegador/protocolo.d.ts +4 -0
  42. package/dist/navegador/protocolo.js +55 -0
  43. package/dist/navegador/protocolo.js.map +1 -0
  44. package/dist/navegador/webgl.d.ts +24 -0
  45. package/dist/navegador/webgl.js +47 -0
  46. package/dist/navegador/webgl.js.map +1 -0
  47. package/dist/navegador/worker.d.ts +33 -0
  48. package/dist/navegador/worker.js +47 -0
  49. package/dist/navegador/worker.js.map +1 -0
  50. package/dist/paleta.d.ts +53 -0
  51. package/dist/paleta.js +53 -0
  52. package/dist/paleta.js.map +1 -0
  53. package/dist/proyecciones.d.ts +160 -0
  54. package/dist/proyecciones.js +2 -0
  55. package/dist/proyecciones.js.map +1 -0
  56. package/dist/puerto.d.ts +244 -0
  57. package/dist/puerto.js +2 -0
  58. package/dist/puerto.js.map +1 -0
  59. package/dist/sdk.d.ts +111 -0
  60. package/dist/sdk.js +552 -0
  61. package/dist/sdk.js.map +1 -0
  62. package/dist/sincronizacion.d.ts +40 -0
  63. package/dist/sincronizacion.js +144 -0
  64. package/dist/sincronizacion.js.map +1 -0
  65. package/dist/worker-de-maplibre.d.ts +36 -0
  66. package/dist/worker-de-maplibre.js +37 -0
  67. package/dist/worker-de-maplibre.js.map +1 -0
  68. package/package.json +59 -0
@@ -0,0 +1,535 @@
1
+ /**
2
+ * Un motor de mapas falso, con la geometría de verdad.
3
+ *
4
+ * ## Qué finge y qué no
5
+ *
6
+ * **No finge la proyección.** Web Mercator está implementada aquí igual que
7
+ * en MapLibre, así que `proyectar` y `desproyectar` dan los mismos píxeles.
8
+ * Eso es lo que permite comprobar en Node cosas que parecerían necesitar una
9
+ * GPU: que un toque a 20 px del centro de un alfiler lo alcanza y uno a 30 no,
10
+ * o que después de encuadrar una caja los tres negocios caen dentro de la
11
+ * pantalla.
12
+ *
13
+ * **No finge WebGL**, y por eso no dibuja: no hay framebuffer, no hay
14
+ * antialiasing, no hay orden de rasterizado. Lo que este doble responde a una
15
+ * consulta de entidades sale de recorrer las fuentes y medir distancias, que
16
+ * es una aproximación honesta del índice espacial de MapLibre y no lo mismo.
17
+ *
18
+ * **No aplica rumbo ni inclinación** a la proyección. El mapa de Cerca es
19
+ * cenital y sin girar; el día que alguien gire la cámara, esta clase miente y
20
+ * hay que arreglarla. Queda dicho aquí para que se encuentre.
21
+ *
22
+ * ## Por qué se publica en vez de quedarse en las pruebas
23
+ *
24
+ * Porque las superficies también necesitan probar sus pantallas sin GPU. Una
25
+ * PWA que pruebe su lista de resultados no debería tener que levantar WebGL
26
+ * para comprobar que al tocar un resultado el mapa recibió la orden de
27
+ * seleccionarlo. Entra por `@cerca.red/mapa/doble`, que es SSR-seguro como
28
+ * todo lo que no está bajo `navegador/`.
29
+ */
30
+ const TAMANIO_DE_TESELA = 512;
31
+ export class MapaFalso {
32
+ #listo;
33
+ #destruido = false;
34
+ #estilo;
35
+ #centro;
36
+ #zoom;
37
+ #rumbo = 0;
38
+ #inclinacion = 0;
39
+ #margenes = { arriba: 0, derecha: 0, abajo: 0, izquierda: 0 };
40
+ #cajaMaxima;
41
+ #ancho;
42
+ #alto;
43
+ #fuentes = new Map();
44
+ #capas = new Map();
45
+ #ordenDeCapas = [];
46
+ #imagenes = new Map();
47
+ #oyentes = new Map();
48
+ #oyentesDeCapa = new Map();
49
+ #esperandoListo = [];
50
+ /** Lo que se le pidió a la cámara, en orden. Es lo que se afirma en las pruebas. */
51
+ movimientos = [];
52
+ /** Cuántas veces se reemplazó el estilo. Un cambio de tema es uno. */
53
+ cambiosDeEstilo = 0;
54
+ constructor(opciones = {}) {
55
+ this.#centro = opciones.centro ?? { lon: -75.6961, lat: 4.8133 };
56
+ this.#zoom = opciones.zoom ?? 13;
57
+ this.#ancho = opciones.ancho ?? 390;
58
+ this.#alto = opciones.alto ?? 844;
59
+ this.#listo = opciones.listoDeInmediato ?? true;
60
+ }
61
+ // — lo que solo existe en el doble —
62
+ /** Marca el estilo como cargado y avisa, como haría el motor al terminar. */
63
+ terminarDeCargar() {
64
+ if (this.#listo)
65
+ return;
66
+ this.#listo = true;
67
+ for (const resolver of this.#esperandoListo.splice(0))
68
+ resolver();
69
+ this.emitirNativo("estilo:listo", undefined);
70
+ }
71
+ /** Emite un evento como si viniera del motor. */
72
+ emitirNativo(tipo, evento) {
73
+ for (const manejador of [...(this.#oyentes.get(tipo) ?? [])]) {
74
+ manejador(evento);
75
+ }
76
+ }
77
+ /** Emite un evento de capa, como si el usuario hubiera tocado ahí. */
78
+ emitirDeCapa(tipo, capa, evento) {
79
+ for (const suscripcion of [...(this.#oyentesDeCapa.get(tipo) ?? [])]) {
80
+ if (!suscripcion.capas.includes(capa))
81
+ continue;
82
+ suscripcion.manejador(evento);
83
+ }
84
+ }
85
+ /** Simula un toque: consulta lo que hay debajo y avisa a quien escuche esa capa. */
86
+ tocarEn(punto, desdePuntero = true) {
87
+ const coordenada = this.desproyectar(punto);
88
+ const capasConOyentes = new Set();
89
+ for (const suscripcion of this.#oyentesDeCapa.get("clic") ?? []) {
90
+ for (const capa of suscripcion.capas)
91
+ capasConOyentes.add(capa);
92
+ }
93
+ // Se consultan todas a la vez porque `entidadesEn` ya recorre de arriba
94
+ // hacia abajo, como MapLibre. Consultarlas de una en una las visitaria en
95
+ // el orden en que alguien se suscribió, que no tiene nada que ver con
96
+ // cuál está encima.
97
+ const entidades = this.entidadesEn(punto, [...capasConOyentes]);
98
+ const primera = entidades[0];
99
+ if (primera !== undefined) {
100
+ this.emitirDeCapa("clic", primera.capa, {
101
+ coordenada,
102
+ punto,
103
+ desdePuntero,
104
+ entidades: entidades.filter((cada) => cada.capa === primera.capa),
105
+ });
106
+ return;
107
+ }
108
+ this.emitirNativo("clic", { coordenada, punto, desdePuntero });
109
+ }
110
+ /** El estado de entidad puesto sobre una entidad. Para comprobar el resalte. */
111
+ estadoDeEntidad(fuente, id) {
112
+ return this.#fuentes.get(fuente)?.estados.get(id);
113
+ }
114
+ /** Los datos que tiene una fuente ahora mismo. */
115
+ datosDeFuente(id) {
116
+ return this.#fuentes.get(id)?.datos;
117
+ }
118
+ /** Los identificadores de capa, en el orden en que quedaron. */
119
+ ordenDeCapas() {
120
+ return [...this.#ordenDeCapas];
121
+ }
122
+ /** Cuántas suscripciones vivas quedan. Es lo que comprueba que `destruir` limpió. */
123
+ cuantosOyentes() {
124
+ let total = 0;
125
+ for (const oyentes of this.#oyentes.values())
126
+ total += oyentes.size;
127
+ for (const oyentes of this.#oyentesDeCapa.values())
128
+ total += oyentes.size;
129
+ return total;
130
+ }
131
+ estaDestruido() {
132
+ return this.#destruido;
133
+ }
134
+ // — MapaNativo —
135
+ estaListo() {
136
+ return this.#listo;
137
+ }
138
+ async cuandoListo() {
139
+ if (this.#listo)
140
+ return;
141
+ await new Promise((resolver) => this.#esperandoListo.push(resolver));
142
+ }
143
+ destruir() {
144
+ this.#destruido = true;
145
+ this.#oyentes.clear();
146
+ this.#oyentesDeCapa.clear();
147
+ this.#fuentes.clear();
148
+ this.#capas.clear();
149
+ this.#ordenDeCapas.length = 0;
150
+ }
151
+ redimensionar() {
152
+ // No hay contenedor que medir.
153
+ }
154
+ ponerEstilo(estilo) {
155
+ this.#estilo = estilo;
156
+ this.cambiosDeEstilo += 1;
157
+ // Igual que MapLibre: el estilo nuevo se lleva por delante las fuentes y
158
+ // las capas que se le habían añadido al anterior. Es exactamente el
159
+ // comportamiento que obliga al SDK a tener el modelo.
160
+ this.#fuentes.clear();
161
+ this.#capas.clear();
162
+ this.#ordenDeCapas.length = 0;
163
+ for (const capa of estilo.layers) {
164
+ this.#capas.set(capa.id, capa);
165
+ this.#ordenDeCapas.push(capa.id);
166
+ }
167
+ // Solo avisa si el motor ya estaba cargado. MapLibre no emite el evento
168
+ // de estilo hasta que termina de cargarlo, y fingir lo contrario haría
169
+ // que el SDK se diera por montado antes de tiempo: el `mapa:listo` se
170
+ // emitiría en el constructor, antes de que nadie haya podido suscribirse.
171
+ if (this.#listo)
172
+ this.emitirNativo("estilo:listo", undefined);
173
+ }
174
+ estilo() {
175
+ return this.#estilo;
176
+ }
177
+ agregarFuenteGeoJSON(id, datos, opciones = {}) {
178
+ this.#exigirVivo(`agregarFuenteGeoJSON(${id})`);
179
+ this.#fuentes.set(id, { datos, opciones, estados: new Map() });
180
+ this.emitirNativo("fuente:datos", { fuente: id });
181
+ }
182
+ ponerDatosDeFuente(id, datos) {
183
+ this.#exigirVivo(`ponerDatosDeFuente(${id})`);
184
+ const fuente = this.#fuentes.get(id);
185
+ if (fuente === undefined) {
186
+ throw new Error(`ponerDatosDeFuente: la fuente "${id}" no existe. En MapLibre esto ` +
187
+ `tampoco es silencioso, pero el orden importa: la fuente se añade ` +
188
+ `cuando el estilo está listo, no antes.`);
189
+ }
190
+ fuente.datos = datos;
191
+ this.emitirNativo("fuente:datos", { fuente: id });
192
+ }
193
+ tieneFuente(id) {
194
+ return this.#fuentes.has(id);
195
+ }
196
+ agregarCapa(capa, antesDe) {
197
+ this.#exigirVivo(`agregarCapa(${capa.id})`);
198
+ this.#capas.set(capa.id, capa);
199
+ const posicion = antesDe === undefined ? -1 : this.#ordenDeCapas.indexOf(antesDe);
200
+ if (posicion === -1)
201
+ this.#ordenDeCapas.push(capa.id);
202
+ else
203
+ this.#ordenDeCapas.splice(posicion, 0, capa.id);
204
+ }
205
+ quitarCapa(id) {
206
+ this.#capas.delete(id);
207
+ const posicion = this.#ordenDeCapas.indexOf(id);
208
+ if (posicion !== -1)
209
+ this.#ordenDeCapas.splice(posicion, 1);
210
+ }
211
+ tieneCapa(id) {
212
+ return this.#capas.has(id);
213
+ }
214
+ ponerEstadoDeEntidad(fuente, id, estado) {
215
+ const encontrada = this.#fuentes.get(fuente);
216
+ if (encontrada === undefined)
217
+ return;
218
+ encontrada.estados.set(id, { ...encontrada.estados.get(id), ...estado });
219
+ }
220
+ limpiarEstadosDeEntidad(fuente) {
221
+ this.#fuentes.get(fuente)?.estados.clear();
222
+ }
223
+ agregarImagen(id, imagen) {
224
+ this.#imagenes.set(id, imagen);
225
+ }
226
+ tieneImagen(id) {
227
+ return this.#imagenes.has(id);
228
+ }
229
+ vista() {
230
+ return {
231
+ centro: this.#centro,
232
+ zoom: this.#zoom,
233
+ rumbo: this.#rumbo,
234
+ inclinacion: this.#inclinacion,
235
+ caja: this.#cajaVisible(),
236
+ };
237
+ }
238
+ moverCamara(destino, opciones) {
239
+ this.#exigirVivo("moverCamara");
240
+ this.movimientos.push({ clase: "camara", destino, opciones });
241
+ if (destino.centro !== undefined)
242
+ this.#centro = destino.centro;
243
+ if (destino.zoom !== undefined)
244
+ this.#zoom = destino.zoom;
245
+ if (destino.rumbo !== undefined)
246
+ this.#rumbo = destino.rumbo;
247
+ if (destino.inclinacion !== undefined) {
248
+ this.#inclinacion = destino.inclinacion;
249
+ }
250
+ const evento = { origen: opciones.origen, vista: this.vista() };
251
+ this.emitirNativo("movimiento", evento);
252
+ this.emitirNativo("movimiento:fin", evento);
253
+ }
254
+ encuadrarCaja(caja, opciones) {
255
+ this.#exigirVivo("encuadrarCaja");
256
+ this.movimientos.push({ clase: "caja", caja, opciones });
257
+ this.#centro = {
258
+ lon: (caja[0] + caja[2]) / 2,
259
+ lat: (caja[1] + caja[3]) / 2,
260
+ };
261
+ this.#zoom = this.#zoomQueHaceCaber(caja, opciones);
262
+ const evento = { origen: opciones.origen, vista: this.vista() };
263
+ this.emitirNativo("movimiento", evento);
264
+ this.emitirNativo("movimiento:fin", evento);
265
+ }
266
+ ponerMargenes(margenes) {
267
+ this.#margenes = margenes;
268
+ }
269
+ margenes() {
270
+ return this.#margenes;
271
+ }
272
+ ponerCajaMaxima(caja) {
273
+ this.#cajaMaxima = caja;
274
+ }
275
+ cajaMaxima() {
276
+ return this.#cajaMaxima;
277
+ }
278
+ proyectar(coordenada) {
279
+ const escala = TAMANIO_DE_TESELA * 2 ** this.#zoom;
280
+ const mundo = proyectarAMundo(coordenada, escala);
281
+ const centro = proyectarAMundo(this.#centro, escala);
282
+ return {
283
+ x: mundo.x - centro.x + this.#ancho / 2,
284
+ y: mundo.y - centro.y + this.#alto / 2,
285
+ };
286
+ }
287
+ desproyectar(punto) {
288
+ const escala = TAMANIO_DE_TESELA * 2 ** this.#zoom;
289
+ const centro = proyectarAMundo(this.#centro, escala);
290
+ return desproyectarDeMundo({
291
+ x: punto.x + centro.x - this.#ancho / 2,
292
+ y: punto.y + centro.y - this.#alto / 2,
293
+ }, escala);
294
+ }
295
+ entidadesEn(punto, capas) {
296
+ const encontradas = [];
297
+ // Se recorre de arriba hacia abajo, como hace MapLibre: lo que está
298
+ // encima se toca antes.
299
+ for (const idDeCapa of [...this.#ordenDeCapas].reverse()) {
300
+ if (!capas.includes(idDeCapa))
301
+ continue;
302
+ const capa = this.#capas.get(idDeCapa);
303
+ if (capa?.source === undefined)
304
+ continue;
305
+ const fuente = this.#fuentes.get(capa.source);
306
+ if (fuente === undefined)
307
+ continue;
308
+ for (const entidad of fuente.datos.features) {
309
+ if (!pasaElFiltro(capa.filter, entidad))
310
+ continue;
311
+ if (this.#entidadAlcanzada(entidad, capa, punto)) {
312
+ encontradas.push({
313
+ capa: idDeCapa,
314
+ id: entidad.id,
315
+ propiedades: entidad.properties,
316
+ });
317
+ }
318
+ }
319
+ }
320
+ return encontradas;
321
+ }
322
+ al(tipo, manejador) {
323
+ const existentes = this.#oyentes.get(tipo) ?? new Set();
324
+ existentes.add(manejador);
325
+ this.#oyentes.set(tipo, existentes);
326
+ return () => {
327
+ existentes.delete(manejador);
328
+ };
329
+ }
330
+ alDeCapa(tipo, capas, manejador) {
331
+ const existentes = this.#oyentesDeCapa.get(tipo) ?? new Set();
332
+ const suscripcion = {
333
+ capas,
334
+ manejador: manejador,
335
+ };
336
+ existentes.add(suscripcion);
337
+ this.#oyentesDeCapa.set(tipo, existentes);
338
+ return () => {
339
+ existentes.delete(suscripcion);
340
+ };
341
+ }
342
+ // — lo interno —
343
+ #exigirVivo(que) {
344
+ if (this.#destruido) {
345
+ throw new Error(`${que}: este mapa ya fue destruido. Un mapa destruido no vuelve; ` +
346
+ `hay que crear otro.`);
347
+ }
348
+ }
349
+ #cajaVisible() {
350
+ const suroeste = this.desproyectar({ x: 0, y: this.#alto });
351
+ const noreste = this.desproyectar({ x: this.#ancho, y: 0 });
352
+ return [suroeste.lon, suroeste.lat, noreste.lon, noreste.lat];
353
+ }
354
+ #zoomQueHaceCaber(caja, opciones) {
355
+ const margenes = opciones.margenes ?? this.#margenes;
356
+ const anchoUtil = Math.max(1, this.#ancho - margenes.izquierda - margenes.derecha);
357
+ const altoUtil = Math.max(1, this.#alto - margenes.arriba - margenes.abajo);
358
+ // A escala 1 (zoom 0 sin tamaño de tesela), en unidades de mundo
359
+ // normalizadas a [0, 1].
360
+ const oeste = normalizarX(caja[0]);
361
+ const este = normalizarX(caja[2]);
362
+ const sur = normalizarY(caja[1]);
363
+ const norte = normalizarY(caja[3]);
364
+ const anchoDelMundo = Math.abs(este - oeste);
365
+ const altoDelMundo = Math.abs(sur - norte);
366
+ // Una caja de área cero pide un zoom infinito. Es exactamente el fallo
367
+ // que `planearEncuadre` evita antes de llegar aquí; el doble lo reproduce
368
+ // en vez de taparlo, porque si lo tapara la prueba que comprueba que el
369
+ // SDK no llama a `encuadrarCaja` con un punto pasaría igual sin el SDK.
370
+ if (anchoDelMundo === 0 && altoDelMundo === 0)
371
+ return 24;
372
+ const zoomEnX = anchoDelMundo === 0
373
+ ? Number.POSITIVE_INFINITY
374
+ : Math.log2(anchoUtil / (TAMANIO_DE_TESELA * anchoDelMundo));
375
+ const zoomEnY = altoDelMundo === 0
376
+ ? Number.POSITIVE_INFINITY
377
+ : Math.log2(altoUtil / (TAMANIO_DE_TESELA * altoDelMundo));
378
+ const zoom = Math.min(zoomEnX, zoomEnY);
379
+ return Math.min(24, Math.max(0, zoom));
380
+ }
381
+ #entidadAlcanzada(entidad, capa, punto) {
382
+ const geometria = entidad.geometry;
383
+ if (geometria.type === "Point") {
384
+ if (capa.type !== "circle" && capa.type !== "symbol")
385
+ return false;
386
+ const centro = this.proyectar({
387
+ lon: geometria.coordinates[0],
388
+ lat: geometria.coordinates[1],
389
+ });
390
+ const radio = radioDeCapa(capa);
391
+ return Math.hypot(centro.x - punto.x, centro.y - punto.y) <= radio;
392
+ }
393
+ if (geometria.type === "Polygon" && capa.type === "fill") {
394
+ const anillo = geometria.coordinates[0];
395
+ if (anillo === undefined)
396
+ return false;
397
+ return this.#puntoDentroDelAnillo(anillo, punto);
398
+ }
399
+ if (geometria.type === "MultiPolygon" && capa.type === "fill") {
400
+ return geometria.coordinates.some((poligono) => {
401
+ const anillo = poligono[0];
402
+ return (anillo !== undefined && this.#puntoDentroDelAnillo(anillo, punto));
403
+ });
404
+ }
405
+ if (geometria.type === "LineString" && capa.type === "line") {
406
+ const grosor = numeroDePintura(capa, "line-width") ?? 4;
407
+ return this.#puntoCercaDeLinea(geometria.coordinates, punto, grosor / 2);
408
+ }
409
+ if (geometria.type === "MultiLineString" && capa.type === "line") {
410
+ const grosor = numeroDePintura(capa, "line-width") ?? 4;
411
+ return geometria.coordinates.some((tramo) => this.#puntoCercaDeLinea(tramo, punto, grosor / 2));
412
+ }
413
+ return false;
414
+ }
415
+ #puntoDentroDelAnillo(anillo, punto) {
416
+ // Cruce de rayos, sobre coordenadas de pantalla para que el resultado
417
+ // corresponda con lo que el usuario ve y no con el sistema geográfico.
418
+ const proyectados = anillo.map((posicion) => this.proyectar({ lon: posicion[0], lat: posicion[1] }));
419
+ let dentro = false;
420
+ for (let i = 0, j = proyectados.length - 1; i < proyectados.length; j = i, i += 1) {
421
+ const a = proyectados[i];
422
+ const b = proyectados[j];
423
+ if (a === undefined || b === undefined)
424
+ continue;
425
+ const cruza = a.y > punto.y !== b.y > punto.y;
426
+ if (!cruza)
427
+ continue;
428
+ const x = ((b.x - a.x) * (punto.y - a.y)) / (b.y - a.y) + a.x;
429
+ if (punto.x < x)
430
+ dentro = !dentro;
431
+ }
432
+ return dentro;
433
+ }
434
+ #puntoCercaDeLinea(linea, punto, tolerancia) {
435
+ const proyectados = linea.map((posicion) => this.proyectar({ lon: posicion[0], lat: posicion[1] }));
436
+ for (let i = 1; i < proyectados.length; i += 1) {
437
+ const a = proyectados[i - 1];
438
+ const b = proyectados[i];
439
+ if (a === undefined || b === undefined)
440
+ continue;
441
+ if (distanciaASegmento(punto, a, b) <= tolerancia)
442
+ return true;
443
+ }
444
+ return false;
445
+ }
446
+ }
447
+ /**
448
+ * Evalúa las formas de filtro que este SDK escribe, y solo esas.
449
+ *
450
+ * Sin filtros, el doble miente sobre qué capa recibe un toque: la capa de
451
+ * grupos y la de negocios comparten fuente y se distinguen **solo** por
452
+ * `["has", "point_count"]`, así que un doble que los ignore hace que tocar un
453
+ * negocio suelto parezca tocar un grupo. Lo encontró una prueba que fallaba
454
+ * por el lado correcto.
455
+ *
456
+ * Un filtro que no se sepa evaluar deja pasar la entidad, y eso es
457
+ * deliberado: es el fallo que se ve —de más, no de menos—, y una prueba con
458
+ * entidades de sobra falla; una con entidades de menos pasa en verde.
459
+ */
460
+ function pasaElFiltro(filtro, entidad) {
461
+ if (filtro === undefined || filtro === null)
462
+ return true;
463
+ if (typeof filtro === "boolean")
464
+ return filtro;
465
+ if (!Array.isArray(filtro))
466
+ return true;
467
+ const [operador, ...argumentos] = filtro;
468
+ switch (operador) {
469
+ case "all":
470
+ return argumentos.every((cada) => pasaElFiltro(cada, entidad));
471
+ case "any":
472
+ return argumentos.some((cada) => pasaElFiltro(cada, entidad));
473
+ case "!":
474
+ return !pasaElFiltro(argumentos[0], entidad);
475
+ case "has":
476
+ return (typeof argumentos[0] === "string" && argumentos[0] in entidad.properties);
477
+ case "get":
478
+ return (typeof argumentos[0] === "string" &&
479
+ entidad.properties[argumentos[0]] === true);
480
+ case "==": {
481
+ const izquierda = valorDeExpresion(argumentos[0], entidad);
482
+ const derecha = valorDeExpresion(argumentos[1], entidad);
483
+ return izquierda === derecha;
484
+ }
485
+ default:
486
+ return true;
487
+ }
488
+ }
489
+ function valorDeExpresion(expresion, entidad) {
490
+ if (!Array.isArray(expresion))
491
+ return expresion;
492
+ const [operador, argumento] = expresion;
493
+ if (operador === "geometry-type")
494
+ return entidad.geometry.type;
495
+ if (operador === "get" && typeof argumento === "string") {
496
+ return entidad.properties[argumento];
497
+ }
498
+ return expresion;
499
+ }
500
+ function radioDeCapa(capa) {
501
+ return numeroDePintura(capa, "circle-radius") ?? 8;
502
+ }
503
+ function numeroDePintura(capa, propiedad) {
504
+ const valor = capa.paint?.[propiedad];
505
+ return typeof valor === "number" ? valor : undefined;
506
+ }
507
+ function distanciaASegmento(punto, a, b) {
508
+ const dx = b.x - a.x;
509
+ const dy = b.y - a.y;
510
+ const largoAlCuadrado = dx * dx + dy * dy;
511
+ if (largoAlCuadrado === 0)
512
+ return Math.hypot(punto.x - a.x, punto.y - a.y);
513
+ const t = Math.max(0, Math.min(1, ((punto.x - a.x) * dx + (punto.y - a.y) * dy) / largoAlCuadrado));
514
+ return Math.hypot(punto.x - (a.x + t * dx), punto.y - (a.y + t * dy));
515
+ }
516
+ function normalizarX(lon) {
517
+ return (lon + 180) / 360;
518
+ }
519
+ function normalizarY(lat) {
520
+ const enRadianes = (Math.max(-85.051129, Math.min(85.051129, lat)) * Math.PI) / 180;
521
+ return 0.5 - Math.log(Math.tan(Math.PI / 4 + enRadianes / 2)) / (2 * Math.PI);
522
+ }
523
+ function proyectarAMundo(coordenada, escala) {
524
+ return {
525
+ x: normalizarX(coordenada.lon) * escala,
526
+ y: normalizarY(coordenada.lat) * escala,
527
+ };
528
+ }
529
+ function desproyectarDeMundo(punto, escala) {
530
+ const lon = (punto.x / escala) * 360 - 180;
531
+ const y = 0.5 - punto.y / escala;
532
+ const lat = (Math.atan(Math.sinh(2 * Math.PI * y)) * 180) / Math.PI;
533
+ return { lon, lat };
534
+ }
535
+ //# sourceMappingURL=mapaFalso.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mapaFalso.js","sourceRoot":"","sources":["../../src/doble/mapaFalso.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Caja,\n ColeccionDeEntidades,\n Coordenada,\n EntidadGeoJSON,\n Posicion,\n} 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\";\n\n/**\n * Un motor de mapas falso, con la geometría de verdad.\n *\n * ## Qué finge y qué no\n *\n * **No finge la proyección.** Web Mercator está implementada aquí igual que\n * en MapLibre, así que `proyectar` y `desproyectar` dan los mismos píxeles.\n * Eso es lo que permite comprobar en Node cosas que parecerían necesitar una\n * GPU: que un toque a 20 px del centro de un alfiler lo alcanza y uno a 30 no,\n * o que después de encuadrar una caja los tres negocios caen dentro de la\n * pantalla.\n *\n * **No finge WebGL**, y por eso no dibuja: no hay framebuffer, no hay\n * antialiasing, no hay orden de rasterizado. Lo que este doble responde a una\n * consulta de entidades sale de recorrer las fuentes y medir distancias, que\n * es una aproximación honesta del índice espacial de MapLibre y no lo mismo.\n *\n * **No aplica rumbo ni inclinación** a la proyección. El mapa de Cerca es\n * cenital y sin girar; el día que alguien gire la cámara, esta clase miente y\n * hay que arreglarla. Queda dicho aquí para que se encuentre.\n *\n * ## Por qué se publica en vez de quedarse en las pruebas\n *\n * Porque las superficies también necesitan probar sus pantallas sin GPU. Una\n * PWA que pruebe su lista de resultados no debería tener que levantar WebGL\n * para comprobar que al tocar un resultado el mapa recibió la orden de\n * seleccionarlo. Entra por `@cerca.red/mapa/doble`, que es SSR-seguro como\n * todo lo que no está bajo `navegador/`.\n */\n\nconst TAMANIO_DE_TESELA = 512;\n\ninterface FuenteFalsa {\n datos: ColeccionDeEntidades;\n opciones: OpcionesDeFuente;\n estados: Map<string | number, Record<string, unknown>>;\n}\n\nexport interface OpcionesDelMapaFalso {\n readonly centro?: Coordenada;\n readonly zoom?: number;\n readonly ancho?: number;\n readonly alto?: number;\n /**\n * Si el estilo se da por cargado de inmediato.\n *\n * `false` es lo que permite probar lo que pasa entre `crearMapa` y\n * `mapa:listo`: una superficie que llame a `ponerNegocios` en ese hueco no\n * puede perder los negocios.\n */\n readonly listoDeInmediato?: boolean;\n}\n\nexport class MapaFalso implements MapaNativo {\n #listo: boolean;\n #destruido = false;\n #estilo: EspecificacionDeEstilo | undefined;\n #centro: Coordenada;\n #zoom: number;\n #rumbo = 0;\n #inclinacion = 0;\n #margenes: Margenes = { arriba: 0, derecha: 0, abajo: 0, izquierda: 0 };\n #cajaMaxima: Caja | undefined;\n\n readonly #ancho: number;\n readonly #alto: number;\n readonly #fuentes = new Map<string, FuenteFalsa>();\n readonly #capas = new Map<string, CapaDelEstilo>();\n readonly #ordenDeCapas: string[] = [];\n readonly #imagenes = new Map<string, ImagenDeSimbolo>();\n readonly #oyentes = new Map<string, Set<(evento: never) => void>>();\n readonly #oyentesDeCapa = new Map<\n string,\n Set<{ capas: readonly string[]; manejador: (evento: never) => void }>\n >();\n readonly #esperandoListo: Array<() => void> = [];\n\n /** Lo que se le pidió a la cámara, en orden. Es lo que se afirma en las pruebas. */\n readonly movimientos: Array<{\n readonly clase: \"camara\" | \"caja\";\n readonly destino?: DestinoDeCamara;\n readonly caja?: Caja;\n readonly opciones: OpcionesDeMovimiento | OpcionesDeEncuadre;\n }> = [];\n\n /** Cuántas veces se reemplazó el estilo. Un cambio de tema es uno. */\n cambiosDeEstilo = 0;\n\n constructor(opciones: OpcionesDelMapaFalso = {}) {\n this.#centro = opciones.centro ?? { lon: -75.6961, lat: 4.8133 };\n this.#zoom = opciones.zoom ?? 13;\n this.#ancho = opciones.ancho ?? 390;\n this.#alto = opciones.alto ?? 844;\n this.#listo = opciones.listoDeInmediato ?? true;\n }\n\n // — lo que solo existe en el doble —\n\n /** Marca el estilo como cargado y avisa, como haría el motor al terminar. */\n terminarDeCargar(): void {\n if (this.#listo) return;\n this.#listo = true;\n for (const resolver of this.#esperandoListo.splice(0)) resolver();\n this.emitirNativo(\"estilo:listo\", undefined);\n }\n\n /** Emite un evento como si viniera del motor. */\n emitirNativo<T extends keyof EventosNativos>(\n tipo: T,\n evento: EventosNativos[T],\n ): void {\n for (const manejador of [...(this.#oyentes.get(tipo) ?? [])]) {\n (manejador as (evento: EventosNativos[T]) => void)(evento);\n }\n }\n\n /** Emite un evento de capa, como si el usuario hubiera tocado ahí. */\n emitirDeCapa<T extends keyof EventosDeCapa>(\n tipo: T,\n capa: string,\n evento: EventosDeCapa[T],\n ): void {\n for (const suscripcion of [...(this.#oyentesDeCapa.get(tipo) ?? [])]) {\n if (!suscripcion.capas.includes(capa)) continue;\n (suscripcion.manejador as (evento: EventosDeCapa[T]) => void)(evento);\n }\n }\n\n /** Simula un toque: consulta lo que hay debajo y avisa a quien escuche esa capa. */\n tocarEn(punto: PuntoEnPantalla, desdePuntero = true): void {\n const coordenada = this.desproyectar(punto);\n const capasConOyentes = new Set<string>();\n for (const suscripcion of this.#oyentesDeCapa.get(\"clic\") ?? []) {\n for (const capa of suscripcion.capas) capasConOyentes.add(capa);\n }\n\n // Se consultan todas a la vez porque `entidadesEn` ya recorre de arriba\n // hacia abajo, como MapLibre. Consultarlas de una en una las visitaria en\n // el orden en que alguien se suscribió, que no tiene nada que ver con\n // cuál está encima.\n const entidades = this.entidadesEn(punto, [...capasConOyentes]);\n const primera = entidades[0];\n\n if (primera !== undefined) {\n this.emitirDeCapa(\"clic\", primera.capa, {\n coordenada,\n punto,\n desdePuntero,\n entidades: entidades.filter((cada) => cada.capa === primera.capa),\n });\n return;\n }\n\n this.emitirNativo(\"clic\", { coordenada, punto, desdePuntero });\n }\n\n /** El estado de entidad puesto sobre una entidad. Para comprobar el resalte. */\n estadoDeEntidad(\n fuente: string,\n id: string | number,\n ): Record<string, unknown> | undefined {\n return this.#fuentes.get(fuente)?.estados.get(id);\n }\n\n /** Los datos que tiene una fuente ahora mismo. */\n datosDeFuente(id: string): ColeccionDeEntidades | undefined {\n return this.#fuentes.get(id)?.datos;\n }\n\n /** Los identificadores de capa, en el orden en que quedaron. */\n ordenDeCapas(): readonly string[] {\n return [...this.#ordenDeCapas];\n }\n\n /** Cuántas suscripciones vivas quedan. Es lo que comprueba que `destruir` limpió. */\n cuantosOyentes(): number {\n let total = 0;\n for (const oyentes of this.#oyentes.values()) total += oyentes.size;\n for (const oyentes of this.#oyentesDeCapa.values()) total += oyentes.size;\n return total;\n }\n\n estaDestruido(): boolean {\n return this.#destruido;\n }\n\n // — MapaNativo —\n\n estaListo(): boolean {\n return this.#listo;\n }\n\n async cuandoListo(): Promise<void> {\n if (this.#listo) return;\n await new Promise<void>((resolver) => this.#esperandoListo.push(resolver));\n }\n\n destruir(): void {\n this.#destruido = true;\n this.#oyentes.clear();\n this.#oyentesDeCapa.clear();\n this.#fuentes.clear();\n this.#capas.clear();\n this.#ordenDeCapas.length = 0;\n }\n\n redimensionar(): void {\n // No hay contenedor que medir.\n }\n\n ponerEstilo(estilo: EspecificacionDeEstilo): void {\n this.#estilo = estilo;\n this.cambiosDeEstilo += 1;\n // Igual que MapLibre: el estilo nuevo se lleva por delante las fuentes y\n // las capas que se le habían añadido al anterior. Es exactamente el\n // comportamiento que obliga al SDK a tener el modelo.\n this.#fuentes.clear();\n this.#capas.clear();\n this.#ordenDeCapas.length = 0;\n for (const capa of estilo.layers) {\n this.#capas.set(capa.id, capa);\n this.#ordenDeCapas.push(capa.id);\n }\n // Solo avisa si el motor ya estaba cargado. MapLibre no emite el evento\n // de estilo hasta que termina de cargarlo, y fingir lo contrario haría\n // que el SDK se diera por montado antes de tiempo: el `mapa:listo` se\n // emitiría en el constructor, antes de que nadie haya podido suscribirse.\n if (this.#listo) this.emitirNativo(\"estilo:listo\", undefined);\n }\n\n estilo(): EspecificacionDeEstilo | undefined {\n return this.#estilo;\n }\n\n agregarFuenteGeoJSON(\n id: string,\n datos: ColeccionDeEntidades,\n opciones: OpcionesDeFuente = {},\n ): void {\n this.#exigirVivo(`agregarFuenteGeoJSON(${id})`);\n this.#fuentes.set(id, { datos, opciones, estados: new Map() });\n this.emitirNativo(\"fuente:datos\", { fuente: id });\n }\n\n ponerDatosDeFuente(id: string, datos: ColeccionDeEntidades): void {\n this.#exigirVivo(`ponerDatosDeFuente(${id})`);\n const fuente = this.#fuentes.get(id);\n if (fuente === undefined) {\n throw new Error(\n `ponerDatosDeFuente: la fuente \"${id}\" no existe. En MapLibre esto ` +\n `tampoco es silencioso, pero el orden importa: la fuente se añade ` +\n `cuando el estilo está listo, no antes.`,\n );\n }\n fuente.datos = datos;\n this.emitirNativo(\"fuente:datos\", { fuente: id });\n }\n\n tieneFuente(id: string): boolean {\n return this.#fuentes.has(id);\n }\n\n agregarCapa(capa: CapaDelEstilo, antesDe?: string): void {\n this.#exigirVivo(`agregarCapa(${capa.id})`);\n this.#capas.set(capa.id, capa);\n const posicion =\n antesDe === undefined ? -1 : this.#ordenDeCapas.indexOf(antesDe);\n if (posicion === -1) this.#ordenDeCapas.push(capa.id);\n else this.#ordenDeCapas.splice(posicion, 0, capa.id);\n }\n\n quitarCapa(id: string): void {\n this.#capas.delete(id);\n const posicion = this.#ordenDeCapas.indexOf(id);\n if (posicion !== -1) this.#ordenDeCapas.splice(posicion, 1);\n }\n\n tieneCapa(id: string): boolean {\n return this.#capas.has(id);\n }\n\n ponerEstadoDeEntidad(\n fuente: string,\n id: string | number,\n estado: Record<string, unknown>,\n ): void {\n const encontrada = this.#fuentes.get(fuente);\n if (encontrada === undefined) return;\n encontrada.estados.set(id, { ...encontrada.estados.get(id), ...estado });\n }\n\n limpiarEstadosDeEntidad(fuente: string): void {\n this.#fuentes.get(fuente)?.estados.clear();\n }\n\n agregarImagen(id: string, imagen: ImagenDeSimbolo): void {\n this.#imagenes.set(id, imagen);\n }\n\n tieneImagen(id: string): boolean {\n return this.#imagenes.has(id);\n }\n\n vista(): VistaDelMapa {\n return {\n centro: this.#centro,\n zoom: this.#zoom,\n rumbo: this.#rumbo,\n inclinacion: this.#inclinacion,\n caja: this.#cajaVisible(),\n };\n }\n\n moverCamara(destino: DestinoDeCamara, opciones: OpcionesDeMovimiento): void {\n this.#exigirVivo(\"moverCamara\");\n this.movimientos.push({ clase: \"camara\", destino, opciones });\n if (destino.centro !== undefined) this.#centro = destino.centro;\n if (destino.zoom !== undefined) this.#zoom = destino.zoom;\n if (destino.rumbo !== undefined) this.#rumbo = destino.rumbo;\n if (destino.inclinacion !== undefined) {\n this.#inclinacion = destino.inclinacion;\n }\n const evento = { origen: opciones.origen, vista: this.vista() };\n this.emitirNativo(\"movimiento\", evento);\n this.emitirNativo(\"movimiento:fin\", evento);\n }\n\n encuadrarCaja(caja: Caja, opciones: OpcionesDeEncuadre): void {\n this.#exigirVivo(\"encuadrarCaja\");\n this.movimientos.push({ clase: \"caja\", caja, opciones });\n\n this.#centro = {\n lon: (caja[0] + caja[2]) / 2,\n lat: (caja[1] + caja[3]) / 2,\n };\n this.#zoom = this.#zoomQueHaceCaber(caja, opciones);\n\n const evento = { origen: opciones.origen, vista: this.vista() };\n this.emitirNativo(\"movimiento\", evento);\n this.emitirNativo(\"movimiento:fin\", evento);\n }\n\n ponerMargenes(margenes: Margenes): void {\n this.#margenes = margenes;\n }\n\n margenes(): Margenes {\n return this.#margenes;\n }\n\n ponerCajaMaxima(caja: Caja | undefined): void {\n this.#cajaMaxima = caja;\n }\n\n cajaMaxima(): Caja | undefined {\n return this.#cajaMaxima;\n }\n\n proyectar(coordenada: Coordenada): PuntoEnPantalla {\n const escala = TAMANIO_DE_TESELA * 2 ** this.#zoom;\n const mundo = proyectarAMundo(coordenada, escala);\n const centro = proyectarAMundo(this.#centro, escala);\n return {\n x: mundo.x - centro.x + this.#ancho / 2,\n y: mundo.y - centro.y + this.#alto / 2,\n };\n }\n\n desproyectar(punto: PuntoEnPantalla): Coordenada {\n const escala = TAMANIO_DE_TESELA * 2 ** this.#zoom;\n const centro = proyectarAMundo(this.#centro, escala);\n return desproyectarDeMundo(\n {\n x: punto.x + centro.x - this.#ancho / 2,\n y: punto.y + centro.y - this.#alto / 2,\n },\n escala,\n );\n }\n\n entidadesEn(\n punto: PuntoEnPantalla,\n capas: readonly string[],\n ): readonly EntidadDibujada[] {\n const encontradas: EntidadDibujada[] = [];\n\n // Se recorre de arriba hacia abajo, como hace MapLibre: lo que está\n // encima se toca antes.\n for (const idDeCapa of [...this.#ordenDeCapas].reverse()) {\n if (!capas.includes(idDeCapa)) continue;\n const capa = this.#capas.get(idDeCapa);\n if (capa?.source === undefined) continue;\n const fuente = this.#fuentes.get(capa.source);\n if (fuente === undefined) continue;\n\n for (const entidad of fuente.datos.features) {\n if (!pasaElFiltro(capa.filter, entidad)) continue;\n if (this.#entidadAlcanzada(entidad, capa, punto)) {\n encontradas.push({\n capa: idDeCapa,\n id: entidad.id,\n propiedades: entidad.properties,\n });\n }\n }\n }\n\n return encontradas;\n }\n\n al<T extends keyof EventosNativos>(\n tipo: T,\n manejador: (evento: EventosNativos[T]) => void,\n ): Desuscribir {\n const existentes = this.#oyentes.get(tipo) ?? new Set();\n existentes.add(manejador as (evento: never) => void);\n this.#oyentes.set(tipo, existentes);\n return () => {\n existentes.delete(manejador as (evento: never) => void);\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 existentes = this.#oyentesDeCapa.get(tipo) ?? new Set();\n const suscripcion = {\n capas,\n manejador: manejador as (evento: never) => void,\n };\n existentes.add(suscripcion);\n this.#oyentesDeCapa.set(tipo, existentes);\n return () => {\n existentes.delete(suscripcion);\n };\n }\n\n // — lo interno —\n\n #exigirVivo(que: string): void {\n if (this.#destruido) {\n throw new Error(\n `${que}: este mapa ya fue destruido. Un mapa destruido no vuelve; ` +\n `hay que crear otro.`,\n );\n }\n }\n\n #cajaVisible(): Caja {\n const suroeste = this.desproyectar({ x: 0, y: this.#alto });\n const noreste = this.desproyectar({ x: this.#ancho, y: 0 });\n return [suroeste.lon, suroeste.lat, noreste.lon, noreste.lat];\n }\n\n #zoomQueHaceCaber(caja: Caja, opciones: OpcionesDeEncuadre): number {\n const margenes = opciones.margenes ?? this.#margenes;\n const anchoUtil = Math.max(\n 1,\n this.#ancho - margenes.izquierda - margenes.derecha,\n );\n const altoUtil = Math.max(1, this.#alto - margenes.arriba - margenes.abajo);\n\n // A escala 1 (zoom 0 sin tamaño de tesela), en unidades de mundo\n // normalizadas a [0, 1].\n const oeste = normalizarX(caja[0]);\n const este = normalizarX(caja[2]);\n const sur = normalizarY(caja[1]);\n const norte = normalizarY(caja[3]);\n\n const anchoDelMundo = Math.abs(este - oeste);\n const altoDelMundo = Math.abs(sur - norte);\n\n // Una caja de área cero pide un zoom infinito. Es exactamente el fallo\n // que `planearEncuadre` evita antes de llegar aquí; el doble lo reproduce\n // en vez de taparlo, porque si lo tapara la prueba que comprueba que el\n // SDK no llama a `encuadrarCaja` con un punto pasaría igual sin el SDK.\n if (anchoDelMundo === 0 && altoDelMundo === 0) return 24;\n\n const zoomEnX =\n anchoDelMundo === 0\n ? Number.POSITIVE_INFINITY\n : Math.log2(anchoUtil / (TAMANIO_DE_TESELA * anchoDelMundo));\n const zoomEnY =\n altoDelMundo === 0\n ? Number.POSITIVE_INFINITY\n : Math.log2(altoUtil / (TAMANIO_DE_TESELA * altoDelMundo));\n\n const zoom = Math.min(zoomEnX, zoomEnY);\n return Math.min(24, Math.max(0, zoom));\n }\n\n #entidadAlcanzada(\n entidad: EntidadGeoJSON,\n capa: CapaDelEstilo,\n punto: PuntoEnPantalla,\n ): boolean {\n const geometria = entidad.geometry;\n\n if (geometria.type === \"Point\") {\n if (capa.type !== \"circle\" && capa.type !== \"symbol\") return false;\n const centro = this.proyectar({\n lon: geometria.coordinates[0],\n lat: geometria.coordinates[1],\n });\n const radio = radioDeCapa(capa);\n return Math.hypot(centro.x - punto.x, centro.y - punto.y) <= radio;\n }\n\n if (geometria.type === \"Polygon\" && capa.type === \"fill\") {\n const anillo = geometria.coordinates[0];\n if (anillo === undefined) return false;\n return this.#puntoDentroDelAnillo(anillo, punto);\n }\n\n if (geometria.type === \"MultiPolygon\" && capa.type === \"fill\") {\n return geometria.coordinates.some((poligono) => {\n const anillo = poligono[0];\n return (\n anillo !== undefined && this.#puntoDentroDelAnillo(anillo, punto)\n );\n });\n }\n\n if (geometria.type === \"LineString\" && capa.type === \"line\") {\n const grosor = numeroDePintura(capa, \"line-width\") ?? 4;\n return this.#puntoCercaDeLinea(geometria.coordinates, punto, grosor / 2);\n }\n\n if (geometria.type === \"MultiLineString\" && capa.type === \"line\") {\n const grosor = numeroDePintura(capa, \"line-width\") ?? 4;\n return geometria.coordinates.some((tramo) =>\n this.#puntoCercaDeLinea(tramo, punto, grosor / 2),\n );\n }\n\n return false;\n }\n\n #puntoDentroDelAnillo(\n anillo: readonly Posicion[],\n punto: PuntoEnPantalla,\n ): boolean {\n // Cruce de rayos, sobre coordenadas de pantalla para que el resultado\n // corresponda con lo que el usuario ve y no con el sistema geográfico.\n const proyectados = anillo.map((posicion) =>\n this.proyectar({ lon: posicion[0], lat: posicion[1] }),\n );\n let dentro = false;\n for (\n let i = 0, j = proyectados.length - 1;\n i < proyectados.length;\n j = i, i += 1\n ) {\n const a = proyectados[i];\n const b = proyectados[j];\n if (a === undefined || b === undefined) continue;\n const cruza = a.y > punto.y !== b.y > punto.y;\n if (!cruza) continue;\n const x = ((b.x - a.x) * (punto.y - a.y)) / (b.y - a.y) + a.x;\n if (punto.x < x) dentro = !dentro;\n }\n return dentro;\n }\n\n #puntoCercaDeLinea(\n linea: readonly Posicion[],\n punto: PuntoEnPantalla,\n tolerancia: number,\n ): boolean {\n const proyectados = linea.map((posicion) =>\n this.proyectar({ lon: posicion[0], lat: posicion[1] }),\n );\n for (let i = 1; i < proyectados.length; i += 1) {\n const a = proyectados[i - 1];\n const b = proyectados[i];\n if (a === undefined || b === undefined) continue;\n if (distanciaASegmento(punto, a, b) <= tolerancia) return true;\n }\n return false;\n }\n}\n\n/**\n * Evalúa las formas de filtro que este SDK escribe, y solo esas.\n *\n * Sin filtros, el doble miente sobre qué capa recibe un toque: la capa de\n * grupos y la de negocios comparten fuente y se distinguen **solo** por\n * `[\"has\", \"point_count\"]`, así que un doble que los ignore hace que tocar un\n * negocio suelto parezca tocar un grupo. Lo encontró una prueba que fallaba\n * por el lado correcto.\n *\n * Un filtro que no se sepa evaluar deja pasar la entidad, y eso es\n * deliberado: es el fallo que se ve —de más, no de menos—, y una prueba con\n * entidades de sobra falla; una con entidades de menos pasa en verde.\n */\nfunction pasaElFiltro(filtro: unknown, entidad: EntidadGeoJSON): boolean {\n if (filtro === undefined || filtro === null) return true;\n if (typeof filtro === \"boolean\") return filtro;\n if (!Array.isArray(filtro)) return true;\n\n const [operador, ...argumentos] = filtro as [string, ...unknown[]];\n\n switch (operador) {\n case \"all\":\n return argumentos.every((cada) => pasaElFiltro(cada, entidad));\n case \"any\":\n return argumentos.some((cada) => pasaElFiltro(cada, entidad));\n case \"!\":\n return !pasaElFiltro(argumentos[0], entidad);\n case \"has\":\n return (\n typeof argumentos[0] === \"string\" && argumentos[0] in entidad.properties\n );\n case \"get\":\n return (\n typeof argumentos[0] === \"string\" &&\n entidad.properties[argumentos[0]] === true\n );\n case \"==\": {\n const izquierda = valorDeExpresion(argumentos[0], entidad);\n const derecha = valorDeExpresion(argumentos[1], entidad);\n return izquierda === derecha;\n }\n default:\n return true;\n }\n}\n\nfunction valorDeExpresion(\n expresion: unknown,\n entidad: EntidadGeoJSON,\n): unknown {\n if (!Array.isArray(expresion)) return expresion;\n const [operador, argumento] = expresion as [string, unknown];\n if (operador === \"geometry-type\") return entidad.geometry.type;\n if (operador === \"get\" && typeof argumento === \"string\") {\n return entidad.properties[argumento];\n }\n return expresion;\n}\n\nfunction radioDeCapa(capa: CapaDelEstilo): number {\n return numeroDePintura(capa, \"circle-radius\") ?? 8;\n}\n\nfunction numeroDePintura(\n capa: CapaDelEstilo,\n propiedad: string,\n): number | undefined {\n const valor = capa.paint?.[propiedad];\n return typeof valor === \"number\" ? valor : undefined;\n}\n\nfunction distanciaASegmento(\n punto: PuntoEnPantalla,\n a: PuntoEnPantalla,\n b: PuntoEnPantalla,\n): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const largoAlCuadrado = dx * dx + dy * dy;\n if (largoAlCuadrado === 0) return Math.hypot(punto.x - a.x, punto.y - a.y);\n const t = Math.max(\n 0,\n Math.min(\n 1,\n ((punto.x - a.x) * dx + (punto.y - a.y) * dy) / largoAlCuadrado,\n ),\n );\n return Math.hypot(punto.x - (a.x + t * dx), punto.y - (a.y + t * dy));\n}\n\nfunction normalizarX(lon: number): number {\n return (lon + 180) / 360;\n}\n\nfunction normalizarY(lat: number): number {\n const enRadianes =\n (Math.max(-85.051129, Math.min(85.051129, lat)) * Math.PI) / 180;\n return 0.5 - Math.log(Math.tan(Math.PI / 4 + enRadianes / 2)) / (2 * Math.PI);\n}\n\nfunction proyectarAMundo(\n coordenada: Coordenada,\n escala: number,\n): { x: number; y: number } {\n return {\n x: normalizarX(coordenada.lon) * escala,\n y: normalizarY(coordenada.lat) * escala,\n };\n}\n\nfunction desproyectarDeMundo(\n punto: { x: number; y: number },\n escala: number,\n): Coordenada {\n const lon = (punto.x / escala) * 360 - 180;\n const y = 0.5 - punto.y / escala;\n const lat = (Math.atan(Math.sinh(2 * Math.PI * y)) * 180) / Math.PI;\n return { lon, lat };\n}\n"]}