@india-boundary-corrector/service-worker 0.0.5 → 0.1.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.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var IndiaBoundaryCorrector = (() => {
3
3
  // ../data/version.js
4
- var packageVersion = "0.0.5";
4
+ var packageVersion = "0.1.0";
5
5
 
6
6
  // ../data/index.js
7
7
  var import_meta = {};
@@ -22,14 +22,17 @@ var IndiaBoundaryCorrector = (() => {
22
22
  scriptUrl = CURRENT_SCRIPT_URL;
23
23
  }
24
24
  if (scriptUrl) {
25
- const moduleUrl = new URL(".", scriptUrl);
26
- if (FALLBACK_CDNS.has(moduleUrl.hostname)) {
27
- return DEFAULT_CDN_URL;
28
- }
29
- return new URL(PMTILES_FILENAME, moduleUrl).href;
25
+ return resolvePmtilesUrl(scriptUrl);
30
26
  }
31
27
  return DEFAULT_CDN_URL;
32
28
  }
29
+ function resolvePmtilesUrl(scriptUrl) {
30
+ const moduleUrl = new URL(".", scriptUrl);
31
+ if (FALLBACK_CDNS.has(moduleUrl.hostname)) {
32
+ return DEFAULT_CDN_URL;
33
+ }
34
+ return new URL(PMTILES_FILENAME, moduleUrl).href;
35
+ }
33
36
  var cachedPmtilesUrl = null;
34
37
  function getPmtilesUrl() {
35
38
  if (cachedPmtilesUrl === null) {
@@ -42,7 +45,6 @@ var IndiaBoundaryCorrector = (() => {
42
45
  var configs_default = [
43
46
  {
44
47
  id: "cartodb-dark",
45
- zoomThreshold: 5,
46
48
  tileUrlTemplates: [
47
49
  "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
48
50
  "https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
@@ -51,15 +53,17 @@ var IndiaBoundaryCorrector = (() => {
51
53
  "https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
52
54
  "https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png"
53
55
  ],
54
- lineWidthStops: { "1": 0.5, "10": 2.5 },
56
+ lineWidthStops: { "0": 1, "2": 1, "3": 1, "10": 2.5 },
55
57
  lineStyles: [
56
- { color: "rgb(40, 40, 40)" }
58
+ { color: "rgb(40, 40, 40)", layerSuffix: "ne", endZoom: 4 },
59
+ { color: "rgb(40, 40, 40)", layerSuffix: "ne-disp", endZoom: 4 },
60
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm", startZoom: 5 },
61
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm-disp", startZoom: 5 },
62
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.3, dashArray: [2, 2] }
57
63
  ]
58
64
  },
59
65
  {
60
66
  id: "cartodb-light",
61
- startZoom: 0,
62
- zoomThreshold: 5,
63
67
  tileUrlTemplates: [
64
68
  "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
65
69
  "https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
@@ -74,56 +78,62 @@ var IndiaBoundaryCorrector = (() => {
74
78
  "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
75
79
  "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png"
76
80
  ],
77
- lineWidthStops: { "1": 0.25, "2": 0.25, "3": 0.5, "4": 0.75, "5": 1 },
81
+ lineWidthStops: { "1": 0.5, "2": 0.5, "3": 0.5, "4": 1, "5": 1.25, "7": 1.5, "16": 2.5 },
78
82
  lineStyles: [
79
- { color: "rgb(235, 214, 214)", alpha: 0.2, startZoom: 6, widthFraction: 5 },
80
- { color: "rgb(235, 214, 214)" }
83
+ { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2 },
84
+ { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, lineExtensionFactor: 0 },
85
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 6, widthFraction: 3, lineExtensionFactor: 0.1 },
86
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
87
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", alpha: 0.2, startZoom: 6, widthFraction: 3, lineExtensionFactor: 0 },
88
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, lineExtensionFactor: 0 },
89
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.75, dashArray: [2, 2], delWidthFactor: 2 }
81
90
  ]
82
91
  },
83
92
  {
84
93
  id: "open-topo",
85
- startZoom: 4,
86
- zoomThreshold: 4,
87
94
  tileUrlTemplates: [
88
95
  "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
89
96
  "https://tile.opentopomap.org/{z}/{x}/{y}.png"
90
97
  ],
91
98
  lineWidthStops: { "4": 0.75, "5": 1, "6": 1.25, "7": 1.5, "8": 1.75, "9": 1.25, "10": 1.25, "13": 1.5 },
92
99
  lineStyles: [
93
- { color: "rgb(83, 83, 83)", startZoom: 7, endZoom: 8, alpha: 0.4, widthFraction: 4 },
94
- { color: "rgb(83, 83, 83)", endZoom: 8 },
95
- { color: "rgb(140, 20, 180)", startZoom: 9, widthFraction: 9, alpha: 0.2 },
96
- { color: "rgb(140, 20, 180)", startZoom: 9 }
100
+ { color: "rgb(83, 83, 83)", layerSuffix: "ne", startZoom: 4, endZoom: 6 },
101
+ { color: "rgb(173, 173, 173)", layerSuffix: "osm", startZoom: 7, endZoom: 8, alpha: 0.5, widthFraction: 4 },
102
+ { color: "rgb(83, 83, 83)", layerSuffix: "osm", startZoom: 7, endZoom: 8 },
103
+ { color: "rgb(199, 158, 204)", layerSuffix: "osm", startZoom: 9, widthFraction: 7, alpha: 0.6, lineExtensionFactor: 0.2 },
104
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 }
97
105
  ]
98
106
  },
99
107
  {
100
108
  id: "osm-carto",
101
- startZoom: 1,
102
- zoomThreshold: 1,
103
109
  tileUrlTemplates: [
104
110
  "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
105
111
  "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
106
112
  ],
107
113
  lineWidthStops: { "1": 0.5, "2": 0.6, "3": 0.7, "4": 1, "10": 3.75 },
108
114
  lineStyles: [
109
- { color: "rgb(200, 180, 200)" },
110
- { color: "rgb(160, 120, 160)", widthFraction: 0.333, dashArray: [30, 2, 8, 2] }
115
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 1, endZoom: 3, delWidthFactor: 2.5, widthFraction: 1.5 },
116
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 4 },
117
+ { color: "rgb(160, 120, 160)", layerSuffix: "osm", startZoom: 4, widthFraction: 0.333, dashArray: [30, 2, 8, 2] },
118
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.45 },
119
+ { color: "rgb(160, 120, 160)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.15, dashArray: [4, 2, 10] }
111
120
  ]
112
121
  },
113
122
  {
114
123
  id: "osm-hot",
115
- startZoom: 2,
116
- zoomThreshold: 2,
117
124
  tileUrlTemplates: [
118
- "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
125
+ "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
126
+ "https://tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
119
127
  ],
120
128
  lineWidthStops: { "2": 3, "3": 3, "7": 3, "8": 3.5, "9": 3.5 },
121
129
  lineStyles: [
122
- { color: "rgb(149, 175, 180)" },
123
- { color: "rgb(89, 117, 123)", widthFraction: 0.33 }
130
+ { color: "rgb(149, 175, 180)", layerSuffix: "osm", lineExtensionFactor: 0.25, startZoom: 2 },
131
+ { color: "rgb(89, 117, 123)", layerSuffix: "osm", widthFraction: 0.33, lineExtensionFactor: 0.25, startZoom: 2 },
132
+ { color: "rgb(172, 163, 163)", layerSuffix: "osm-internal", widthFraction: 0.33, startZoom: 4, dashArray: [7, 7, 7] }
124
133
  ]
125
134
  }
126
135
  ];
136
+ var INFINITY = -1;
127
137
  function templateToRegex(template) {
128
138
  const groups = [];
129
139
  let pattern = template.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
@@ -190,6 +200,9 @@ var IndiaBoundaryCorrector = (() => {
190
200
  if (!isValidColor(obj.color)) {
191
201
  throw new Error(`${prefix}: color "${obj.color}" is not a valid CSS color`);
192
202
  }
203
+ if (!obj.layerSuffix || typeof obj.layerSuffix !== "string") {
204
+ throw new Error(`${prefix}: layerSuffix must be a non-empty string`);
205
+ }
193
206
  if (obj.widthFraction !== void 0 && (typeof obj.widthFraction !== "number" || obj.widthFraction <= 0)) {
194
207
  throw new Error(`${prefix}: widthFraction must be a positive number`);
195
208
  }
@@ -202,26 +215,38 @@ var IndiaBoundaryCorrector = (() => {
202
215
  if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
203
216
  throw new Error(`${prefix}: startZoom must be a non-negative number`);
204
217
  }
205
- if (obj.endZoom !== void 0 && (typeof obj.endZoom !== "number" || obj.endZoom < 0)) {
206
- throw new Error(`${prefix}: endZoom must be a non-negative number`);
218
+ if (obj.endZoom !== void 0 && (typeof obj.endZoom !== "number" || obj.endZoom < 0 && obj.endZoom !== INFINITY)) {
219
+ throw new Error(`${prefix}: endZoom must be a non-negative number or INFINITY (${INFINITY})`);
220
+ }
221
+ if (obj.lineExtensionFactor !== void 0 && (typeof obj.lineExtensionFactor !== "number" || obj.lineExtensionFactor < 0)) {
222
+ throw new Error(`${prefix}: lineExtensionFactor must be a non-negative number`);
223
+ }
224
+ if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor < 0)) {
225
+ throw new Error(`${prefix}: delWidthFactor must be a non-negative number`);
207
226
  }
208
227
  }
209
228
  /**
210
229
  * @param {Object} options
211
230
  * @param {string} options.color - CSS color string
231
+ * @param {string} options.layerSuffix - Layer suffix (e.g., 'osm', 'ne', 'osm-disp')
212
232
  * @param {number} [options.widthFraction=1.0] - Multiplier for base line width
213
233
  * @param {number[]} [options.dashArray] - Dash pattern for dashed lines
214
234
  * @param {number} [options.alpha=1.0] - Opacity (0-1)
215
- * @param {number} [options.startZoom] - Minimum zoom level for this style
216
- * @param {number} [options.endZoom=Infinity] - Maximum zoom level for this style
235
+ * @param {number} [options.startZoom=0] - Minimum zoom level for this style
236
+ * @param {number} [options.endZoom=INFINITY] - Maximum zoom level for this style (INFINITY means no limit)
237
+ * @param {number} [options.lineExtensionFactor=0.5] - Factor to extend lines by (multiplied by deletion line width)
238
+ * @param {number} [options.delWidthFactor=1.5] - Factor to multiply line width for deletion blur
217
239
  */
218
- constructor({ color, widthFraction = 1, dashArray, alpha = 1, startZoom, endZoom = Infinity }) {
240
+ constructor({ color, layerSuffix, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY, lineExtensionFactor = 0.5, delWidthFactor = 1.5 }) {
219
241
  this.color = color;
242
+ this.layerSuffix = layerSuffix;
220
243
  this.widthFraction = widthFraction;
221
244
  this.dashArray = dashArray;
222
245
  this.alpha = alpha;
223
246
  this.startZoom = startZoom;
224
247
  this.endZoom = endZoom;
248
+ this.lineExtensionFactor = lineExtensionFactor;
249
+ this.delWidthFactor = delWidthFactor;
225
250
  }
226
251
  /**
227
252
  * Check if this style is active at the given zoom level.
@@ -229,34 +254,34 @@ var IndiaBoundaryCorrector = (() => {
229
254
  * @returns {boolean}
230
255
  */
231
256
  isActiveAtZoom(z2) {
232
- return z2 >= this.startZoom && z2 <= this.endZoom;
257
+ return z2 >= this.startZoom && (this.endZoom === INFINITY || z2 <= this.endZoom);
233
258
  }
234
259
  /**
235
260
  * Serialize to plain object.
236
261
  * @returns {Object}
237
262
  */
238
263
  toJSON() {
239
- const obj = { color: this.color };
240
- if (this.widthFraction !== 1) obj.widthFraction = this.widthFraction;
241
- if (this.dashArray) obj.dashArray = this.dashArray;
242
- if (this.alpha !== 1) obj.alpha = this.alpha;
243
- if (this.startZoom !== void 0) obj.startZoom = this.startZoom;
244
- if (this.endZoom !== Infinity) obj.endZoom = this.endZoom;
245
- return obj;
264
+ return {
265
+ color: this.color,
266
+ layerSuffix: this.layerSuffix,
267
+ widthFraction: this.widthFraction,
268
+ dashArray: this.dashArray,
269
+ alpha: this.alpha,
270
+ startZoom: this.startZoom,
271
+ endZoom: this.endZoom,
272
+ lineExtensionFactor: this.lineExtensionFactor,
273
+ delWidthFactor: this.delWidthFactor
274
+ };
246
275
  }
247
276
  /**
248
277
  * Create from plain object with validation.
249
278
  * @param {Object} obj
250
- * @param {number} [defaultStartZoom=0] - Default startZoom if not specified
251
279
  * @param {number} [index] - Optional index for error messages
252
280
  * @returns {LineStyle}
253
281
  */
254
- static fromJSON(obj, defaultStartZoom = 0, index) {
282
+ static fromJSON(obj, index) {
255
283
  _LineStyle.validateJSON(obj, index);
256
- return new _LineStyle({
257
- ...obj,
258
- startZoom: obj.startZoom ?? defaultStartZoom
259
- });
284
+ return new _LineStyle(obj);
260
285
  }
261
286
  };
262
287
  var LayerConfig = class _LayerConfig {
@@ -277,17 +302,6 @@ var IndiaBoundaryCorrector = (() => {
277
302
  throw new Error(`LayerConfig: id cannot contain slashes: "${obj.id}"`);
278
303
  }
279
304
  const id = obj.id;
280
- if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
281
- throw new Error(`LayerConfig "${id}": startZoom must be a non-negative number`);
282
- }
283
- if (obj.zoomThreshold !== void 0 && (typeof obj.zoomThreshold !== "number" || obj.zoomThreshold < 0)) {
284
- throw new Error(`LayerConfig "${id}": zoomThreshold must be a non-negative number`);
285
- }
286
- const startZoom = obj.startZoom ?? 0;
287
- const zoomThreshold = obj.zoomThreshold ?? 5;
288
- if (startZoom > zoomThreshold) {
289
- throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
290
- }
291
305
  if (obj.lineWidthStops !== void 0) {
292
306
  if (!obj.lineWidthStops || typeof obj.lineWidthStops !== "object" || Array.isArray(obj.lineWidthStops)) {
293
307
  throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
@@ -306,59 +320,35 @@ var IndiaBoundaryCorrector = (() => {
306
320
  }
307
321
  }
308
322
  }
309
- if (obj.lineStyles !== void 0) {
310
- if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
311
- throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
312
- }
313
- for (let i2 = 0; i2 < obj.lineStyles.length; i2++) {
314
- LineStyle.validateJSON(obj.lineStyles[i2], i2);
315
- }
316
- }
317
- if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor <= 0)) {
318
- throw new Error(`LayerConfig "${id}": delWidthFactor must be a positive number`);
323
+ if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
324
+ throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
319
325
  }
320
- if (obj.lineExtensionFactor !== void 0 && (typeof obj.lineExtensionFactor !== "number" || obj.lineExtensionFactor < 0)) {
321
- throw new Error(`LayerConfig "${id}": lineExtensionFactor must be a non-negative number`);
326
+ for (let i2 = 0; i2 < obj.lineStyles.length; i2++) {
327
+ LineStyle.validateJSON(obj.lineStyles[i2], i2);
322
328
  }
323
329
  }
324
330
  constructor({
325
331
  id,
326
- startZoom = 0,
327
- zoomThreshold = 5,
328
332
  // Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
329
333
  tileUrlTemplates = [],
330
334
  // Line width stops: map of zoom level to line width (at least 2 entries)
331
335
  // Note: interpolated/extrapolated line width is capped at a minimum of 0.5
332
336
  lineWidthStops = { 1: 0.5, 10: 2.5 },
333
- // Line styles array - each element describes a line to draw
334
- // { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }
335
- // Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity
336
- lineStyles = [{ color: "green", widthFraction: 1 }],
337
- // Factor to multiply line width for deletion blur (default 1.5)
338
- // Higher values leave gaps where wiped lines meet existing lines
339
- // Lower values mean wiped lines show through
340
- delWidthFactor = 1.5,
341
- // Factor to extend add lines by (multiplied by deletion line width)
342
- // Helps cover gaps where deleted lines meet the new boundary
343
- // Set to 0 to disable extension
344
- lineExtensionFactor = 0.5
337
+ // Line styles array - each element describes a line to draw from a specific layer
338
+ // { color: string, layerSuffix: string, widthFraction?: number, dashArray?: number[],
339
+ // startZoom?: number, endZoom?: number, lineExtensionFactor?: number, delWidthFactor?: number }
340
+ // Lines are drawn in array order. layerSuffix determines which PMTiles layer to use.
341
+ lineStyles
345
342
  }) {
346
343
  this.id = id;
347
- this.startZoom = startZoom;
348
- this.zoomThreshold = zoomThreshold;
349
344
  const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : tileUrlTemplates ? [tileUrlTemplates] : [];
350
345
  this.tileUrlTemplates = templates;
351
346
  this._compiledPatterns = templates.map((t) => templateToRegex(t));
352
347
  this._templatePatterns = templates.map((t) => templateToTemplateRegex(t));
353
348
  this.lineWidthStops = lineWidthStops;
354
349
  this.lineStyles = lineStyles.map(
355
- (style) => style instanceof LineStyle ? style : new LineStyle({
356
- ...style,
357
- startZoom: style.startZoom ?? startZoom
358
- })
350
+ (style) => style instanceof LineStyle ? style : new LineStyle(style)
359
351
  );
360
- this.delWidthFactor = delWidthFactor;
361
- this.lineExtensionFactor = lineExtensionFactor;
362
352
  }
363
353
  /**
364
354
  * Get line styles active at a given zoom level
@@ -368,6 +358,15 @@ var IndiaBoundaryCorrector = (() => {
368
358
  getLineStylesForZoom(z2) {
369
359
  return this.lineStyles.filter((style) => style.isActiveAtZoom(z2));
370
360
  }
361
+ /**
362
+ * Get unique layer suffixes from styles active at a given zoom level
363
+ * @param {number} z - Zoom level
364
+ * @returns {string[]}
365
+ */
366
+ getLayerSuffixesForZoom(z2) {
367
+ const activeStyles = this.getLineStylesForZoom(z2);
368
+ return [...new Set(activeStyles.map((s) => s.layerSuffix))];
369
+ }
371
370
  /**
372
371
  * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
373
372
  * @param {string | string[]} templates - Single template URL or array of template URLs
@@ -425,13 +424,9 @@ var IndiaBoundaryCorrector = (() => {
425
424
  toJSON() {
426
425
  return {
427
426
  id: this.id,
428
- startZoom: this.startZoom,
429
- zoomThreshold: this.zoomThreshold,
430
427
  tileUrlTemplates: this.tileUrlTemplates,
431
428
  lineWidthStops: this.lineWidthStops,
432
- lineStyles: this.lineStyles.map((s) => s.toJSON()),
433
- delWidthFactor: this.delWidthFactor,
434
- lineExtensionFactor: this.lineExtensionFactor
429
+ lineStyles: this.lineStyles.map((s) => s.toJSON())
435
430
  };
436
431
  }
437
432
  /**
@@ -2768,6 +2763,494 @@ var IndiaBoundaryCorrector = (() => {
2768
2763
  return await this._fetchTile(z2, x22, y, signal);
2769
2764
  }
2770
2765
  };
2766
+ var configs_default2 = [
2767
+ {
2768
+ id: "cartodb-dark",
2769
+ tileUrlTemplates: [
2770
+ "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
2771
+ "https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
2772
+ "https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png",
2773
+ "https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
2774
+ "https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
2775
+ "https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png"
2776
+ ],
2777
+ lineWidthStops: { "0": 1, "2": 1, "3": 1, "10": 2.5 },
2778
+ lineStyles: [
2779
+ { color: "rgb(40, 40, 40)", layerSuffix: "ne", endZoom: 4 },
2780
+ { color: "rgb(40, 40, 40)", layerSuffix: "ne-disp", endZoom: 4 },
2781
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm", startZoom: 5 },
2782
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm-disp", startZoom: 5 },
2783
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.3, dashArray: [2, 2] }
2784
+ ]
2785
+ },
2786
+ {
2787
+ id: "cartodb-light",
2788
+ tileUrlTemplates: [
2789
+ "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
2790
+ "https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
2791
+ "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png",
2792
+ "https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",
2793
+ "https://basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png",
2794
+ "https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}{r}.png",
2795
+ "https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
2796
+ "https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png",
2797
+ "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png",
2798
+ "https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png",
2799
+ "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
2800
+ "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png"
2801
+ ],
2802
+ lineWidthStops: { "1": 0.5, "2": 0.5, "3": 0.5, "4": 1, "5": 1.25, "7": 1.5, "16": 2.5 },
2803
+ lineStyles: [
2804
+ { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2 },
2805
+ { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, lineExtensionFactor: 0 },
2806
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 6, widthFraction: 3, lineExtensionFactor: 0.1 },
2807
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
2808
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", alpha: 0.2, startZoom: 6, widthFraction: 3, lineExtensionFactor: 0 },
2809
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, lineExtensionFactor: 0 },
2810
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.75, dashArray: [2, 2], delWidthFactor: 2 }
2811
+ ]
2812
+ },
2813
+ {
2814
+ id: "open-topo",
2815
+ tileUrlTemplates: [
2816
+ "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
2817
+ "https://tile.opentopomap.org/{z}/{x}/{y}.png"
2818
+ ],
2819
+ lineWidthStops: { "4": 0.75, "5": 1, "6": 1.25, "7": 1.5, "8": 1.75, "9": 1.25, "10": 1.25, "13": 1.5 },
2820
+ lineStyles: [
2821
+ { color: "rgb(83, 83, 83)", layerSuffix: "ne", startZoom: 4, endZoom: 6 },
2822
+ { color: "rgb(173, 173, 173)", layerSuffix: "osm", startZoom: 7, endZoom: 8, alpha: 0.5, widthFraction: 4 },
2823
+ { color: "rgb(83, 83, 83)", layerSuffix: "osm", startZoom: 7, endZoom: 8 },
2824
+ { color: "rgb(199, 158, 204)", layerSuffix: "osm", startZoom: 9, widthFraction: 7, alpha: 0.6, lineExtensionFactor: 0.2 },
2825
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 }
2826
+ ]
2827
+ },
2828
+ {
2829
+ id: "osm-carto",
2830
+ tileUrlTemplates: [
2831
+ "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
2832
+ "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
2833
+ ],
2834
+ lineWidthStops: { "1": 0.5, "2": 0.6, "3": 0.7, "4": 1, "10": 3.75 },
2835
+ lineStyles: [
2836
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 1, endZoom: 3, delWidthFactor: 2.5, widthFraction: 1.5 },
2837
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 4 },
2838
+ { color: "rgb(160, 120, 160)", layerSuffix: "osm", startZoom: 4, widthFraction: 0.333, dashArray: [30, 2, 8, 2] },
2839
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.45 },
2840
+ { color: "rgb(160, 120, 160)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.15, dashArray: [4, 2, 10] }
2841
+ ]
2842
+ },
2843
+ {
2844
+ id: "osm-hot",
2845
+ tileUrlTemplates: [
2846
+ "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
2847
+ "https://tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
2848
+ ],
2849
+ lineWidthStops: { "2": 3, "3": 3, "7": 3, "8": 3.5, "9": 3.5 },
2850
+ lineStyles: [
2851
+ { color: "rgb(149, 175, 180)", layerSuffix: "osm", lineExtensionFactor: 0.25, startZoom: 2 },
2852
+ { color: "rgb(89, 117, 123)", layerSuffix: "osm", widthFraction: 0.33, lineExtensionFactor: 0.25, startZoom: 2 },
2853
+ { color: "rgb(172, 163, 163)", layerSuffix: "osm-internal", widthFraction: 0.33, startZoom: 4, dashArray: [7, 7, 7] }
2854
+ ]
2855
+ }
2856
+ ];
2857
+ var INFINITY2 = -1;
2858
+ function templateToRegex2(template) {
2859
+ const groups = [];
2860
+ let pattern = template.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
2861
+ if (char === "{" || char === "}") return char;
2862
+ return "\\" + char;
2863
+ }).replace(/^https:\/\//, "https?://").replace(/^http:\/\//, "https?://").replace(/\{[a-z0-9]-[a-z0-9]\}/gi, () => {
2864
+ groups.push("s");
2865
+ return "([a-z0-9]+)";
2866
+ }).replace(/\{(z|x|y|s|r)\}/gi, (_2, name) => {
2867
+ const lowerName = name.toLowerCase();
2868
+ groups.push(lowerName);
2869
+ if (lowerName === "s") {
2870
+ return "([a-z0-9]+)";
2871
+ }
2872
+ if (lowerName === "r") {
2873
+ return "(@\\d+x)?";
2874
+ }
2875
+ return "(\\d+)";
2876
+ });
2877
+ return { pattern: new RegExp("^" + pattern + "(\\?.*)?$", "i"), groups };
2878
+ }
2879
+ function templateToTemplateRegex2(template) {
2880
+ let pattern = template.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
2881
+ if (char === "{" || char === "}") return char;
2882
+ return "\\" + char;
2883
+ }).replace(/^https:\/\//, "https?://").replace(/^http:\/\//, "https?://").replace(/\{([a-z0-9])-([a-z0-9])\}/gi, (_2, start, end) => `(\\{${start}-${end}\\}|\\{s\\}|[a-z0-9]+)`).replace(/\{(z|x|y|s|r)\}/gi, (_2, name) => {
2884
+ const lowerName = name.toLowerCase();
2885
+ if (lowerName === "s") {
2886
+ return "(\\{s\\}|\\{[a-z0-9]-[a-z0-9]\\}|[a-z0-9]+)";
2887
+ }
2888
+ if (lowerName === "r") {
2889
+ return "(\\{r\\}|@\\d+x)?";
2890
+ }
2891
+ return `\\{${lowerName}\\}`;
2892
+ });
2893
+ return new RegExp("^" + pattern + "(\\?.*)?$", "i");
2894
+ }
2895
+ function isValidColor2(color) {
2896
+ if (typeof color !== "string" || !color.trim()) return false;
2897
+ if (typeof CSS !== "undefined" && CSS.supports) {
2898
+ return CSS.supports("color", color);
2899
+ }
2900
+ const trimmed = color.trim().toLowerCase();
2901
+ if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;
2902
+ if (/^(rgb|hsl)a?\(/.test(trimmed)) return true;
2903
+ if (/^[a-z]+$/.test(trimmed)) return true;
2904
+ return false;
2905
+ }
2906
+ var LineStyle2 = class _LineStyle2 {
2907
+ /**
2908
+ * Validate a LineStyle configuration object.
2909
+ * @param {Object} obj - The object to validate
2910
+ * @param {number} [index] - Optional index for error messages (when validating in an array)
2911
+ * @throws {Error} If validation fails
2912
+ */
2913
+ static validateJSON(obj, index) {
2914
+ const prefix = index !== void 0 ? `lineStyles[${index}]` : "LineStyle";
2915
+ if (!obj || typeof obj !== "object") {
2916
+ throw new Error(`${prefix}: must be an object`);
2917
+ }
2918
+ if (!obj.color || typeof obj.color !== "string") {
2919
+ throw new Error(`${prefix}: color must be a non-empty string`);
2920
+ }
2921
+ if (!isValidColor2(obj.color)) {
2922
+ throw new Error(`${prefix}: color "${obj.color}" is not a valid CSS color`);
2923
+ }
2924
+ if (!obj.layerSuffix || typeof obj.layerSuffix !== "string") {
2925
+ throw new Error(`${prefix}: layerSuffix must be a non-empty string`);
2926
+ }
2927
+ if (obj.widthFraction !== void 0 && (typeof obj.widthFraction !== "number" || obj.widthFraction <= 0)) {
2928
+ throw new Error(`${prefix}: widthFraction must be a positive number`);
2929
+ }
2930
+ if (obj.dashArray !== void 0 && !Array.isArray(obj.dashArray)) {
2931
+ throw new Error(`${prefix}: dashArray must be an array`);
2932
+ }
2933
+ if (obj.alpha !== void 0 && (typeof obj.alpha !== "number" || obj.alpha < 0 || obj.alpha > 1)) {
2934
+ throw new Error(`${prefix}: alpha must be a number between 0 and 1`);
2935
+ }
2936
+ if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
2937
+ throw new Error(`${prefix}: startZoom must be a non-negative number`);
2938
+ }
2939
+ if (obj.endZoom !== void 0 && (typeof obj.endZoom !== "number" || obj.endZoom < 0 && obj.endZoom !== INFINITY2)) {
2940
+ throw new Error(`${prefix}: endZoom must be a non-negative number or INFINITY (${INFINITY2})`);
2941
+ }
2942
+ if (obj.lineExtensionFactor !== void 0 && (typeof obj.lineExtensionFactor !== "number" || obj.lineExtensionFactor < 0)) {
2943
+ throw new Error(`${prefix}: lineExtensionFactor must be a non-negative number`);
2944
+ }
2945
+ if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor < 0)) {
2946
+ throw new Error(`${prefix}: delWidthFactor must be a non-negative number`);
2947
+ }
2948
+ }
2949
+ /**
2950
+ * @param {Object} options
2951
+ * @param {string} options.color - CSS color string
2952
+ * @param {string} options.layerSuffix - Layer suffix (e.g., 'osm', 'ne', 'osm-disp')
2953
+ * @param {number} [options.widthFraction=1.0] - Multiplier for base line width
2954
+ * @param {number[]} [options.dashArray] - Dash pattern for dashed lines
2955
+ * @param {number} [options.alpha=1.0] - Opacity (0-1)
2956
+ * @param {number} [options.startZoom=0] - Minimum zoom level for this style
2957
+ * @param {number} [options.endZoom=INFINITY] - Maximum zoom level for this style (INFINITY means no limit)
2958
+ * @param {number} [options.lineExtensionFactor=0.5] - Factor to extend lines by (multiplied by deletion line width)
2959
+ * @param {number} [options.delWidthFactor=1.5] - Factor to multiply line width for deletion blur
2960
+ */
2961
+ constructor({ color, layerSuffix, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY2, lineExtensionFactor = 0.5, delWidthFactor = 1.5 }) {
2962
+ this.color = color;
2963
+ this.layerSuffix = layerSuffix;
2964
+ this.widthFraction = widthFraction;
2965
+ this.dashArray = dashArray;
2966
+ this.alpha = alpha;
2967
+ this.startZoom = startZoom;
2968
+ this.endZoom = endZoom;
2969
+ this.lineExtensionFactor = lineExtensionFactor;
2970
+ this.delWidthFactor = delWidthFactor;
2971
+ }
2972
+ /**
2973
+ * Check if this style is active at the given zoom level.
2974
+ * @param {number} z - Zoom level
2975
+ * @returns {boolean}
2976
+ */
2977
+ isActiveAtZoom(z2) {
2978
+ return z2 >= this.startZoom && (this.endZoom === INFINITY2 || z2 <= this.endZoom);
2979
+ }
2980
+ /**
2981
+ * Serialize to plain object.
2982
+ * @returns {Object}
2983
+ */
2984
+ toJSON() {
2985
+ return {
2986
+ color: this.color,
2987
+ layerSuffix: this.layerSuffix,
2988
+ widthFraction: this.widthFraction,
2989
+ dashArray: this.dashArray,
2990
+ alpha: this.alpha,
2991
+ startZoom: this.startZoom,
2992
+ endZoom: this.endZoom,
2993
+ lineExtensionFactor: this.lineExtensionFactor,
2994
+ delWidthFactor: this.delWidthFactor
2995
+ };
2996
+ }
2997
+ /**
2998
+ * Create from plain object with validation.
2999
+ * @param {Object} obj
3000
+ * @param {number} [index] - Optional index for error messages
3001
+ * @returns {LineStyle}
3002
+ */
3003
+ static fromJSON(obj, index) {
3004
+ _LineStyle2.validateJSON(obj, index);
3005
+ return new _LineStyle2(obj);
3006
+ }
3007
+ };
3008
+ var LayerConfig2 = class _LayerConfig2 {
3009
+ /**
3010
+ * Validate a LayerConfig configuration object.
3011
+ * Also validates all lineStyles within the config.
3012
+ * @param {Object} obj - The object to validate
3013
+ * @throws {Error} If validation fails
3014
+ */
3015
+ static validateJSON(obj) {
3016
+ if (!obj || typeof obj !== "object") {
3017
+ throw new Error("LayerConfig: must be an object");
3018
+ }
3019
+ if (!obj.id || typeof obj.id !== "string") {
3020
+ throw new Error("LayerConfig: id must be a non-empty string");
3021
+ }
3022
+ if (obj.id.includes("/")) {
3023
+ throw new Error(`LayerConfig: id cannot contain slashes: "${obj.id}"`);
3024
+ }
3025
+ const id = obj.id;
3026
+ if (obj.lineWidthStops !== void 0) {
3027
+ if (!obj.lineWidthStops || typeof obj.lineWidthStops !== "object" || Array.isArray(obj.lineWidthStops)) {
3028
+ throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
3029
+ }
3030
+ const stopKeys = Object.keys(obj.lineWidthStops);
3031
+ if (stopKeys.length < 2) {
3032
+ throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
3033
+ }
3034
+ for (const key of stopKeys) {
3035
+ const zoom = Number(key);
3036
+ if (!Number.isInteger(zoom) || zoom < 0) {
3037
+ throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
3038
+ }
3039
+ if (typeof obj.lineWidthStops[key] !== "number" || obj.lineWidthStops[key] <= 0) {
3040
+ throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
3041
+ }
3042
+ }
3043
+ }
3044
+ if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
3045
+ throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
3046
+ }
3047
+ for (let i2 = 0; i2 < obj.lineStyles.length; i2++) {
3048
+ LineStyle2.validateJSON(obj.lineStyles[i2], i2);
3049
+ }
3050
+ }
3051
+ constructor({
3052
+ id,
3053
+ // Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
3054
+ tileUrlTemplates = [],
3055
+ // Line width stops: map of zoom level to line width (at least 2 entries)
3056
+ // Note: interpolated/extrapolated line width is capped at a minimum of 0.5
3057
+ lineWidthStops = { 1: 0.5, 10: 2.5 },
3058
+ // Line styles array - each element describes a line to draw from a specific layer
3059
+ // { color: string, layerSuffix: string, widthFraction?: number, dashArray?: number[],
3060
+ // startZoom?: number, endZoom?: number, lineExtensionFactor?: number, delWidthFactor?: number }
3061
+ // Lines are drawn in array order. layerSuffix determines which PMTiles layer to use.
3062
+ lineStyles
3063
+ }) {
3064
+ this.id = id;
3065
+ const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : tileUrlTemplates ? [tileUrlTemplates] : [];
3066
+ this.tileUrlTemplates = templates;
3067
+ this._compiledPatterns = templates.map((t) => templateToRegex2(t));
3068
+ this._templatePatterns = templates.map((t) => templateToTemplateRegex2(t));
3069
+ this.lineWidthStops = lineWidthStops;
3070
+ this.lineStyles = lineStyles.map(
3071
+ (style) => style instanceof LineStyle2 ? style : new LineStyle2(style)
3072
+ );
3073
+ }
3074
+ /**
3075
+ * Get line styles active at a given zoom level
3076
+ * @param {number} z - Zoom level
3077
+ * @returns {LineStyle[]}
3078
+ */
3079
+ getLineStylesForZoom(z2) {
3080
+ return this.lineStyles.filter((style) => style.isActiveAtZoom(z2));
3081
+ }
3082
+ /**
3083
+ * Get unique layer suffixes from styles active at a given zoom level
3084
+ * @param {number} z - Zoom level
3085
+ * @returns {string[]}
3086
+ */
3087
+ getLayerSuffixesForZoom(z2) {
3088
+ const activeStyles = this.getLineStylesForZoom(z2);
3089
+ return [...new Set(activeStyles.map((s) => s.layerSuffix))];
3090
+ }
3091
+ /**
3092
+ * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
3093
+ * @param {string | string[]} templates - Single template URL or array of template URLs
3094
+ * @returns {boolean}
3095
+ */
3096
+ matchTemplate(templates) {
3097
+ if (this._templatePatterns.length === 0) return false;
3098
+ const urls = Array.isArray(templates) ? templates : [templates];
3099
+ if (urls.length === 0) return false;
3100
+ return urls.some(
3101
+ (url) => this._templatePatterns.some((pattern) => pattern.test(url))
3102
+ );
3103
+ }
3104
+ /**
3105
+ * Check if this config matches the given tile URLs (with actual coordinates)
3106
+ * @param {string | string[]} tiles - Single tile URL or array of tile URLs
3107
+ * @returns {boolean}
3108
+ */
3109
+ matchTileUrl(tiles) {
3110
+ if (this._compiledPatterns.length === 0) return false;
3111
+ const urls = Array.isArray(tiles) ? tiles : [tiles];
3112
+ if (urls.length === 0) return false;
3113
+ return urls.some(
3114
+ (url) => this._compiledPatterns.some(({ pattern }) => pattern.test(url))
3115
+ );
3116
+ }
3117
+ /**
3118
+ * Extract tile coordinates (z, x, y) from a URL using this config's templates
3119
+ * @param {string} url - Tile URL to extract coordinates from
3120
+ * @returns {{ z: number, x: number, y: number } | null}
3121
+ */
3122
+ extractCoords(url) {
3123
+ for (const { pattern, groups } of this._compiledPatterns) {
3124
+ const match = url.match(pattern);
3125
+ if (match) {
3126
+ const result = {};
3127
+ for (let i2 = 0; i2 < groups.length; i2++) {
3128
+ const name = groups[i2];
3129
+ const value = match[i2 + 1];
3130
+ if (name === "z" || name === "x" || name === "y") {
3131
+ result[name] = parseInt(value, 10);
3132
+ }
3133
+ }
3134
+ if ("z" in result && "x" in result && "y" in result) {
3135
+ return { z: result.z, x: result.x, y: result.y };
3136
+ }
3137
+ }
3138
+ }
3139
+ return null;
3140
+ }
3141
+ /**
3142
+ * Serialize the config to a plain object for postMessage
3143
+ * @returns {Object}
3144
+ */
3145
+ toJSON() {
3146
+ return {
3147
+ id: this.id,
3148
+ tileUrlTemplates: this.tileUrlTemplates,
3149
+ lineWidthStops: this.lineWidthStops,
3150
+ lineStyles: this.lineStyles.map((s) => s.toJSON())
3151
+ };
3152
+ }
3153
+ /**
3154
+ * Create a LayerConfig from a plain object with validation.
3155
+ * @param {Object} obj
3156
+ * @returns {LayerConfig}
3157
+ * @throws {Error} If validation fails
3158
+ */
3159
+ static fromJSON(obj) {
3160
+ _LayerConfig2.validateJSON(obj);
3161
+ return new _LayerConfig2(obj);
3162
+ }
3163
+ };
3164
+ var LayerConfigRegistry2 = class _LayerConfigRegistry2 {
3165
+ constructor() {
3166
+ this.registry = {};
3167
+ }
3168
+ /**
3169
+ * Get a layer config by id
3170
+ */
3171
+ get(id) {
3172
+ return this.registry[id];
3173
+ }
3174
+ /**
3175
+ * Register a new layer config
3176
+ */
3177
+ register(config) {
3178
+ this.registry[config.id] = config;
3179
+ }
3180
+ /**
3181
+ * Remove a layer config by id
3182
+ */
3183
+ remove(id) {
3184
+ if (!this.registry[id]) return false;
3185
+ delete this.registry[id];
3186
+ return true;
3187
+ }
3188
+ /**
3189
+ * Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)
3190
+ * @param {string | string[]} templates - Single template URL or array of template URLs
3191
+ */
3192
+ detectFromTemplates(templates) {
3193
+ if (!templates || Array.isArray(templates) && templates.length === 0) return void 0;
3194
+ for (const config of Object.values(this.registry)) {
3195
+ if (config.matchTemplate(templates)) {
3196
+ return config;
3197
+ }
3198
+ }
3199
+ return void 0;
3200
+ }
3201
+ /**
3202
+ * Detect layer config from actual tile URLs (with numeric coordinates)
3203
+ * @param {string | string[]} urls - Single tile URL or array of tile URLs
3204
+ */
3205
+ detectFromTileUrls(urls) {
3206
+ if (!urls || Array.isArray(urls) && urls.length === 0) return void 0;
3207
+ for (const config of Object.values(this.registry)) {
3208
+ if (config.matchTileUrl(urls)) {
3209
+ return config;
3210
+ }
3211
+ }
3212
+ return void 0;
3213
+ }
3214
+ /**
3215
+ * Get all available layer config ids
3216
+ */
3217
+ getAvailableIds() {
3218
+ return Object.keys(this.registry);
3219
+ }
3220
+ /**
3221
+ * Create a new registry with all configs from this registry plus extra configs.
3222
+ * @param {LayerConfig[]} extraLayerConfigs - Additional configs to add
3223
+ * @returns {LayerConfigRegistry} A new registry with merged configs
3224
+ */
3225
+ createMergedRegistry(extraLayerConfigs) {
3226
+ const registry = new _LayerConfigRegistry2();
3227
+ for (const id of this.getAvailableIds()) {
3228
+ registry.register(this.get(id));
3229
+ }
3230
+ if (extraLayerConfigs && extraLayerConfigs.length > 0) {
3231
+ for (const config of extraLayerConfigs) {
3232
+ registry.register(config);
3233
+ }
3234
+ }
3235
+ return registry;
3236
+ }
3237
+ /**
3238
+ * Parse a tile URL into its components: layer config and coordinates
3239
+ * @param {string} url - Tile URL to parse
3240
+ * @returns {{ layerConfig: LayerConfig, coords: { z: number, x: number, y: number } } | null}
3241
+ */
3242
+ parseTileUrl(url) {
3243
+ const layerConfig = this.detectFromTileUrls([url]);
3244
+ if (!layerConfig) return null;
3245
+ const coords = layerConfig.extractCoords(url);
3246
+ if (!coords) return null;
3247
+ return { layerConfig, coords };
3248
+ }
3249
+ };
3250
+ var layerConfigs2 = new LayerConfigRegistry2();
3251
+ for (const configData of configs_default2) {
3252
+ layerConfigs2.register(new LayerConfig2(configData));
3253
+ }
2771
3254
  var TileFetchError = class _TileFetchError extends Error {
2772
3255
  /**
2773
3256
  * @param {number} status - HTTP status code
@@ -2795,7 +3278,7 @@ var IndiaBoundaryCorrector = (() => {
2795
3278
  return new _TileFetchError(response.status, response.url, body);
2796
3279
  }
2797
3280
  };
2798
- var MIN_LINE_WIDTH = 0.5;
3281
+ var MIN_LINE_WIDTH = 0.1;
2799
3282
  var DEFAULT_TILE_EXTENT = 4096;
2800
3283
  var DEFAULT_LINE_WIDTH = 1;
2801
3284
  function getLineWidth(zoom, lineWidthStops) {
@@ -3091,29 +3574,22 @@ var IndiaBoundaryCorrector = (() => {
3091
3574
  * @returns {Promise<ArrayBuffer>} The corrected tile as ArrayBuffer (PNG)
3092
3575
  */
3093
3576
  async fixTile(corrections, rasterTile, layerConfig, zoom) {
3094
- const {
3095
- startZoom = 0,
3096
- zoomThreshold,
3097
- lineWidthStops,
3098
- delWidthFactor
3099
- } = layerConfig;
3100
- if (zoom < startZoom) {
3101
- return rasterTile;
3102
- }
3577
+ const { lineWidthStops } = layerConfig;
3103
3578
  let activeLineStyles;
3104
3579
  if (layerConfig.getLineStylesForZoom) {
3105
3580
  activeLineStyles = layerConfig.getLineStylesForZoom(zoom);
3106
3581
  } else {
3107
3582
  const allStyles = layerConfig.lineStyles || [];
3108
3583
  activeLineStyles = allStyles.filter((style) => {
3109
- const styleStart = style.startZoom ?? startZoom;
3110
- const styleEnd = style.endZoom ?? Infinity;
3111
- return zoom >= styleStart && zoom <= styleEnd;
3584
+ const styleStart = style.startZoom ?? 0;
3585
+ const styleEnd = style.endZoom ?? INFINITY2;
3586
+ return zoom >= styleStart && (styleEnd === INFINITY2 || zoom <= styleEnd);
3112
3587
  });
3113
3588
  }
3114
- const useOsm = zoom >= zoomThreshold;
3115
- const addLayerName = useOsm ? "to-add-osm" : "to-add-ne";
3116
- const delLayerName = useOsm ? "to-del-osm" : "to-del-ne";
3589
+ if (activeLineStyles.length === 0) {
3590
+ return rasterTile;
3591
+ }
3592
+ const layerSuffixes = [...new Set(activeLineStyles.map((s) => s.layerSuffix))];
3117
3593
  const blob = new Blob([rasterTile]);
3118
3594
  const imageBitmap = await createImageBitmap(blob);
3119
3595
  const tileSize = imageBitmap.width;
@@ -3124,22 +3600,33 @@ var IndiaBoundaryCorrector = (() => {
3124
3600
  const ctx = canvas.getContext("2d", { willReadFrequently: true });
3125
3601
  ctx.drawImage(imageBitmap, 0, 0, tileSize, tileSize);
3126
3602
  const baseLineWidth = getLineWidth(zoom, lineWidthStops);
3127
- const maxWidthFraction = activeLineStyles.length > 0 ? Math.max(...activeLineStyles.map((s) => s.widthFraction)) : 1;
3128
- const delLineWidth = baseLineWidth * maxWidthFraction * delWidthFactor;
3129
- const delFeatures = corrections[delLayerName] || [];
3130
- if (delFeatures.length > 0) {
3131
- if (!this._maskCanvas || this._maskCanvas.width !== tileSize) {
3132
- this._maskCanvas = new OffscreenCanvas(tileSize, tileSize);
3133
- }
3134
- applyMedianBlurAlongPath(ctx, delFeatures, delLineWidth, tileSize, this._maskCanvas);
3135
- }
3136
- let addFeatures = corrections[addLayerName] || [];
3137
- if (addFeatures.length > 0 && activeLineStyles.length > 0) {
3603
+ const delLineWidthBySuffix = {};
3604
+ for (const suffix of layerSuffixes) {
3605
+ const stylesForSuffix = activeLineStyles.filter((s) => s.layerSuffix === suffix);
3606
+ const maxProduct = Math.max(...stylesForSuffix.map((s) => s.widthFraction * s.delWidthFactor));
3607
+ delLineWidthBySuffix[suffix] = baseLineWidth * maxProduct;
3608
+ }
3609
+ for (const suffix of layerSuffixes) {
3610
+ const delLineWidth = delLineWidthBySuffix[suffix];
3611
+ if (delLineWidth === 0) continue;
3612
+ const delLayerName = `to-del-${suffix}`;
3613
+ const delFeatures = corrections[delLayerName] || [];
3138
3614
  if (delFeatures.length > 0) {
3139
- addFeatures = extendFeaturesByFactor(addFeatures, layerConfig.lineExtensionFactor, delLineWidth, tileSize);
3615
+ if (!this._maskCanvas || this._maskCanvas.width !== tileSize) {
3616
+ this._maskCanvas = new OffscreenCanvas(tileSize, tileSize);
3617
+ }
3618
+ applyMedianBlurAlongPath(ctx, delFeatures, delLineWidth, tileSize, this._maskCanvas);
3140
3619
  }
3141
- for (const style of activeLineStyles) {
3142
- const { color, widthFraction, dashArray, alpha } = style;
3620
+ }
3621
+ for (const style of activeLineStyles) {
3622
+ const { color, layerSuffix, widthFraction, dashArray, alpha, lineExtensionFactor } = style;
3623
+ const addLayerName = `to-add-${layerSuffix}`;
3624
+ let addFeatures = corrections[addLayerName] || [];
3625
+ if (addFeatures.length > 0) {
3626
+ const delLineWidth = delLineWidthBySuffix[layerSuffix];
3627
+ if (lineExtensionFactor > 0) {
3628
+ addFeatures = extendFeaturesByFactor(addFeatures, lineExtensionFactor, delLineWidth, tileSize);
3629
+ }
3143
3630
  const lineWidth = baseLineWidth * widthFraction;
3144
3631
  drawFeatures(ctx, addFeatures, color, lineWidth, tileSize, dashArray, alpha);
3145
3632
  }