zenit-sdk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1095 @@
1
+ import {
2
+ ChevronLeft,
3
+ ChevronRight,
4
+ Eye,
5
+ EyeOff,
6
+ FloatingChatBox,
7
+ Layers,
8
+ Upload,
9
+ X,
10
+ ZenitFeatureFilterPanel,
11
+ ZenitLayerManager,
12
+ ZenitMap,
13
+ ZoomIn,
14
+ applyFilteredGeoJSONStrategy,
15
+ applyLayerOverrides,
16
+ centroidFromBBox,
17
+ clampNumber,
18
+ clampOpacity,
19
+ computeBBoxFromFeature,
20
+ createChatService,
21
+ extractMapDto,
22
+ getAccentByLayerId,
23
+ getEffectiveLayerOpacity,
24
+ getLayerColor,
25
+ getLayerZoomOpacityFactor,
26
+ getStyleByLayerId,
27
+ getZoomOpacityFactor,
28
+ initLayerStates,
29
+ isPolygonLayer,
30
+ normalizeMapCenter,
31
+ normalizeMapLayers,
32
+ resetOverrides,
33
+ resolveLayerAccent,
34
+ sendMessage,
35
+ sendMessageStream,
36
+ useSendMessage,
37
+ useSendMessageStream
38
+ } from "./chunk-RGS6AZWV.mjs";
39
+
40
+ // src/http/HttpClient.ts
41
+ var HttpClient = class {
42
+ constructor(options) {
43
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
44
+ this.resolveTokens = options.resolveTokens;
45
+ this.resolveAuthorizationHeader = options.resolveAuthorizationHeader ?? (() => ({}));
46
+ }
47
+ async get(path, options = {}) {
48
+ const headers = {
49
+ ...options.headers,
50
+ ...this.resolveAuthorizationHeader()
51
+ };
52
+ return this.request(path, { ...options, method: "GET", headers });
53
+ }
54
+ async post(path, body, options = {}) {
55
+ const headers = {
56
+ "Content-Type": "application/json",
57
+ ...options.headers,
58
+ ...this.resolveAuthorizationHeader()
59
+ };
60
+ return this.request(path, {
61
+ ...options,
62
+ method: "POST",
63
+ headers,
64
+ body: body !== void 0 ? JSON.stringify(body) : void 0
65
+ });
66
+ }
67
+ async put(path, body, options = {}) {
68
+ const headers = {
69
+ "Content-Type": "application/json",
70
+ ...options.headers,
71
+ ...this.resolveAuthorizationHeader()
72
+ };
73
+ return this.request(path, {
74
+ ...options,
75
+ method: "PUT",
76
+ headers,
77
+ body: body !== void 0 ? JSON.stringify(body) : void 0
78
+ });
79
+ }
80
+ async patch(path, body, options = {}) {
81
+ const headers = {
82
+ "Content-Type": "application/json",
83
+ ...options.headers,
84
+ ...this.resolveAuthorizationHeader()
85
+ };
86
+ return this.request(path, {
87
+ ...options,
88
+ method: "PATCH",
89
+ headers,
90
+ body: body !== void 0 ? JSON.stringify(body) : void 0
91
+ });
92
+ }
93
+ async delete(path, options = {}) {
94
+ const headers = {
95
+ ...options.headers,
96
+ ...this.resolveAuthorizationHeader()
97
+ };
98
+ return this.request(path, { ...options, method: "DELETE", headers });
99
+ }
100
+ async request(path, options) {
101
+ const url = `${this.baseUrl}${path}`;
102
+ const tokenState = this.resolveTokens?.();
103
+ const headers = {
104
+ ...options.headers
105
+ };
106
+ if (!headers["Authorization"] && tokenState?.accessToken) {
107
+ headers["Authorization"] = `Bearer ${tokenState.accessToken}`;
108
+ }
109
+ if (tokenState?.sdkToken) {
110
+ headers["X-SDK-Token"] = tokenState.sdkToken;
111
+ }
112
+ const response = await fetch(url, {
113
+ ...options,
114
+ headers
115
+ });
116
+ const contentType = response.headers.get("content-type");
117
+ const isJson = contentType?.includes("application/json");
118
+ const payload = isJson ? await response.json() : await response.text();
119
+ if (!response.ok) {
120
+ const normalizedError = {
121
+ success: false,
122
+ statusCode: response.status,
123
+ message: typeof payload === "string" ? payload : payload?.message || "Unknown error",
124
+ timestamp: typeof payload === "object" ? payload.timestamp : void 0,
125
+ path: typeof payload === "object" ? payload.path : void 0,
126
+ method: typeof payload === "object" ? payload.method : void 0,
127
+ error: typeof payload === "object" ? payload.error : void 0
128
+ };
129
+ throw normalizedError;
130
+ }
131
+ return payload;
132
+ }
133
+ };
134
+
135
+ // src/auth/ZenitAuthClient.ts
136
+ var ZenitAuthClient = class {
137
+ constructor(http, updateTokens, config) {
138
+ this.http = http;
139
+ this.updateTokens = updateTokens;
140
+ this.config = config;
141
+ }
142
+ async login(credentials) {
143
+ try {
144
+ const response = await this.http.post("/auth/login", credentials);
145
+ const accessToken = response?.accessToken ?? response?.data?.accessToken;
146
+ const refreshToken = response?.refreshToken ?? response?.data?.refreshToken;
147
+ this.updateTokens({ accessToken, refreshToken });
148
+ return response;
149
+ } catch (error) {
150
+ this.config.onAuthError?.(error);
151
+ throw error;
152
+ }
153
+ }
154
+ async refresh(refreshToken) {
155
+ const tokenToUse = refreshToken || this.config.refreshToken;
156
+ const headers = tokenToUse ? { Authorization: `Bearer ${tokenToUse}` } : void 0;
157
+ const response = await this.http.post("/auth/refresh", void 0, { headers });
158
+ const accessToken = response?.accessToken ?? response?.data?.accessToken;
159
+ const nextRefreshToken = response?.refreshToken ?? response?.data?.refreshToken;
160
+ this.updateTokens({ accessToken, refreshToken: nextRefreshToken });
161
+ return response;
162
+ }
163
+ async me() {
164
+ return this.http.get("/auth/me");
165
+ }
166
+ async validate() {
167
+ return this.http.get("/auth/validate");
168
+ }
169
+ };
170
+
171
+ // src/sdkAuth/ZenitSdkAuthClient.ts
172
+ var ZenitSdkAuthClient = class {
173
+ constructor(http, updateAccessToken, config) {
174
+ this.http = http;
175
+ this.updateAccessToken = updateAccessToken;
176
+ this.config = config;
177
+ }
178
+ /**
179
+ * Validate an SDK token. If no token argument is provided, it falls back to config.sdkToken.
180
+ */
181
+ async validateSdkToken(token) {
182
+ const sdkToken = token || this.config.sdkToken;
183
+ return this.http.post("/sdk-auth/validate", { token: sdkToken });
184
+ }
185
+ /**
186
+ * Exchange an SDK token for an access token. Token can come from method args or config.sdkToken.
187
+ */
188
+ async exchangeSdkToken(token) {
189
+ const sdkToken = token || this.config.sdkToken;
190
+ const response = await this.http.post("/sdk-auth/exchange", { token: sdkToken });
191
+ const accessToken = response?.accessToken ?? response?.data?.accessToken;
192
+ this.updateAccessToken(accessToken);
193
+ return response;
194
+ }
195
+ };
196
+
197
+ // src/maps/ZenitMapsClient.ts
198
+ var ZenitMapsClient = class {
199
+ constructor(httpClient) {
200
+ this.httpClient = httpClient;
201
+ }
202
+ async getMap(mapId, includeLayers = true) {
203
+ const include = includeLayers ? "?includeLayers=true" : "";
204
+ return this.httpClient.get(`/maps/${mapId}${include}`);
205
+ }
206
+ };
207
+
208
+ // src/layers/catalogSupport.ts
209
+ function isPolygonLike(value) {
210
+ if (!value) {
211
+ return false;
212
+ }
213
+ const normalized = value.toLowerCase();
214
+ return normalized === "polygon" || normalized === "multipolygon";
215
+ }
216
+ function getCatalogSupport(args) {
217
+ const { layerType, geometryType } = args;
218
+ if (layerType !== void 0) {
219
+ const supported = isPolygonLike(layerType);
220
+ return {
221
+ supported,
222
+ reason: supported ? "supported" : "unsupported-geometry",
223
+ layerType,
224
+ geometryType
225
+ };
226
+ }
227
+ if (geometryType !== void 0) {
228
+ const supported = isPolygonLike(geometryType);
229
+ return {
230
+ supported,
231
+ reason: supported ? "supported" : "unsupported-geometry",
232
+ geometryType
233
+ };
234
+ }
235
+ return {
236
+ supported: false,
237
+ reason: "unknown-geometry"
238
+ };
239
+ }
240
+ var ZenitCatalogNotSupportedError = class extends Error {
241
+ constructor(support, message) {
242
+ super(message ?? "Catalog is not supported for this layer geometry");
243
+ this.name = "ZenitCatalogNotSupportedError";
244
+ this.support = support;
245
+ Object.setPrototypeOf(this, new.target.prototype);
246
+ }
247
+ };
248
+
249
+ // src/engine/PrefilterEngine.ts
250
+ var normalizeString = (value) => value.trim().toLowerCase();
251
+ var isPrefilterValue = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
252
+ var matchesPrefilterValue = (candidate, expected) => {
253
+ if (typeof expected === "string") {
254
+ if (candidate === null || candidate === void 0) return false;
255
+ return normalizeString(String(candidate)) === normalizeString(expected);
256
+ }
257
+ return candidate === expected;
258
+ };
259
+ var shouldApplyPrefilters = (prefilters) => !!prefilters && Object.keys(prefilters).length > 0;
260
+ var featureMatchesPrefilters = (feature, prefilters) => {
261
+ const properties = feature.properties ?? {};
262
+ return Object.entries(prefilters).every(([key, expected]) => {
263
+ if (!isPrefilterValue(expected)) return true;
264
+ return matchesPrefilterValue(properties[key], expected);
265
+ });
266
+ };
267
+ var applyPrefiltersToFeatureCollection = (featureCollection, prefilters) => {
268
+ if (!shouldApplyPrefilters(prefilters)) return featureCollection;
269
+ const nextFeatures = featureCollection.features.filter(
270
+ (feature) => featureMatchesPrefilters(feature, prefilters)
271
+ );
272
+ return {
273
+ ...featureCollection,
274
+ features: nextFeatures
275
+ };
276
+ };
277
+ var decorateFeaturesWithLayerId = (featureCollection, layerId) => {
278
+ let shouldClone = false;
279
+ featureCollection.features.forEach((feature) => {
280
+ const existing = feature.properties?.__zenit_layerId;
281
+ if (existing !== layerId) {
282
+ shouldClone = true;
283
+ }
284
+ });
285
+ if (!shouldClone) {
286
+ return featureCollection;
287
+ }
288
+ return {
289
+ ...featureCollection,
290
+ features: featureCollection.features.map((feature) => ({
291
+ ...feature,
292
+ properties: {
293
+ ...feature.properties ?? {},
294
+ __zenit_layerId: layerId
295
+ }
296
+ }))
297
+ };
298
+ };
299
+
300
+ // src/layers/ZenitLayersClient.ts
301
+ function parseQueryParams(options = {}) {
302
+ const params = new URLSearchParams();
303
+ if (options.maxFeatures !== void 0) {
304
+ params.set("maxFeatures", String(options.maxFeatures));
305
+ }
306
+ if (options.simplify !== void 0) {
307
+ params.set("simplify", String(options.simplify));
308
+ }
309
+ return params;
310
+ }
311
+ function serializeFilterValue(value) {
312
+ if (Array.isArray(value)) {
313
+ const serialized2 = value.map(String).filter((item) => item.trim().length > 0).join(",");
314
+ return serialized2.length > 0 ? serialized2 : void 0;
315
+ }
316
+ if (value === void 0 || value === null) {
317
+ return void 0;
318
+ }
319
+ const serialized = String(value);
320
+ return serialized.trim().length > 0 ? serialized : void 0;
321
+ }
322
+ function buildLayerFilters(filters) {
323
+ const query = new URLSearchParams();
324
+ if (!filters) {
325
+ return query;
326
+ }
327
+ Object.entries(filters).forEach(([key, value]) => {
328
+ const serialized = serializeFilterValue(value);
329
+ if (serialized !== void 0) {
330
+ query.set(key, serialized);
331
+ }
332
+ });
333
+ return query;
334
+ }
335
+ function buildFilterMultipleParams(params) {
336
+ const query = buildLayerFilters(params.filters);
337
+ if (!Array.isArray(params.layerIds) || params.layerIds.length === 0) {
338
+ throw new Error("layerIds is required and cannot be empty");
339
+ }
340
+ query.set("layerIds", params.layerIds.map((id) => String(id)).join(","));
341
+ return query;
342
+ }
343
+ function asOptionalNumber(value) {
344
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
345
+ }
346
+ function createEmptyFeatureCollection() {
347
+ return { type: "FeatureCollection", features: [] };
348
+ }
349
+ function hasFilters(filters) {
350
+ return buildLayerFilters(filters).size > 0;
351
+ }
352
+ function mergeLayerFilters(base, extra) {
353
+ if (!base && !extra) return void 0;
354
+ const merged = { ...base ?? {}, ...extra ?? {} };
355
+ return hasFilters(merged) ? merged : void 0;
356
+ }
357
+ var DEFAULT_MAX_FEATURES_FULL_GEOJSON = 5e4;
358
+ function normalizeLayerType(layerType) {
359
+ return typeof layerType === "string" ? layerType.toLowerCase() : void 0;
360
+ }
361
+ function shouldUseFilterMultiple(layerType) {
362
+ return normalizeLayerType(layerType) === "multipolygon";
363
+ }
364
+ function shouldSkipGeojsonDownload(layerType, totalFeatures, thresholds = {
365
+ maxFeatures: DEFAULT_MAX_FEATURES_FULL_GEOJSON
366
+ }) {
367
+ if (thresholds.allowLargeGeojson) {
368
+ return false;
369
+ }
370
+ const normalizedType = normalizeLayerType(layerType);
371
+ if (normalizedType === "mixed") {
372
+ return true;
373
+ }
374
+ if (typeof totalFeatures === "number" && totalFeatures > thresholds.maxFeatures) {
375
+ return true;
376
+ }
377
+ return false;
378
+ }
379
+ function parseBbox(value) {
380
+ if (!value || typeof value !== "object") {
381
+ return void 0;
382
+ }
383
+ const candidate = value;
384
+ if (typeof candidate.minLon === "number" && typeof candidate.minLat === "number" && typeof candidate.maxLon === "number" && typeof candidate.maxLat === "number") {
385
+ return {
386
+ minLon: candidate.minLon,
387
+ minLat: candidate.minLat,
388
+ maxLon: candidate.maxLon,
389
+ maxLat: candidate.maxLat
390
+ };
391
+ }
392
+ if (Array.isArray(value) && value.length === 4) {
393
+ const [minLon, minLat, maxLon, maxLat] = value;
394
+ if (typeof minLon === "number" && typeof minLat === "number" && typeof maxLon === "number" && typeof maxLat === "number") {
395
+ return { minLon, minLat, maxLon, maxLat };
396
+ }
397
+ }
398
+ return void 0;
399
+ }
400
+ function buildBboxFromFeatureCollection(featureCollection) {
401
+ const coords = [];
402
+ const collect = (candidate) => {
403
+ if (!Array.isArray(candidate)) return;
404
+ if (candidate.length === 2 && typeof candidate[0] === "number" && typeof candidate[1] === "number" && Number.isFinite(candidate[0]) && Number.isFinite(candidate[1])) {
405
+ coords.push([candidate[0], candidate[1]]);
406
+ return;
407
+ }
408
+ candidate.forEach((item) => collect(item));
409
+ };
410
+ featureCollection.features.forEach((feature) => {
411
+ collect(feature.geometry?.coordinates);
412
+ });
413
+ if (coords.length === 0) return void 0;
414
+ const [firstLon, firstLat] = coords[0];
415
+ const bbox = { minLon: firstLon, minLat: firstLat, maxLon: firstLon, maxLat: firstLat };
416
+ coords.forEach(([lon, lat]) => {
417
+ bbox.minLon = Math.min(bbox.minLon, lon);
418
+ bbox.minLat = Math.min(bbox.minLat, lat);
419
+ bbox.maxLon = Math.max(bbox.maxLon, lon);
420
+ bbox.maxLat = Math.max(bbox.maxLat, lat);
421
+ });
422
+ return bbox;
423
+ }
424
+ function buildGeometryCollectionFromFeatureCollection(featureCollection) {
425
+ return {
426
+ type: "GeometryCollection",
427
+ geometries: featureCollection.features.map((feature) => feature.geometry).filter((geometry) => geometry !== void 0 && geometry !== null)
428
+ };
429
+ }
430
+ function buildAoiFromFeatureCollection(featureCollection, metadata) {
431
+ const metadataBbox = parseBbox(metadata?.bbox);
432
+ const derivedBbox = metadataBbox ?? buildBboxFromFeatureCollection(featureCollection);
433
+ const hasFeatures = featureCollection.features.length > 0;
434
+ const geometry = hasFeatures ? buildGeometryCollectionFromFeatureCollection(featureCollection) : void 0;
435
+ if (!derivedBbox && !geometry) {
436
+ return null;
437
+ }
438
+ return { bbox: derivedBbox, geometry };
439
+ }
440
+ function splitFeatureCollectionByLayerId(featureCollection, layerIds) {
441
+ const grouped = {};
442
+ layerIds.forEach((layerId) => {
443
+ grouped[String(layerId)] = createEmptyFeatureCollection();
444
+ });
445
+ featureCollection.features.forEach((feature) => {
446
+ const properties = feature.properties;
447
+ const layerId = properties?.layerId ?? properties?.layer_id ?? properties?.__zenit_layerId;
448
+ if (layerId === void 0 || layerId === null) {
449
+ return;
450
+ }
451
+ const key = String(layerId);
452
+ if (!grouped[key]) {
453
+ grouped[key] = createEmptyFeatureCollection();
454
+ }
455
+ grouped[key] = {
456
+ ...grouped[key],
457
+ features: [...grouped[key].features, feature]
458
+ };
459
+ });
460
+ return grouped;
461
+ }
462
+ function resolveLayerStrategy(params) {
463
+ const {
464
+ layerDetail,
465
+ bbox,
466
+ filterBBox,
467
+ filteredGeoJSON,
468
+ isVisible = true,
469
+ maxFeaturesFullGeojson,
470
+ allowLargeGeojson
471
+ } = params;
472
+ if (!isVisible) {
473
+ return { strategy: "geojson", effectiveBBox: bbox };
474
+ }
475
+ if (filteredGeoJSON && layerDetail.layerType !== "multipolygon") {
476
+ return {
477
+ strategy: "intersect",
478
+ intersectionGeometry: buildGeometryCollectionFromFeatureCollection(filteredGeoJSON)
479
+ };
480
+ }
481
+ const shouldSkipGeojson = shouldSkipGeojsonDownload(layerDetail.layerType, layerDetail.totalFeatures, {
482
+ maxFeatures: maxFeaturesFullGeojson ?? DEFAULT_MAX_FEATURES_FULL_GEOJSON,
483
+ allowLargeGeojson
484
+ });
485
+ if (shouldSkipGeojson && (filterBBox ?? bbox)) {
486
+ return { strategy: "bbox", effectiveBBox: filterBBox ?? bbox };
487
+ }
488
+ if ((layerDetail.totalFeatures ?? 0) > 5e3 && (filterBBox ?? bbox)) {
489
+ return { strategy: "bbox", effectiveBBox: filterBBox ?? bbox };
490
+ }
491
+ return { strategy: "geojson", effectiveBBox: bbox };
492
+ }
493
+ function buildLimitedMessage(metadata) {
494
+ if (!metadata) {
495
+ return void 0;
496
+ }
497
+ const explicit = metadata.limitedMessage;
498
+ if (typeof explicit === "string" && explicit.trim().length > 0) {
499
+ return explicit;
500
+ }
501
+ const limit = typeof metadata.limit === "number" ? metadata.limit : void 0;
502
+ const total = typeof metadata.totalFeatures === "number" ? metadata.totalFeatures : void 0;
503
+ if (limit !== void 0 && total !== void 0 && total > limit) {
504
+ return `Mostrando ${limit.toLocaleString()} de ${total.toLocaleString()} elementos por l\xEDmite de capa.`;
505
+ }
506
+ if (metadata.limited && limit !== void 0) {
507
+ return `Mostrando un m\xE1ximo de ${limit.toLocaleString()} elementos por l\xEDmite de capa.`;
508
+ }
509
+ return void 0;
510
+ }
511
+ var ZenitLayersClient = class {
512
+ constructor(httpClient) {
513
+ this.httpClient = httpClient;
514
+ }
515
+ async getLayer(layerId) {
516
+ const response = await this.httpClient.get(
517
+ `/layers/${layerId}`
518
+ );
519
+ return this.unwrapResponse(response);
520
+ }
521
+ async getLayerGeoJson(layerId, options = {}) {
522
+ const params = parseQueryParams(options);
523
+ const suffix = params.size > 0 ? `?${params.toString()}` : "";
524
+ const response = await this.httpClient.get(
525
+ `/layers/${layerId}/geojson${suffix}`
526
+ );
527
+ return this.unwrapResponse(response);
528
+ }
529
+ async getLayerFeaturesCatalog(layerId, options) {
530
+ if (options?.strict) {
531
+ const support = getCatalogSupport({
532
+ layerType: options.layerType,
533
+ geometryType: options.geometryType
534
+ });
535
+ if (!support.supported) {
536
+ throw new ZenitCatalogNotSupportedError(
537
+ support,
538
+ support.reason === "unknown-geometry" ? "Catalog support cannot be determined without a geometry type" : "Catalog is only supported for polygon or multipolygon geometries"
539
+ );
540
+ }
541
+ }
542
+ const response = await this.httpClient.get(
543
+ `/layers/${layerId}/features/catalog`
544
+ );
545
+ return this.unwrapResponse(response);
546
+ }
547
+ async getLayerFeatures(layerId, featureIds) {
548
+ if (!Array.isArray(featureIds) || featureIds.length === 0) {
549
+ throw new Error("featureIds is required and cannot be empty");
550
+ }
551
+ const query = featureIds.map(String).join(",");
552
+ const response = await this.httpClient.get(
553
+ `/layers/${layerId}/features?featureIds=${encodeURIComponent(query)}`
554
+ );
555
+ return this.unwrapResponse(response);
556
+ }
557
+ async filterLayerFeatures(layerId, filters) {
558
+ const query = buildLayerFilters(filters);
559
+ const suffix = query.toString();
560
+ const response = await this.httpClient.get(
561
+ `/layers/${layerId}/features/filter${suffix ? `?${suffix}` : ""}`
562
+ );
563
+ return this.unwrapResponse(response);
564
+ }
565
+ async filterMultipleLayersFeatures(params) {
566
+ const query = buildFilterMultipleParams(params);
567
+ const suffix = query.toString();
568
+ const response = await this.httpClient.get(`/layers/features/filter-multiple${suffix ? `?${suffix}` : ""}`);
569
+ return this.unwrapResponse(response);
570
+ }
571
+ async filterMultiple(params) {
572
+ return this.filterMultipleLayersFeatures(params);
573
+ }
574
+ /**
575
+ * Resilient filter-multiple helper:
576
+ * - Usa filter-multiple SOLO en capas multipolygon.
577
+ * - Para otras capas aplica filterLayerFeatures o getLayerGeoJson según haya filtros.
578
+ * - Nunca falla toda la operación por una capa inválida.
579
+ *
580
+ * Para mejor detección de layerType, pasa layerMetas con el layerType de cada capa.
581
+ */
582
+ async filterMultipleWithFallback(params) {
583
+ const layerIds = params.layerIds ?? [];
584
+ const perLayer = {};
585
+ const warnings = [];
586
+ const hasLayerMetas = (params.layerMetas ?? []).length > 0;
587
+ const maxFeaturesFullGeojson = params.maxFeaturesFullGeojson ?? DEFAULT_MAX_FEATURES_FULL_GEOJSON;
588
+ const allowLargeGeojson = params.allowLargeGeojson ?? false;
589
+ layerIds.forEach((layerId) => {
590
+ perLayer[String(layerId)] = createEmptyFeatureCollection();
591
+ });
592
+ if (layerIds.length === 0) {
593
+ return { perLayer };
594
+ }
595
+ const globalFilters = hasFilters(params.filters) ? params.filters : void 0;
596
+ const metaIndex = /* @__PURE__ */ new Map();
597
+ (params.layerMetas ?? []).forEach((meta) => {
598
+ metaIndex.set(String(meta.layerId), meta);
599
+ });
600
+ let multipolygonIds = [];
601
+ let otherIds = [];
602
+ const layerIdsWithData = /* @__PURE__ */ new Set();
603
+ let aoi = null;
604
+ const pushWarning = (message) => {
605
+ warnings.push(message);
606
+ console.warn(message);
607
+ };
608
+ const buildSkipWarning = (layerId, layerType, totalFeatures, reason) => {
609
+ const typeLabel = layerType ?? "unknown";
610
+ const totalLabel = typeof totalFeatures === "number" ? ` totalFeatures=${totalFeatures}` : "";
611
+ const reasonLabel = reason ? ` ${reason}` : "";
612
+ return `Layer ${layerId} skipped: layerType=${typeLabel}${totalLabel}${reasonLabel}`;
613
+ };
614
+ if (hasLayerMetas) {
615
+ layerIds.forEach((layerId) => {
616
+ const meta = metaIndex.get(String(layerId));
617
+ if (shouldUseFilterMultiple(meta?.layerType)) {
618
+ multipolygonIds.push(layerId);
619
+ } else {
620
+ otherIds.push(layerId);
621
+ }
622
+ });
623
+ } else {
624
+ multipolygonIds = [...layerIds];
625
+ }
626
+ let metadata;
627
+ if (multipolygonIds.length > 0) {
628
+ try {
629
+ const response = await this.filterMultipleLayersFeatures({
630
+ layerIds: multipolygonIds,
631
+ filters: globalFilters
632
+ });
633
+ metadata = response.metadata;
634
+ const geojson = response.data ?? createEmptyFeatureCollection();
635
+ if (globalFilters) {
636
+ aoi = buildAoiFromFeatureCollection(geojson, metadata);
637
+ }
638
+ const perLayerFromMultiple = splitFeatureCollectionByLayerId(geojson, layerIds);
639
+ Object.entries(perLayerFromMultiple).forEach(([layerKey, collection]) => {
640
+ if (collection.features.length === 0) {
641
+ return;
642
+ }
643
+ perLayer[layerKey] = decorateFeaturesWithLayerId(collection, layerKey);
644
+ layerIdsWithData.add(layerKey);
645
+ });
646
+ } catch {
647
+ if (hasLayerMetas) {
648
+ otherIds = [.../* @__PURE__ */ new Set([...otherIds, ...multipolygonIds])];
649
+ } else {
650
+ otherIds = [...layerIds];
651
+ }
652
+ multipolygonIds = [];
653
+ }
654
+ }
655
+ if (otherIds.length > 0) {
656
+ const pendingIds = otherIds.filter(
657
+ (layerId) => !layerIdsWithData.has(String(layerId))
658
+ );
659
+ const resolved = await Promise.all(
660
+ pendingIds.map(async (layerId) => {
661
+ const meta = metaIndex.get(String(layerId));
662
+ const layerType = meta?.layerType;
663
+ const totalFeatures = meta?.totalFeatures;
664
+ const layerFilters = mergeLayerFilters(globalFilters, meta?.prefilters);
665
+ const canUseIntersect = !!globalFilters && !shouldUseFilterMultiple(layerType) && !!aoi?.geometry;
666
+ if (canUseIntersect) {
667
+ const intersectionGeometry = aoi?.geometry;
668
+ if (!intersectionGeometry) {
669
+ pushWarning(
670
+ buildSkipWarning(
671
+ layerId,
672
+ layerType,
673
+ totalFeatures,
674
+ "no AOI geometry available"
675
+ )
676
+ );
677
+ return { layerId, geojson: createEmptyFeatureCollection() };
678
+ }
679
+ try {
680
+ const response = await this.getLayerGeoJsonIntersect({
681
+ id: layerId,
682
+ geometry: intersectionGeometry
683
+ });
684
+ if (aoi?.bbox) {
685
+ console.debug(
686
+ `Layer ${layerId} fetched by intersect using AOI bbox=${JSON.stringify(
687
+ aoi.bbox
688
+ )}`
689
+ );
690
+ } else {
691
+ console.debug(`Layer ${layerId} fetched by intersect using AOI geometry`);
692
+ }
693
+ const geojson = response.data ?? createEmptyFeatureCollection();
694
+ return {
695
+ layerId,
696
+ geojson: decorateFeaturesWithLayerId(geojson, layerId)
697
+ };
698
+ } catch (error) {
699
+ const message = error instanceof Error ? error.message : "Unknown error";
700
+ pushWarning(
701
+ buildSkipWarning(
702
+ layerId,
703
+ layerType,
704
+ totalFeatures,
705
+ `intersect failed: ${message}`
706
+ )
707
+ );
708
+ return { layerId, geojson: createEmptyFeatureCollection() };
709
+ }
710
+ }
711
+ if (layerFilters && (hasLayerMetas ? shouldUseFilterMultiple(layerType) : true)) {
712
+ try {
713
+ const response = await this.filterLayerFeatures(layerId, layerFilters);
714
+ const geojson = response.data ?? createEmptyFeatureCollection();
715
+ return {
716
+ layerId,
717
+ geojson: decorateFeaturesWithLayerId(geojson, layerId)
718
+ };
719
+ } catch (error) {
720
+ const message = error instanceof Error ? error.message : "Unknown error";
721
+ pushWarning(
722
+ `Layer ${layerId} filter failed: ${message}`
723
+ );
724
+ return { layerId, geojson: createEmptyFeatureCollection() };
725
+ }
726
+ }
727
+ if (shouldSkipGeojsonDownload(layerType, totalFeatures, {
728
+ maxFeatures: maxFeaturesFullGeojson,
729
+ allowLargeGeojson
730
+ })) {
731
+ pushWarning(
732
+ buildSkipWarning(
733
+ layerId,
734
+ layerType,
735
+ totalFeatures,
736
+ "not supported by filter endpoint and too large for /geojson; use intersect with AOI"
737
+ )
738
+ );
739
+ return { layerId, geojson: createEmptyFeatureCollection() };
740
+ }
741
+ try {
742
+ const response = await this.getLayerGeoJson(layerId);
743
+ const geojson = response.data ?? createEmptyFeatureCollection();
744
+ return {
745
+ layerId,
746
+ geojson: decorateFeaturesWithLayerId(geojson, layerId)
747
+ };
748
+ } catch (error) {
749
+ const message = error instanceof Error ? error.message : "Unknown error";
750
+ pushWarning(
751
+ buildSkipWarning(layerId, layerType, totalFeatures, `error=${message}`)
752
+ );
753
+ return { layerId, geojson: createEmptyFeatureCollection() };
754
+ }
755
+ })
756
+ );
757
+ resolved.forEach(({ layerId, geojson }) => {
758
+ perLayer[String(layerId)] = geojson;
759
+ });
760
+ }
761
+ const mergedMetadata = warnings.length > 0 ? {
762
+ ...metadata ?? {},
763
+ warnings: [...metadata?.warnings ?? [], ...warnings]
764
+ } : metadata;
765
+ return { perLayer, metadata: mergedMetadata, aoi };
766
+ }
767
+ async getLayerGeoJsonBBox(request) {
768
+ const params = new URLSearchParams({
769
+ minLon: String(request.bbox.minLon),
770
+ minLat: String(request.bbox.minLat),
771
+ maxLon: String(request.bbox.maxLon),
772
+ maxLat: String(request.bbox.maxLat)
773
+ });
774
+ parseQueryParams(request).forEach((value, key) => params.set(key, value));
775
+ const response = await this.httpClient.get(
776
+ `/layers/${request.id}/geojson/bbox?${params.toString()}`
777
+ );
778
+ return this.unwrapResponse(response);
779
+ }
780
+ async getLayerGeoJsonIntersect(request) {
781
+ const params = parseQueryParams(request);
782
+ const suffix = params.size > 0 ? `?${params.toString()}` : "";
783
+ const response = await this.httpClient.post(
784
+ `/layers/${request.id}/geojson/intersects${suffix}`,
785
+ { geometry: request.geometry }
786
+ );
787
+ return this.unwrapResponse(response);
788
+ }
789
+ async getLayerTileTemplate(layerId) {
790
+ const response = await this.httpClient.get(
791
+ `/layers/${layerId}/tiles/template`
792
+ );
793
+ const unwrapped = this.unwrapResponse(response);
794
+ if (typeof unwrapped.data === "string") {
795
+ return unwrapped.data;
796
+ }
797
+ if (unwrapped.data && typeof unwrapped.data.template === "string") {
798
+ return unwrapped.data.template;
799
+ }
800
+ throw new Error("Invalid tile template response");
801
+ }
802
+ async loadLayerData(params) {
803
+ const { id, layerDetail, bbox, filterBBox, filteredGeoJSON, isVisible = true, ...options } = params;
804
+ const resolution = resolveLayerStrategy({
805
+ layerDetail,
806
+ bbox,
807
+ filterBBox,
808
+ filteredGeoJSON,
809
+ isVisible,
810
+ maxFeaturesFullGeojson: params.maxFeaturesFullGeojson,
811
+ allowLargeGeojson: params.allowLargeGeojson
812
+ });
813
+ const response = await this.fetchByStrategy(id, resolution, options);
814
+ const totalFeatures = asOptionalNumber(response.totalFeatures) ?? asOptionalNumber(response.metadata?.totalFeatures) ?? asOptionalNumber(layerDetail.totalFeatures);
815
+ const limitedMessage = response.limitedMessage ?? buildLimitedMessage(response.metadata);
816
+ return {
817
+ geojson: response.data,
818
+ strategy: resolution.strategy,
819
+ limitedMessage,
820
+ totalFeatures,
821
+ metadata: response.metadata
822
+ };
823
+ }
824
+ async loadFilteredFeatures(params) {
825
+ const response = await this.filterMultipleLayersFeatures(params);
826
+ return {
827
+ geojson: response.data,
828
+ metadata: response.metadata,
829
+ totalFeatures: response.metadata?.totalFeatures ?? response.totalFeatures
830
+ };
831
+ }
832
+ async fetchByStrategy(layerId, resolution, options) {
833
+ switch (resolution.strategy) {
834
+ case "bbox": {
835
+ if (!resolution.effectiveBBox) {
836
+ throw new Error("Bounding box is required for bbox strategy");
837
+ }
838
+ return this.getLayerGeoJsonBBox({ id: layerId, bbox: resolution.effectiveBBox, ...options });
839
+ }
840
+ case "intersect": {
841
+ if (!resolution.intersectionGeometry) {
842
+ throw new Error("Geometry is required for intersect strategy");
843
+ }
844
+ return this.getLayerGeoJsonIntersect({
845
+ id: layerId,
846
+ geometry: resolution.intersectionGeometry,
847
+ ...options
848
+ });
849
+ }
850
+ case "geojson":
851
+ default:
852
+ return this.getLayerGeoJson(layerId, options);
853
+ }
854
+ }
855
+ unwrapResponse(response) {
856
+ if (!response || typeof response !== "object" || !("data" in response)) {
857
+ throw new Error("Invalid API response: missing data field");
858
+ }
859
+ if ("success" in response && response.success === false) {
860
+ throw new Error(response.message ?? "Request was not successful");
861
+ }
862
+ return response;
863
+ }
864
+ };
865
+
866
+ // src/config/ZenitSdkConfig.ts
867
+ var ZenitClient = class {
868
+ constructor(config) {
869
+ this.config = config;
870
+ this.accessToken = config.accessToken;
871
+ this.refreshToken = config.refreshToken;
872
+ this.sdkToken = config.sdkToken;
873
+ this.httpClient = new HttpClient({
874
+ baseUrl: config.baseUrl,
875
+ resolveTokens: () => ({ accessToken: this.accessToken, sdkToken: this.sdkToken }),
876
+ resolveAuthorizationHeader: this.getAuthorizationHeader.bind(this)
877
+ });
878
+ this.auth = new ZenitAuthClient(this.httpClient, this.updateTokens.bind(this), config);
879
+ this.sdkAuth = new ZenitSdkAuthClient(
880
+ this.httpClient,
881
+ this.updateAccessTokenFromSdkExchange.bind(this),
882
+ config
883
+ );
884
+ this.maps = new ZenitMapsClient(this.httpClient);
885
+ this.layers = new ZenitLayersClient(this.httpClient);
886
+ }
887
+ /**
888
+ * Update tokens in memory and propagate to config callbacks.
889
+ */
890
+ updateTokens(tokens) {
891
+ if (tokens.accessToken) {
892
+ this.setAccessToken(tokens.accessToken);
893
+ }
894
+ if (tokens.refreshToken) {
895
+ this.setRefreshToken(tokens.refreshToken);
896
+ }
897
+ this.config.onTokenRefreshed?.({
898
+ accessToken: this.accessToken || "",
899
+ refreshToken: this.refreshToken
900
+ });
901
+ }
902
+ // Se usa cuando /sdk-auth/exchange devuelve un accessToken; pasa a ser el accessToken principal del SDK.
903
+ updateAccessTokenFromSdkExchange(token) {
904
+ if (token) {
905
+ this.setAccessToken(token);
906
+ }
907
+ }
908
+ setAccessToken(token) {
909
+ this.accessToken = token;
910
+ this.config.accessToken = token;
911
+ }
912
+ setRefreshToken(token) {
913
+ this.refreshToken = token;
914
+ this.config.refreshToken = token;
915
+ }
916
+ getAuthorizationHeader() {
917
+ const header = {};
918
+ if (this.accessToken) {
919
+ header.Authorization = `Bearer ${this.accessToken}`;
920
+ }
921
+ return header;
922
+ }
923
+ getHttpClient() {
924
+ return this.httpClient;
925
+ }
926
+ };
927
+
928
+ // src/engine/FilterEngine.ts
929
+ var FilterEngine = class {
930
+ decide(params) {
931
+ if (params.request) {
932
+ return { nextRequest: params.request };
933
+ }
934
+ if (params.filters && params.layerIds && params.layerIds.length > 1) {
935
+ return {
936
+ nextRequest: {
937
+ type: "filter-multiple",
938
+ layerIds: params.layerIds,
939
+ filters: params.filters
940
+ },
941
+ reason: "filters+layerIds"
942
+ };
943
+ }
944
+ if (params.filters && params.layerId !== void 0) {
945
+ return {
946
+ nextRequest: {
947
+ type: "filter-layer",
948
+ layerId: params.layerId,
949
+ filters: params.filters
950
+ },
951
+ reason: "filters+layerId"
952
+ };
953
+ }
954
+ if (params.bbox && params.layerId !== void 0) {
955
+ return {
956
+ nextRequest: {
957
+ type: "bbox",
958
+ layerId: params.layerId,
959
+ bbox: params.bbox,
960
+ options: params.options
961
+ },
962
+ reason: "bbox+layerId"
963
+ };
964
+ }
965
+ if (params.geometry && params.layerId !== void 0) {
966
+ return {
967
+ nextRequest: {
968
+ type: "intersect",
969
+ layerId: params.layerId,
970
+ geometry: params.geometry,
971
+ options: params.options
972
+ },
973
+ reason: "geometry+layerId"
974
+ };
975
+ }
976
+ if (params.layerId !== void 0) {
977
+ return {
978
+ nextRequest: {
979
+ type: "geojson",
980
+ layerId: params.layerId,
981
+ options: params.options
982
+ },
983
+ reason: "layerId"
984
+ };
985
+ }
986
+ throw new Error("FilterEngine could not resolve a next request");
987
+ }
988
+ };
989
+ var filterEngine = new FilterEngine();
990
+
991
+ // src/ai/normalize.ts
992
+ function normalizeBbox(input) {
993
+ if (!input) return null;
994
+ if (Array.isArray(input)) {
995
+ if (input.length !== 4) {
996
+ console.warn("[normalizeBbox] Array must have exactly 4 elements, got:", input.length);
997
+ return null;
998
+ }
999
+ const [minLon, minLat, maxLon, maxLat] = input;
1000
+ if (typeof minLon !== "number" || typeof minLat !== "number" || typeof maxLon !== "number" || typeof maxLat !== "number" || !Number.isFinite(minLon) || !Number.isFinite(minLat) || !Number.isFinite(maxLon) || !Number.isFinite(maxLat)) {
1001
+ console.warn("[normalizeBbox] Array contains non-finite numbers:", input);
1002
+ return null;
1003
+ }
1004
+ if (minLon >= maxLon || minLat >= maxLat) {
1005
+ console.warn("[normalizeBbox] Invalid bbox: min must be < max", {
1006
+ minLon,
1007
+ maxLon,
1008
+ minLat,
1009
+ maxLat
1010
+ });
1011
+ return null;
1012
+ }
1013
+ return { minLon, minLat, maxLon, maxLat };
1014
+ }
1015
+ if (typeof input === "object") {
1016
+ const candidate = input;
1017
+ const { minLon, minLat, maxLon, maxLat } = candidate;
1018
+ if (typeof minLon !== "number" || typeof minLat !== "number" || typeof maxLon !== "number" || typeof maxLat !== "number" || !Number.isFinite(minLon) || !Number.isFinite(minLat) || !Number.isFinite(maxLon) || !Number.isFinite(maxLat)) {
1019
+ console.warn("[normalizeBbox] Object missing valid bbox keys or contains non-finite numbers:", input);
1020
+ return null;
1021
+ }
1022
+ if (minLon >= maxLon || minLat >= maxLat) {
1023
+ console.warn("[normalizeBbox] Invalid bbox: min must be < max", {
1024
+ minLon,
1025
+ maxLon,
1026
+ minLat,
1027
+ maxLat
1028
+ });
1029
+ return null;
1030
+ }
1031
+ return { minLon, minLat, maxLon, maxLat };
1032
+ }
1033
+ console.warn("[normalizeBbox] Unsupported bbox format:", typeof input);
1034
+ return null;
1035
+ }
1036
+ export {
1037
+ ChevronLeft,
1038
+ ChevronRight,
1039
+ DEFAULT_MAX_FEATURES_FULL_GEOJSON,
1040
+ Eye,
1041
+ EyeOff,
1042
+ FilterEngine,
1043
+ FloatingChatBox,
1044
+ HttpClient,
1045
+ Layers,
1046
+ Upload,
1047
+ X,
1048
+ ZenitAuthClient,
1049
+ ZenitCatalogNotSupportedError,
1050
+ ZenitClient,
1051
+ ZenitFeatureFilterPanel,
1052
+ ZenitLayerManager,
1053
+ ZenitLayersClient,
1054
+ ZenitMap,
1055
+ ZenitMapsClient,
1056
+ ZenitSdkAuthClient,
1057
+ ZoomIn,
1058
+ applyFilteredGeoJSONStrategy,
1059
+ applyLayerOverrides,
1060
+ applyPrefiltersToFeatureCollection,
1061
+ buildAoiFromFeatureCollection,
1062
+ buildFilterMultipleParams,
1063
+ buildLayerFilters,
1064
+ buildLimitedMessage,
1065
+ centroidFromBBox,
1066
+ clampNumber,
1067
+ clampOpacity,
1068
+ computeBBoxFromFeature,
1069
+ createChatService,
1070
+ decorateFeaturesWithLayerId,
1071
+ extractMapDto,
1072
+ filterEngine,
1073
+ getAccentByLayerId,
1074
+ getCatalogSupport,
1075
+ getEffectiveLayerOpacity,
1076
+ getLayerColor,
1077
+ getLayerZoomOpacityFactor,
1078
+ getStyleByLayerId,
1079
+ getZoomOpacityFactor,
1080
+ initLayerStates,
1081
+ isPolygonLayer,
1082
+ normalizeBbox,
1083
+ normalizeMapCenter,
1084
+ normalizeMapLayers,
1085
+ resetOverrides,
1086
+ resolveLayerAccent,
1087
+ resolveLayerStrategy,
1088
+ sendMessage,
1089
+ sendMessageStream,
1090
+ shouldSkipGeojsonDownload,
1091
+ shouldUseFilterMultiple,
1092
+ useSendMessage,
1093
+ useSendMessageStream
1094
+ };
1095
+ //# sourceMappingURL=index.mjs.map