@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
package/dist/modelo.js ADDED
@@ -0,0 +1,227 @@
1
+ import { aPosicion, cajaDeCoordenadas, cajaDeGeometria, circuloComoPoligono, coleccion, entidad, unirCajas, } from "@cerca.red/geo";
2
+ /** El modelo de un mapa recién creado. */
3
+ export function modeloInicial(parciales = {}) {
4
+ const { negocios, resultados, ...resto } = parciales;
5
+ return {
6
+ resultados: resultados ?? { modo: "negocios", negocios: negocios ?? [] },
7
+ seleccionado: undefined,
8
+ ubicacion: undefined,
9
+ vehiculos: [],
10
+ rutas: [],
11
+ zonas: [],
12
+ isocronas: [],
13
+ tema: "claro",
14
+ idioma: "es",
15
+ margenes: { arriba: 0, derecha: 0, abajo: 0, izquierda: 0 },
16
+ radioDeImprecisionEnMetros: 150,
17
+ ...resto,
18
+ };
19
+ }
20
+ /** Los negocios del modo actual; vacío mientras se muestran grupos del servidor. */
21
+ export function negociosDelModelo(modelo) {
22
+ return modelo.resultados.modo === "negocios"
23
+ ? modelo.resultados.negocios
24
+ : [];
25
+ }
26
+ /** Los grupos explícitos del modo actual; vacío mientras se muestran negocios. */
27
+ export function gruposDelModelo(modelo) {
28
+ return modelo.resultados.modo === "grupos" ? modelo.resultados.grupos : [];
29
+ }
30
+ /**
31
+ * Los negocios, como colección GeoJSON.
32
+ *
33
+ * El `id` va **dos veces**: como identificador de la entidad y como
34
+ * propiedad. No es descuido. El identificador de entidad es lo que necesita
35
+ * el estado de entidad —el resalte al pasar por encima— y tiene que ser
36
+ * numérico o una cadena; la propiedad es lo que se lee al consultar lo
37
+ * dibujado bajo el dedo, porque MapLibre **pierde el identificador de la
38
+ * entidad al agrupar** y solo conserva las propiedades.
39
+ */
40
+ export function coleccionDeNegocios(modelo) {
41
+ return coleccion(negociosExactos(modelo).map((negocio) => entidad({ type: "Point", coordinates: aPosicion(negocio.ubicacion) }, {
42
+ id: negocio.id,
43
+ enfasis: negocio.enfasis ?? "normal",
44
+ ...(negocio.rotulo === undefined ? {} : { rotulo: negocio.rotulo }),
45
+ ...(negocio.simbolo === undefined
46
+ ? {}
47
+ : { simbolo: negocio.simbolo }),
48
+ }, negocio.id)));
49
+ }
50
+ /** Los agregados del servidor, en una fuente sin clustering local. */
51
+ export function coleccionDeGrupos(modelo) {
52
+ return coleccion(gruposDelModelo(modelo).map((grupo) => entidad({ type: "Point", coordinates: aPosicion(grupo.ubicacion) }, {
53
+ id: grupo.id,
54
+ cantidad: grupo.cantidad,
55
+ ...(grupo.rotulo === undefined ? {} : { rotulo: grupo.rotulo }),
56
+ }, grupo.id)));
57
+ }
58
+ /** Las sedes de todos los negocios, aplanadas en una sola colección. */
59
+ export function coleccionDeSedes(modelo) {
60
+ const entidades = negociosDelModelo(modelo).flatMap((negocio) => (negocio.sedes ?? []).map((sede) => entidad({ type: "Point", coordinates: aPosicion(sede.ubicacion) }, {
61
+ id: sede.id,
62
+ negocio: negocio.id,
63
+ ...(sede.rotulo === undefined ? {} : { rotulo: sede.rotulo }),
64
+ }, sede.id)));
65
+ return coleccion(entidades);
66
+ }
67
+ /**
68
+ * El negocio seleccionado, solo. En su propia fuente porque tiene que
69
+ * dibujarse **encima** de sus vecinos, y la clave de ordenación de símbolos
70
+ * no lee el estado de entidad.
71
+ */
72
+ export function coleccionDeSeleccion(modelo) {
73
+ const elegido = negociosDelModelo(modelo).find((negocio) => negocio.id === modelo.seleccionado);
74
+ if (elegido === undefined)
75
+ return coleccion([]);
76
+ // Un negocio aproximado se resalta como área, no como alfiler. Seleccionar
77
+ // no es motivo para desdecirse: si nadie verificó el punto, señalarlo con
78
+ // un alfiler grande y naranja afirma con más fuerza algo que Cerca sigue
79
+ // sin saber.
80
+ const geometria = elegido.precision === "aproximada"
81
+ ? circuloComoPoligono(elegido.ubicacion, modelo.radioDeImprecisionEnMetros)
82
+ : { type: "Point", coordinates: aPosicion(elegido.ubicacion) };
83
+ return coleccion([
84
+ entidad(geometria, {
85
+ id: elegido.id,
86
+ enfasis: elegido.enfasis ?? "normal",
87
+ ...(elegido.rotulo === undefined ? {} : { rotulo: elegido.rotulo }),
88
+ }, elegido.id),
89
+ ]);
90
+ }
91
+ /**
92
+ * Las áreas de las ubicaciones aproximadas.
93
+ *
94
+ * Esta es la función que hace visible una decisión que en una lista es una
95
+ * etiqueta: «no se da por exacta una ubicación que nadie verificó». En un
96
+ * mapa, un alfiler afirma un punto. Si nadie verificó ese punto, el alfiler
97
+ * está afirmando algo que Cerca no sabe, y el usuario que camina hasta ahí
98
+ * descubre el error caminando.
99
+ *
100
+ * Un negocio aproximado sale **como área y sin punto**: aparece en esta
101
+ * colección y se filtra de la de negocios. Dibujar las dos cosas sería
102
+ * desdecirse en el mismo mapa.
103
+ */
104
+ export function coleccionDeImprecision(modelo) {
105
+ const areas = negociosDelModelo(modelo)
106
+ .filter((negocio) => negocio.precision === "aproximada")
107
+ .map((negocio) => entidad(circuloComoPoligono(negocio.ubicacion, modelo.radioDeImprecisionEnMetros), { id: negocio.id }, negocio.id));
108
+ return coleccion(areas);
109
+ }
110
+ /** Los negocios con ubicación exacta: los únicos que se dibujan como alfiler. */
111
+ export function negociosExactos(modelo) {
112
+ return negociosDelModelo(modelo).filter((negocio) => (negocio.precision ?? "exacta") === "exacta");
113
+ }
114
+ /** La ubicación del usuario: el punto y, si se sabe, su área de incertidumbre. */
115
+ export function coleccionDeUbicacion(modelo) {
116
+ const ubicacion = modelo.ubicacion;
117
+ if (ubicacion === undefined)
118
+ return coleccion([]);
119
+ const entidades = [
120
+ entidad({ type: "Point", coordinates: aPosicion(ubicacion.ubicacion) }, ubicacion.rumbo === undefined ? {} : { rumbo: ubicacion.rumbo }, "usuario"),
121
+ ];
122
+ if (ubicacion.precisionEnMetros !== undefined &&
123
+ ubicacion.precisionEnMetros > 0) {
124
+ entidades.unshift(entidad(circuloComoPoligono(ubicacion.ubicacion, ubicacion.precisionEnMetros), {}, "usuario-halo"));
125
+ }
126
+ return coleccion(entidades);
127
+ }
128
+ export function coleccionDeVehiculos(modelo) {
129
+ return coleccion(modelo.vehiculos.map((vehiculo) => entidad({ type: "Point", coordinates: aPosicion(vehiculo.ubicacion) }, {
130
+ id: vehiculo.id,
131
+ ...(vehiculo.rumbo === undefined ? {} : { rumbo: vehiculo.rumbo }),
132
+ ...(vehiculo.simbolo === undefined
133
+ ? {}
134
+ : { simbolo: vehiculo.simbolo }),
135
+ ...(vehiculo.rotulo === undefined ? {} : { rotulo: vehiculo.rotulo }),
136
+ }, vehiculo.id)));
137
+ }
138
+ /**
139
+ * Las rutas, con `principal` resuelto.
140
+ *
141
+ * La regla de desempate está aquí y no en el filtro del estilo porque una
142
+ * expresión de MapLibre no puede decidir «la primera si ninguna se marcó»:
143
+ * con dos principales dibujaría las dos encima, y con cero no dibujaría
144
+ * ninguna en la capa gruesa — la ruta elegida se vería como una alternativa.
145
+ */
146
+ export function coleccionDeRutas(modelo) {
147
+ const indicePrincipal = indiceDeRutaPrincipal(modelo.rutas);
148
+ return coleccion(modelo.rutas.map((ruta, indice) => entidad(ruta.geometria, {
149
+ id: ruta.id,
150
+ principal: indice === indicePrincipal,
151
+ ...(ruta.rotulo === undefined ? {} : { rotulo: ruta.rotulo }),
152
+ }, ruta.id)));
153
+ }
154
+ /** Cuál de las rutas es la principal: la primera marcada, o la primera a secas. */
155
+ export function indiceDeRutaPrincipal(rutas) {
156
+ if (rutas.length === 0)
157
+ return -1;
158
+ const marcada = rutas.findIndex((ruta) => ruta.principal === true);
159
+ return marcada === -1 ? 0 : marcada;
160
+ }
161
+ export function coleccionDeZonas(modelo) {
162
+ return coleccion(modelo.zonas.map((zona) => entidad(zona.geometria, {
163
+ id: zona.id,
164
+ enfasis: zona.enfasis ?? "normal",
165
+ ...(zona.rotulo === undefined ? {} : { rotulo: zona.rotulo }),
166
+ }, zona.id)));
167
+ }
168
+ /**
169
+ * Las isócronas, **de mayor a menor**.
170
+ *
171
+ * El orden importa y no es estético: las bandas se solapan —la de 15 minutos
172
+ * contiene entera a la de 5— y en una capa de relleno se dibujan en el orden
173
+ * de la colección. Con la pequeña primero, la grande la tapa y el mapa
174
+ * enseña un solo polígono plano.
175
+ */
176
+ export function coleccionDeIsocronas(modelo) {
177
+ const ordenadas = [...modelo.isocronas].sort((a, b) => b.minutos - a.minutos);
178
+ return coleccion(ordenadas.map((banda) => entidad(banda.geometria, { minutos: banda.minutos }, banda.minutos)));
179
+ }
180
+ /**
181
+ * La caja de todo lo que hay dibujado, o de una parte.
182
+ *
183
+ * Recorre las sedes además de los negocios: una cadena con cinco locales
184
+ * encuadrada solo por su sede principal deja las otras cuatro fuera de
185
+ * pantalla, y el mapa parece correcto.
186
+ */
187
+ export function cajaDelModelo(modelo, que = "todo") {
188
+ const cajas = [];
189
+ if (que === "todo" || que === "grupos") {
190
+ for (const grupo of gruposDelModelo(modelo))
191
+ cajas.push(grupo.caja);
192
+ }
193
+ if (que === "todo" || que === "negocios") {
194
+ const puntos = [];
195
+ for (const negocio of negociosDelModelo(modelo)) {
196
+ puntos.push(negocio.ubicacion);
197
+ for (const sede of negocio.sedes ?? [])
198
+ puntos.push(sede.ubicacion);
199
+ }
200
+ const caja = cajaDeCoordenadas(puntos);
201
+ if (caja !== undefined)
202
+ cajas.push(caja);
203
+ }
204
+ if (que === "todo" || que === "rutas") {
205
+ for (const ruta of modelo.rutas) {
206
+ const caja = cajaDeGeometria(ruta.geometria);
207
+ if (caja !== undefined)
208
+ cajas.push(caja);
209
+ }
210
+ }
211
+ if (que === "todo" || que === "zonas") {
212
+ for (const zona of modelo.zonas) {
213
+ const caja = cajaDeGeometria(zona.geometria);
214
+ if (caja !== undefined)
215
+ cajas.push(caja);
216
+ }
217
+ }
218
+ return cajas.reduce((acumulada, caja) => acumulada === undefined ? caja : unirCajas(acumulada, caja), undefined);
219
+ }
220
+ /** La caja de un negocio y sus sedes. Lo que se encuadra al seleccionarlo. */
221
+ export function cajaDeNegocio(negocio) {
222
+ return cajaDeCoordenadas([
223
+ negocio.ubicacion,
224
+ ...(negocio.sedes ?? []).map((sede) => sede.ubicacion),
225
+ ]);
226
+ }
227
+ //# sourceMappingURL=modelo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modelo.js","sourceRoot":"","sources":["../src/modelo.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type Caja,\n type ColeccionDeEntidades,\n type Coordenada,\n aPosicion,\n cajaDeCoordenadas,\n cajaDeGeometria,\n circuloComoPoligono,\n coleccion,\n entidad,\n unirCajas,\n} from \"@cerca.red/geo\";\n\nimport type { Tema } from \"./estilo.ts\";\nimport type {\n BandaDeIsocrona,\n GrupoEnMapa,\n NegocioEnMapa,\n ResultadosEnMapa,\n RutaEnMapa,\n UbicacionDeUsuarioEnMapa,\n VehiculoEnMapa,\n ZonaEnMapa,\n} from \"./proyecciones.ts\";\nimport type { Margenes } from \"./puerto.ts\";\n\n/**\n * El modelo: lo que el SDK sabe que hay que dibujar.\n *\n * ## La decisión que sostiene todo el paquete\n *\n * **El SDK tiene el modelo y el mapa es una proyección.** No al revés.\n *\n * La razón inmediata es que cambiar de tema o de idioma en MapLibre es\n * reemplazar el estilo entero, y el estilo nuevo llega **sin** las fuentes ni\n * las capas de Cerca. Si el estado viviera en el mapa, cambiar de tema\n * perdería los negocios, la ruta y la selección — y no de forma ruidosa: el\n * mapa se quedaría bonito y vacío.\n *\n * La razón de fondo es que hace fiel al doble de pruebas. Si el estado está\n * aquí, en objetos planos y funciones puras, entonces lo que se prueba en\n * Node es el comportamiento de verdad y no una imitación: la conversión de\n * `NegocioEnMapa` a GeoJSON, qué caja hay que encuadrar, qué pasa al\n * seleccionar algo que no existe. Lo que queda para WebGL es solo que el\n * motor pinte lo que se le dio.\n *\n * ## Inmutable\n *\n * Cada operación devuelve un modelo nuevo. No es dogma: el SDK compara el\n * modelo anterior con el nuevo para decidir qué fuentes tocar, y esa\n * comparación es imposible si el objeto se modificó por debajo.\n */\nexport interface ModeloDelMapa {\n readonly resultados: ResultadosEnMapa;\n readonly seleccionado: string | undefined;\n readonly ubicacion: UbicacionDeUsuarioEnMapa | undefined;\n readonly vehiculos: readonly VehiculoEnMapa[];\n readonly rutas: readonly RutaEnMapa[];\n readonly zonas: readonly ZonaEnMapa[];\n readonly isocronas: readonly BandaDeIsocrona[];\n readonly tema: Tema;\n readonly idioma: string;\n readonly margenes: Margenes;\n /**\n * Con cuánto radio se dibuja una ubicación marcada como aproximada.\n *\n * Vive aquí y **no** en `NegocioEnMapa` a propósito. Que una ubicación sea\n * aproximada lo decide Cerca; con cuántos metros de área se representa esa\n * imprecisión es una decisión de dibujo, y meterla en la proyección sería\n * el primer campo de la acumulación que ADR-027 dice que mata la frontera.\n */\n readonly radioDeImprecisionEnMetros: number;\n}\n\n/** El modelo de un mapa recién creado. */\nexport function modeloInicial(\n parciales:\n | (Partial<Omit<ModeloDelMapa, \"resultados\">> & {\n readonly resultados?: ResultadosEnMapa;\n readonly negocios?: never;\n })\n | (Partial<Omit<ModeloDelMapa, \"resultados\">> & {\n readonly resultados?: never;\n readonly negocios?: readonly NegocioEnMapa[];\n }) = {},\n): ModeloDelMapa {\n const { negocios, resultados, ...resto } = parciales;\n return {\n resultados: resultados ?? { modo: \"negocios\", negocios: negocios ?? [] },\n seleccionado: undefined,\n ubicacion: undefined,\n vehiculos: [],\n rutas: [],\n zonas: [],\n isocronas: [],\n tema: \"claro\",\n idioma: \"es\",\n margenes: { arriba: 0, derecha: 0, abajo: 0, izquierda: 0 },\n radioDeImprecisionEnMetros: 150,\n ...resto,\n };\n}\n\n/** Los negocios del modo actual; vacío mientras se muestran grupos del servidor. */\nexport function negociosDelModelo(\n modelo: ModeloDelMapa,\n): readonly NegocioEnMapa[] {\n return modelo.resultados.modo === \"negocios\"\n ? modelo.resultados.negocios\n : [];\n}\n\n/** Los grupos explícitos del modo actual; vacío mientras se muestran negocios. */\nexport function gruposDelModelo(modelo: ModeloDelMapa): readonly GrupoEnMapa[] {\n return modelo.resultados.modo === \"grupos\" ? modelo.resultados.grupos : [];\n}\n\n/** Las propiedades con las que viaja un negocio a la fuente GeoJSON. */\ninterface PropiedadesDeNegocio extends Record<string, unknown> {\n readonly id: string;\n readonly enfasis: string;\n readonly rotulo?: string;\n readonly simbolo?: string;\n}\n\n/**\n * Los negocios, como colección GeoJSON.\n *\n * El `id` va **dos veces**: como identificador de la entidad y como\n * propiedad. No es descuido. El identificador de entidad es lo que necesita\n * el estado de entidad —el resalte al pasar por encima— y tiene que ser\n * numérico o una cadena; la propiedad es lo que se lee al consultar lo\n * dibujado bajo el dedo, porque MapLibre **pierde el identificador de la\n * entidad al agrupar** y solo conserva las propiedades.\n */\nexport function coleccionDeNegocios(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<PropiedadesDeNegocio> {\n return coleccion(\n negociosExactos(modelo).map((negocio) =>\n entidad(\n { type: \"Point\", coordinates: aPosicion(negocio.ubicacion) },\n {\n id: negocio.id,\n enfasis: negocio.enfasis ?? \"normal\",\n ...(negocio.rotulo === undefined ? {} : { rotulo: negocio.rotulo }),\n ...(negocio.simbolo === undefined\n ? {}\n : { simbolo: negocio.simbolo }),\n },\n negocio.id,\n ),\n ),\n );\n}\n\ninterface PropiedadesDeGrupo extends Record<string, unknown> {\n readonly id: string;\n readonly cantidad: number;\n readonly rotulo?: string;\n}\n\n/** Los agregados del servidor, en una fuente sin clustering local. */\nexport function coleccionDeGrupos(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<PropiedadesDeGrupo> {\n return coleccion(\n gruposDelModelo(modelo).map((grupo) =>\n entidad(\n { type: \"Point\", coordinates: aPosicion(grupo.ubicacion) },\n {\n id: grupo.id,\n cantidad: grupo.cantidad,\n ...(grupo.rotulo === undefined ? {} : { rotulo: grupo.rotulo }),\n },\n grupo.id,\n ),\n ),\n );\n}\n\ninterface PropiedadesDeSede extends Record<string, unknown> {\n readonly id: string;\n readonly negocio: string;\n readonly rotulo?: string;\n}\n\n/** Las sedes de todos los negocios, aplanadas en una sola colección. */\nexport function coleccionDeSedes(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<PropiedadesDeSede> {\n const entidades = negociosDelModelo(modelo).flatMap((negocio) =>\n (negocio.sedes ?? []).map((sede) =>\n entidad(\n { type: \"Point\", coordinates: aPosicion(sede.ubicacion) },\n {\n id: sede.id,\n negocio: negocio.id,\n ...(sede.rotulo === undefined ? {} : { rotulo: sede.rotulo }),\n },\n sede.id,\n ),\n ),\n );\n return coleccion(entidades);\n}\n\n/**\n * El negocio seleccionado, solo. En su propia fuente porque tiene que\n * dibujarse **encima** de sus vecinos, y la clave de ordenación de símbolos\n * no lee el estado de entidad.\n */\nexport function coleccionDeSeleccion(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<PropiedadesDeNegocio> {\n const elegido = negociosDelModelo(modelo).find(\n (negocio) => negocio.id === modelo.seleccionado,\n );\n if (elegido === undefined) return coleccion([]);\n\n // Un negocio aproximado se resalta como área, no como alfiler. Seleccionar\n // no es motivo para desdecirse: si nadie verificó el punto, señalarlo con\n // un alfiler grande y naranja afirma con más fuerza algo que Cerca sigue\n // sin saber.\n const geometria =\n elegido.precision === \"aproximada\"\n ? circuloComoPoligono(\n elegido.ubicacion,\n modelo.radioDeImprecisionEnMetros,\n )\n : ({ type: \"Point\", coordinates: aPosicion(elegido.ubicacion) } as const);\n\n return coleccion([\n entidad(\n geometria,\n {\n id: elegido.id,\n enfasis: elegido.enfasis ?? \"normal\",\n ...(elegido.rotulo === undefined ? {} : { rotulo: elegido.rotulo }),\n },\n elegido.id,\n ),\n ]);\n}\n\n/**\n * Las áreas de las ubicaciones aproximadas.\n *\n * Esta es la función que hace visible una decisión que en una lista es una\n * etiqueta: «no se da por exacta una ubicación que nadie verificó». En un\n * mapa, un alfiler afirma un punto. Si nadie verificó ese punto, el alfiler\n * está afirmando algo que Cerca no sabe, y el usuario que camina hasta ahí\n * descubre el error caminando.\n *\n * Un negocio aproximado sale **como área y sin punto**: aparece en esta\n * colección y se filtra de la de negocios. Dibujar las dos cosas sería\n * desdecirse en el mismo mapa.\n */\nexport function coleccionDeImprecision(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<{ id: string }> {\n const areas = negociosDelModelo(modelo)\n .filter((negocio) => negocio.precision === \"aproximada\")\n .map((negocio) =>\n entidad(\n circuloComoPoligono(\n negocio.ubicacion,\n modelo.radioDeImprecisionEnMetros,\n ),\n { id: negocio.id },\n negocio.id,\n ),\n );\n return coleccion(areas);\n}\n\n/** Los negocios con ubicación exacta: los únicos que se dibujan como alfiler. */\nexport function negociosExactos(\n modelo: ModeloDelMapa,\n): readonly NegocioEnMapa[] {\n return negociosDelModelo(modelo).filter(\n (negocio) => (negocio.precision ?? \"exacta\") === \"exacta\",\n );\n}\n\n/** La ubicación del usuario: el punto y, si se sabe, su área de incertidumbre. */\nexport function coleccionDeUbicacion(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<Record<string, unknown>> {\n const ubicacion = modelo.ubicacion;\n if (ubicacion === undefined) return coleccion([]);\n\n const entidades = [\n entidad(\n { type: \"Point\", coordinates: aPosicion(ubicacion.ubicacion) },\n ubicacion.rumbo === undefined ? {} : { rumbo: ubicacion.rumbo },\n \"usuario\",\n ),\n ];\n\n if (\n ubicacion.precisionEnMetros !== undefined &&\n ubicacion.precisionEnMetros > 0\n ) {\n entidades.unshift(\n entidad(\n circuloComoPoligono(ubicacion.ubicacion, ubicacion.precisionEnMetros),\n {},\n \"usuario-halo\",\n ),\n );\n }\n\n return coleccion(entidades);\n}\n\nexport function coleccionDeVehiculos(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<Record<string, unknown>> {\n return coleccion(\n modelo.vehiculos.map((vehiculo) =>\n entidad(\n { type: \"Point\", coordinates: aPosicion(vehiculo.ubicacion) },\n {\n id: vehiculo.id,\n ...(vehiculo.rumbo === undefined ? {} : { rumbo: vehiculo.rumbo }),\n ...(vehiculo.simbolo === undefined\n ? {}\n : { simbolo: vehiculo.simbolo }),\n ...(vehiculo.rotulo === undefined ? {} : { rotulo: vehiculo.rotulo }),\n },\n vehiculo.id,\n ),\n ),\n );\n}\n\n/**\n * Las rutas, con `principal` resuelto.\n *\n * La regla de desempate está aquí y no en el filtro del estilo porque una\n * expresión de MapLibre no puede decidir «la primera si ninguna se marcó»:\n * con dos principales dibujaría las dos encima, y con cero no dibujaría\n * ninguna en la capa gruesa — la ruta elegida se vería como una alternativa.\n */\nexport function coleccionDeRutas(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<Record<string, unknown>> {\n const indicePrincipal = indiceDeRutaPrincipal(modelo.rutas);\n return coleccion(\n modelo.rutas.map((ruta, indice) =>\n entidad(\n ruta.geometria,\n {\n id: ruta.id,\n principal: indice === indicePrincipal,\n ...(ruta.rotulo === undefined ? {} : { rotulo: ruta.rotulo }),\n },\n ruta.id,\n ),\n ),\n );\n}\n\n/** Cuál de las rutas es la principal: la primera marcada, o la primera a secas. */\nexport function indiceDeRutaPrincipal(rutas: readonly RutaEnMapa[]): number {\n if (rutas.length === 0) return -1;\n const marcada = rutas.findIndex((ruta) => ruta.principal === true);\n return marcada === -1 ? 0 : marcada;\n}\n\nexport function coleccionDeZonas(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<Record<string, unknown>> {\n return coleccion(\n modelo.zonas.map((zona) =>\n entidad(\n zona.geometria,\n {\n id: zona.id,\n enfasis: zona.enfasis ?? \"normal\",\n ...(zona.rotulo === undefined ? {} : { rotulo: zona.rotulo }),\n },\n zona.id,\n ),\n ),\n );\n}\n\n/**\n * Las isócronas, **de mayor a menor**.\n *\n * El orden importa y no es estético: las bandas se solapan —la de 15 minutos\n * contiene entera a la de 5— y en una capa de relleno se dibujan en el orden\n * de la colección. Con la pequeña primero, la grande la tapa y el mapa\n * enseña un solo polígono plano.\n */\nexport function coleccionDeIsocronas(\n modelo: ModeloDelMapa,\n): ColeccionDeEntidades<Record<string, unknown>> {\n const ordenadas = [...modelo.isocronas].sort((a, b) => b.minutos - a.minutos);\n return coleccion(\n ordenadas.map((banda) =>\n entidad(banda.geometria, { minutos: banda.minutos }, banda.minutos),\n ),\n );\n}\n\n/**\n * La caja de todo lo que hay dibujado, o de una parte.\n *\n * Recorre las sedes además de los negocios: una cadena con cinco locales\n * encuadrada solo por su sede principal deja las otras cuatro fuera de\n * pantalla, y el mapa parece correcto.\n */\nexport function cajaDelModelo(\n modelo: ModeloDelMapa,\n que: \"todo\" | \"grupos\" | \"negocios\" | \"rutas\" | \"zonas\" = \"todo\",\n): Caja | undefined {\n const cajas: Caja[] = [];\n\n if (que === \"todo\" || que === \"grupos\") {\n for (const grupo of gruposDelModelo(modelo)) cajas.push(grupo.caja);\n }\n\n if (que === \"todo\" || que === \"negocios\") {\n const puntos: Coordenada[] = [];\n for (const negocio of negociosDelModelo(modelo)) {\n puntos.push(negocio.ubicacion);\n for (const sede of negocio.sedes ?? []) puntos.push(sede.ubicacion);\n }\n const caja = cajaDeCoordenadas(puntos);\n if (caja !== undefined) cajas.push(caja);\n }\n\n if (que === \"todo\" || que === \"rutas\") {\n for (const ruta of modelo.rutas) {\n const caja = cajaDeGeometria(ruta.geometria);\n if (caja !== undefined) cajas.push(caja);\n }\n }\n\n if (que === \"todo\" || que === \"zonas\") {\n for (const zona of modelo.zonas) {\n const caja = cajaDeGeometria(zona.geometria);\n if (caja !== undefined) cajas.push(caja);\n }\n }\n\n return cajas.reduce<Caja | undefined>(\n (acumulada, caja) =>\n acumulada === undefined ? caja : unirCajas(acumulada, caja),\n undefined,\n );\n}\n\n/** La caja de un negocio y sus sedes. Lo que se encuadra al seleccionarlo. */\nexport function cajaDeNegocio(negocio: NegocioEnMapa): Caja | undefined {\n return cajaDeCoordenadas([\n negocio.ubicacion,\n ...(negocio.sedes ?? []).map((sede) => sede.ubicacion),\n ]);\n}\n"]}
@@ -0,0 +1,70 @@
1
+ import type { Coordenada } from "@cerca.red/geo";
2
+ import { type Manifiesto } from "../manifiesto.ts";
3
+ import { type MapaDeCerca, type OpcionesDeCrearMapa } from "../sdk.ts";
4
+ /**
5
+ * `@cerca.red/mapa/navegador` — el mapa de verdad.
6
+ *
7
+ * Esta es la única subruta del paquete que alcanza `maplibre-gl` y `pmtiles`,
8
+ * y es explícita a propósito: importarla **se ve en el diff**, y quien la
9
+ * escribe sabe que acaba de meter WebGL y 250 KB de motor en ese archivo. El
10
+ * punto de entrada por defecto (`@cerca.red/mapa`) no llega hasta aquí ni
11
+ * indirectamente, y hay dos pruebas que lo comprueban —una ejecutando dentro
12
+ * de workerd, otra leyendo el grafo de imports—.
13
+ *
14
+ * En una pantalla de resultados el mapa se carga **bajo intención**: el estado
15
+ * por defecto es la lista, y el presupuesto de la primera pintura no paga el
16
+ * peso del motor. `packages/mapa-react` hace esa carga diferida por dentro,
17
+ * para que ninguna superficie tenga que acordarse.
18
+ */
19
+ export interface OpcionesDeCrearMapaEnNavegador extends OpcionesDeCrearMapa {
20
+ /** El elemento donde va el canvas. Tiene que tener alto. */
21
+ readonly contenedor: HTMLElement;
22
+ readonly centro?: Coordenada;
23
+ readonly zoom?: number;
24
+ readonly zoomMinimo?: number;
25
+ readonly zoomMaximo?: number;
26
+ /**
27
+ * De dónde bajar `maplibre-gl-worker.mjs`.
28
+ *
29
+ * Hace falta bajo Next.js y bajo cualquier bundler que reescriba
30
+ * `import.meta.url`: sin ella, MapLibre deduce la URL del worker a partir
31
+ * de la de la página y pide un archivo que no existe. Bajo Vite no hace
32
+ * falta y no se pasa.
33
+ *
34
+ * El archivo que hay que servir es `maplibre-gl-worker.mjs` de la **misma
35
+ * versión** de `maplibre-gl` que resuelve el proyecto:
36
+ * `RUTA_DEL_WORKER_DE_MAPLIBRE` y `VERSION_DE_MAPLIBRE`, exportadas desde
37
+ * `@cerca.red/mapa`, son lo que se necesita para copiarlo y comprobarlo en
38
+ * la compilación de la superficie.
39
+ *
40
+ * Se aplica antes de construir el mapa. Quien prefiera fijarla una sola vez
41
+ * al arrancar usa `fijarUrlDelWorker`.
42
+ */
43
+ readonly urlDelWorker?: string;
44
+ /** Adónde lleva el enlace de licencias. Sin esta opción no se añade. */
45
+ readonly urlDeLicencias?: string;
46
+ }
47
+ /**
48
+ * Construye el mapa.
49
+ *
50
+ * Lanza `SinWebGL2` si el navegador no puede dibujarlo. No es un fallo
51
+ * inesperado: MapLibre 6 exige WebGL2 sin alternativa, y lo que corresponde
52
+ * entonces es enseñar la lista (SDD §13). Se puede preguntar antes con
53
+ * `hayWebGL2()`, o dejar que `<Mapa>` de `@cerca.red/mapa-react` lo resuelva
54
+ * con su `respaldo`.
55
+ */
56
+ export declare function crearMapa(opciones: OpcionesDeCrearMapaEnNavegador): MapaDeCerca;
57
+ /**
58
+ * Lo mismo, pero sacando la URL del manifiesto.
59
+ *
60
+ * Es la forma que deberían usar las superficies, y existe para que **nadie
61
+ * componga una URL de `.pmtiles`**: se le da el manifiesto ya leído y el
62
+ * centro inicial, y elige el archivo con más detalle que cubra ese punto.
63
+ * Promover un puntero a una versión anterior revierte de verdad solo si esto
64
+ * es lo único que construye URLs (ADR-024, ADR-027).
65
+ */
66
+ export declare function crearMapaDesdeManifiesto(manifiesto: Manifiesto, opciones: Omit<OpcionesDeCrearMapaEnNavegador, "urlDeTeselas">): MapaDeCerca;
67
+ export { adaptarMapLibre, type OpcionesDelAdaptador } from "./maplibre.ts";
68
+ export { registrarProtocoloPmtiles, cuantosUsanElProtocolo, } from "./protocolo.ts";
69
+ export { hayWebGL2, SinWebGL2 } from "./webgl.ts";
70
+ export { fijarUrlDelWorker } from "./worker.ts";
@@ -0,0 +1,132 @@
1
+ import { archivoQueCubre, urlDeProtocolo, } from "../manifiesto.js";
2
+ import { crearMapaConMotor, } from "../sdk.js";
3
+ import { adaptarMapLibre } from "./maplibre.js";
4
+ import { registrarProtocoloPmtiles } from "./protocolo.js";
5
+ import { hayWebGL2, SinWebGL2 } from "./webgl.js";
6
+ /**
7
+ * Construye el mapa.
8
+ *
9
+ * Lanza `SinWebGL2` si el navegador no puede dibujarlo. No es un fallo
10
+ * inesperado: MapLibre 6 exige WebGL2 sin alternativa, y lo que corresponde
11
+ * entonces es enseñar la lista (SDD §13). Se puede preguntar antes con
12
+ * `hayWebGL2()`, o dejar que `<Mapa>` de `@cerca.red/mapa-react` lo resuelva
13
+ * con su `respaldo`.
14
+ */
15
+ export function crearMapa(opciones) {
16
+ if (!hayWebGL2())
17
+ throw new SinWebGL2();
18
+ // Antes de construir el mapa: el estilo va a referirse a `pmtiles://…` y
19
+ // MapLibre pide las teselas en cuanto lo carga.
20
+ const soltarProtocolo = registrarProtocoloPmtiles();
21
+ try {
22
+ const motor = adaptarMapLibre({
23
+ contenedor: opciones.contenedor,
24
+ // El estilo se calcula aquí y el mapa nace con él. El SDK se entera por
25
+ // `estiloYaAplicado` y no lo vuelve a poner: ponerlo dos veces descarga
26
+ // los glifos dos veces.
27
+ estilo: opciones.estilo({
28
+ urlDeTeselas: opciones.urlDeTeselas,
29
+ tema: opciones.tema ?? "claro",
30
+ idioma: opciones.idioma ?? "es",
31
+ }),
32
+ ...(opciones.centro === undefined ? {} : { centro: opciones.centro }),
33
+ ...(opciones.zoom === undefined ? {} : { zoom: opciones.zoom }),
34
+ ...(opciones.zoomMinimo === undefined
35
+ ? {}
36
+ : { zoomMinimo: opciones.zoomMinimo }),
37
+ ...(opciones.zoomMaximo === undefined
38
+ ? {}
39
+ : { zoomMaximo: opciones.zoomMaximo }),
40
+ ...(opciones.urlDelWorker === undefined
41
+ ? {}
42
+ : { urlDelWorker: opciones.urlDelWorker }),
43
+ ...(opciones.urlDeLicencias === undefined
44
+ ? {}
45
+ : { urlDeLicencias: opciones.urlDeLicencias }),
46
+ });
47
+ const mapa = crearMapaConMotor(motor, {
48
+ ...opciones,
49
+ estiloYaAplicado: true,
50
+ });
51
+ // `destruir` tiene que soltar además el protocolo, y hacerlo **aquí** y no
52
+ // dentro del SDK es lo que mantiene al núcleo sin saber que pmtiles
53
+ // existe.
54
+ return {
55
+ ...mapa,
56
+ // Los métodos se reenvían atados al objeto original: repartir un objeto
57
+ // con `...` sobre una clase pierde el `this` de sus métodos.
58
+ ponerResultados: mapa.ponerResultados.bind(mapa),
59
+ ponerNegocios: mapa.ponerNegocios.bind(mapa),
60
+ seleccionarNegocio: mapa.seleccionarNegocio.bind(mapa),
61
+ ponerUbicacionDeUsuario: mapa.ponerUbicacionDeUsuario.bind(mapa),
62
+ ponerVehiculos: mapa.ponerVehiculos.bind(mapa),
63
+ moverVehiculo: mapa.moverVehiculo.bind(mapa),
64
+ mostrarRuta: mapa.mostrarRuta.bind(mapa),
65
+ mostrarZonaDeCobertura: mapa.mostrarZonaDeCobertura.bind(mapa),
66
+ mostrarIsocrona: mapa.mostrarIsocrona.bind(mapa),
67
+ encuadrar: mapa.encuadrar.bind(mapa),
68
+ volarA: mapa.volarA.bind(mapa),
69
+ vista: mapa.vista.bind(mapa),
70
+ proyectar: mapa.proyectar.bind(mapa),
71
+ desproyectar: mapa.desproyectar.bind(mapa),
72
+ queHayEn: mapa.queHayEn.bind(mapa),
73
+ ponerTema: mapa.ponerTema.bind(mapa),
74
+ ponerIdioma: mapa.ponerIdioma.bind(mapa),
75
+ registrarSimbolos: mapa.registrarSimbolos.bind(mapa),
76
+ ponerMargenes: mapa.ponerMargenes.bind(mapa),
77
+ agregarCapaExtra: mapa.agregarCapaExtra.bind(mapa),
78
+ cuando: mapa.cuando.bind(mapa),
79
+ unaVez: mapa.unaVez.bind(mapa),
80
+ estaDestruido: mapa.estaDestruido.bind(mapa),
81
+ destruir: () => {
82
+ mapa.destruir();
83
+ soltarProtocolo();
84
+ },
85
+ };
86
+ }
87
+ catch (fallo) {
88
+ // Si la construcción se cae a medias, el protocolo queda registrado y el
89
+ // contador nunca vuelve a cero: el siguiente mapa lo hereda y el
90
+ // `removeProtocol` del último se lleva por delante a alguien.
91
+ soltarProtocolo();
92
+ throw fallo;
93
+ }
94
+ }
95
+ /**
96
+ * Lo mismo, pero sacando la URL del manifiesto.
97
+ *
98
+ * Es la forma que deberían usar las superficies, y existe para que **nadie
99
+ * componga una URL de `.pmtiles`**: se le da el manifiesto ya leído y el
100
+ * centro inicial, y elige el archivo con más detalle que cubra ese punto.
101
+ * Promover un puntero a una versión anterior revierte de verdad solo si esto
102
+ * es lo único que construye URLs (ADR-024, ADR-027).
103
+ */
104
+ export function crearMapaDesdeManifiesto(manifiesto, opciones) {
105
+ const centro = opciones.centro;
106
+ const archivo = centro === undefined
107
+ ? manifiesto.archivos[0]
108
+ : (archivoQueCubre(manifiesto, centro) ?? manifiesto.archivos[0]);
109
+ if (archivo === undefined) {
110
+ throw new Error("crearMapaDesdeManifiesto: el manifiesto no trae ningún archivo de " +
111
+ "teselas. `leerManifiesto` ya rechaza ese caso, así que llegar aquí " +
112
+ "significa que alguien construyó el objeto a mano.");
113
+ }
114
+ return crearMapa({
115
+ ...opciones,
116
+ urlDeTeselas: urlDeProtocolo(archivo),
117
+ // La atribución **no se pasa al estilo**: viaja dentro del `.pmtiles` y el
118
+ // protocolo la publica solo. `manifiesto.atribucion` sigue disponible para
119
+ // quien tenga que mostrarla fuera del mapa —una lista sin canvas, un aviso
120
+ // legal—, pero no se le da al estilo, porque duplicar un texto legal es
121
+ // como una copia acaba quedándose atrás.
122
+ // Sin esto se puede navegar hasta el otro lado del mundo y ver un mapa
123
+ // vacío, que parece un fallo de carga.
124
+ cajaMaxima: opciones.cajaMaxima ?? archivo.caja,
125
+ zoomMaximo: opciones.zoomMaximo ?? archivo.zoomMaximo + 2,
126
+ });
127
+ }
128
+ export { adaptarMapLibre } from "./maplibre.js";
129
+ export { registrarProtocoloPmtiles, cuantosUsanElProtocolo, } from "./protocolo.js";
130
+ export { hayWebGL2, SinWebGL2 } from "./webgl.js";
131
+ export { fijarUrlDelWorker } from "./worker.js";
132
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/navegador/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Coordenada } from \"@cerca.red/geo\";\n\nimport {\n archivoQueCubre,\n type Manifiesto,\n urlDeProtocolo,\n} from \"../manifiesto.ts\";\nimport {\n crearMapaConMotor,\n type MapaDeCerca,\n type OpcionesDeCrearMapa,\n} from \"../sdk.ts\";\nimport { adaptarMapLibre } from \"./maplibre.ts\";\nimport { registrarProtocoloPmtiles } from \"./protocolo.ts\";\nimport { hayWebGL2, SinWebGL2 } from \"./webgl.ts\";\n\n/**\n * `@cerca.red/mapa/navegador` — el mapa de verdad.\n *\n * Esta es la única subruta del paquete que alcanza `maplibre-gl` y `pmtiles`,\n * y es explícita a propósito: importarla **se ve en el diff**, y quien la\n * escribe sabe que acaba de meter WebGL y 250 KB de motor en ese archivo. El\n * punto de entrada por defecto (`@cerca.red/mapa`) no llega hasta aquí ni\n * indirectamente, y hay dos pruebas que lo comprueban —una ejecutando dentro\n * de workerd, otra leyendo el grafo de imports—.\n *\n * En una pantalla de resultados el mapa se carga **bajo intención**: el estado\n * por defecto es la lista, y el presupuesto de la primera pintura no paga el\n * peso del motor. `packages/mapa-react` hace esa carga diferida por dentro,\n * para que ninguna superficie tenga que acordarse.\n */\n\nexport interface OpcionesDeCrearMapaEnNavegador extends OpcionesDeCrearMapa {\n /** El elemento donde va el canvas. Tiene que tener alto. */\n readonly contenedor: HTMLElement;\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`.\n *\n * Hace falta bajo Next.js y bajo cualquier bundler que reescriba\n * `import.meta.url`: sin ella, MapLibre deduce la URL del worker a partir\n * de la de la página y pide un archivo que no existe. Bajo Vite no hace\n * falta y no se pasa.\n *\n * El archivo que hay que servir es `maplibre-gl-worker.mjs` de la **misma\n * versión** de `maplibre-gl` que resuelve el proyecto:\n * `RUTA_DEL_WORKER_DE_MAPLIBRE` y `VERSION_DE_MAPLIBRE`, exportadas desde\n * `@cerca.red/mapa`, son lo que se necesita para copiarlo y comprobarlo en\n * la compilación de la superficie.\n *\n * Se aplica antes de construir el mapa. Quien prefiera fijarla una sola vez\n * al arrancar usa `fijarUrlDelWorker`.\n */\n readonly urlDelWorker?: string;\n /** Adónde lleva el enlace de licencias. Sin esta opción no se añade. */\n readonly urlDeLicencias?: string;\n}\n\n/**\n * Construye el mapa.\n *\n * Lanza `SinWebGL2` si el navegador no puede dibujarlo. No es un fallo\n * inesperado: MapLibre 6 exige WebGL2 sin alternativa, y lo que corresponde\n * entonces es enseñar la lista (SDD §13). Se puede preguntar antes con\n * `hayWebGL2()`, o dejar que `<Mapa>` de `@cerca.red/mapa-react` lo resuelva\n * con su `respaldo`.\n */\nexport function crearMapa(\n opciones: OpcionesDeCrearMapaEnNavegador,\n): MapaDeCerca {\n if (!hayWebGL2()) throw new SinWebGL2();\n\n // Antes de construir el mapa: el estilo va a referirse a `pmtiles://…` y\n // MapLibre pide las teselas en cuanto lo carga.\n const soltarProtocolo = registrarProtocoloPmtiles();\n\n try {\n const motor = adaptarMapLibre({\n contenedor: opciones.contenedor,\n // El estilo se calcula aquí y el mapa nace con él. El SDK se entera por\n // `estiloYaAplicado` y no lo vuelve a poner: ponerlo dos veces descarga\n // los glifos dos veces.\n estilo: opciones.estilo({\n urlDeTeselas: opciones.urlDeTeselas,\n tema: opciones.tema ?? \"claro\",\n idioma: opciones.idioma ?? \"es\",\n }),\n ...(opciones.centro === undefined ? {} : { centro: opciones.centro }),\n ...(opciones.zoom === undefined ? {} : { zoom: opciones.zoom }),\n ...(opciones.zoomMinimo === undefined\n ? {}\n : { zoomMinimo: opciones.zoomMinimo }),\n ...(opciones.zoomMaximo === undefined\n ? {}\n : { zoomMaximo: opciones.zoomMaximo }),\n ...(opciones.urlDelWorker === undefined\n ? {}\n : { urlDelWorker: opciones.urlDelWorker }),\n ...(opciones.urlDeLicencias === undefined\n ? {}\n : { urlDeLicencias: opciones.urlDeLicencias }),\n });\n\n const mapa = crearMapaConMotor(motor, {\n ...opciones,\n estiloYaAplicado: true,\n });\n\n // `destruir` tiene que soltar además el protocolo, y hacerlo **aquí** y no\n // dentro del SDK es lo que mantiene al núcleo sin saber que pmtiles\n // existe.\n return {\n ...mapa,\n // Los métodos se reenvían atados al objeto original: repartir un objeto\n // con `...` sobre una clase pierde el `this` de sus métodos.\n ponerResultados: mapa.ponerResultados.bind(mapa),\n ponerNegocios: mapa.ponerNegocios.bind(mapa),\n seleccionarNegocio: mapa.seleccionarNegocio.bind(mapa),\n ponerUbicacionDeUsuario: mapa.ponerUbicacionDeUsuario.bind(mapa),\n ponerVehiculos: mapa.ponerVehiculos.bind(mapa),\n moverVehiculo: mapa.moverVehiculo.bind(mapa),\n mostrarRuta: mapa.mostrarRuta.bind(mapa),\n mostrarZonaDeCobertura: mapa.mostrarZonaDeCobertura.bind(mapa),\n mostrarIsocrona: mapa.mostrarIsocrona.bind(mapa),\n encuadrar: mapa.encuadrar.bind(mapa),\n volarA: mapa.volarA.bind(mapa),\n vista: mapa.vista.bind(mapa),\n proyectar: mapa.proyectar.bind(mapa),\n desproyectar: mapa.desproyectar.bind(mapa),\n queHayEn: mapa.queHayEn.bind(mapa),\n ponerTema: mapa.ponerTema.bind(mapa),\n ponerIdioma: mapa.ponerIdioma.bind(mapa),\n registrarSimbolos: mapa.registrarSimbolos.bind(mapa),\n ponerMargenes: mapa.ponerMargenes.bind(mapa),\n agregarCapaExtra: mapa.agregarCapaExtra.bind(mapa),\n cuando: mapa.cuando.bind(mapa),\n unaVez: mapa.unaVez.bind(mapa),\n estaDestruido: mapa.estaDestruido.bind(mapa),\n destruir: () => {\n mapa.destruir();\n soltarProtocolo();\n },\n };\n } catch (fallo: unknown) {\n // Si la construcción se cae a medias, el protocolo queda registrado y el\n // contador nunca vuelve a cero: el siguiente mapa lo hereda y el\n // `removeProtocol` del último se lleva por delante a alguien.\n soltarProtocolo();\n throw fallo;\n }\n}\n\n/**\n * Lo mismo, pero sacando la URL del manifiesto.\n *\n * Es la forma que deberían usar las superficies, y existe para que **nadie\n * componga una URL de `.pmtiles`**: se le da el manifiesto ya leído y el\n * centro inicial, y elige el archivo con más detalle que cubra ese punto.\n * Promover un puntero a una versión anterior revierte de verdad solo si esto\n * es lo único que construye URLs (ADR-024, ADR-027).\n */\nexport function crearMapaDesdeManifiesto(\n manifiesto: Manifiesto,\n opciones: Omit<OpcionesDeCrearMapaEnNavegador, \"urlDeTeselas\">,\n): MapaDeCerca {\n const centro = opciones.centro;\n const archivo =\n centro === undefined\n ? manifiesto.archivos[0]\n : (archivoQueCubre(manifiesto, centro) ?? manifiesto.archivos[0]);\n\n if (archivo === undefined) {\n throw new Error(\n \"crearMapaDesdeManifiesto: el manifiesto no trae ningún archivo de \" +\n \"teselas. `leerManifiesto` ya rechaza ese caso, así que llegar aquí \" +\n \"significa que alguien construyó el objeto a mano.\",\n );\n }\n\n return crearMapa({\n ...opciones,\n urlDeTeselas: urlDeProtocolo(archivo),\n // La atribución **no se pasa al estilo**: viaja dentro del `.pmtiles` y el\n // protocolo la publica solo. `manifiesto.atribucion` sigue disponible para\n // quien tenga que mostrarla fuera del mapa —una lista sin canvas, un aviso\n // legal—, pero no se le da al estilo, porque duplicar un texto legal es\n // como una copia acaba quedándose atrás.\n // Sin esto se puede navegar hasta el otro lado del mundo y ver un mapa\n // vacío, que parece un fallo de carga.\n cajaMaxima: opciones.cajaMaxima ?? archivo.caja,\n zoomMaximo: opciones.zoomMaximo ?? archivo.zoomMaximo + 2,\n });\n}\n\nexport { adaptarMapLibre, type OpcionesDelAdaptador } from \"./maplibre.ts\";\nexport {\n registrarProtocoloPmtiles,\n cuantosUsanElProtocolo,\n} from \"./protocolo.ts\";\nexport { hayWebGL2, SinWebGL2 } from \"./webgl.ts\";\nexport { fijarUrlDelWorker } from \"./worker.ts\";\n"]}
@@ -0,0 +1,41 @@
1
+ import type { Coordenada } from "@cerca.red/geo";
2
+ import type { EspecificacionDeEstilo } from "../estilo.ts";
3
+ import type { MapaNativo } from "../puerto.ts";
4
+ export interface OpcionesDelAdaptador {
5
+ readonly contenedor: HTMLElement;
6
+ /** Si no se da, el mapa nace vacío y el estilo lo pone el SDK. */
7
+ readonly estilo?: EspecificacionDeEstilo;
8
+ readonly centro?: Coordenada;
9
+ readonly zoom?: number;
10
+ readonly zoomMinimo?: number;
11
+ readonly zoomMaximo?: number;
12
+ /**
13
+ * De dónde bajar `maplibre-gl-worker.mjs`, para los bundlers que reescriben
14
+ * `import.meta.url` y dejan a MapLibre deduciendo mal esa URL.
15
+ *
16
+ * Se aplica **antes** de construir el mapa, y tiene que ser así: el pool de
17
+ * workers se crea dentro del constructor y no vuelve a leer la URL mientras
18
+ * quede un mapa vivo. El porqué entero está en `worker.ts`.
19
+ *
20
+ * Sin esta opción no se toca nada, que es lo correcto bajo Vite: ahí la
21
+ * deducción por defecto funciona.
22
+ */
23
+ readonly urlDelWorker?: string;
24
+ /**
25
+ * Adónde lleva el enlace de licencias de la esquina inferior derecha.
26
+ *
27
+ * El control de atribución de MapLibre ya muestra el `© OpenStreetMap` que
28
+ * viene en los metadatos del `.pmtiles` — eso es obligación legal y no se
29
+ * toca. Este es un control aparte, más pequeño, para el documento que junta
30
+ * esa atribución con el resto de la cadena de licencias (el perfil de
31
+ * teselas, los proveedores). Sin esta opción no se añade nada.
32
+ */
33
+ readonly urlDeLicencias?: string;
34
+ }
35
+ /**
36
+ * Envuelve un mapa de MapLibre en el puerto.
37
+ *
38
+ * No comprueba WebGL2 —eso lo hace `crearMapa` antes de llegar aquí— porque
39
+ * esta función también se usa desde el laboratorio con un mapa ya construido.
40
+ */
41
+ export declare function adaptarMapLibre(opciones: OpcionesDelAdaptador): MapaNativo;