@india-boundary-corrector/service-worker 0.0.4 → 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.
package/README.md CHANGED
@@ -75,8 +75,9 @@ await sw.addLayerConfig(new LayerConfig({
75
75
  tileUrlTemplates: ['https://tile.openstreetmap.de/{z}/{x}/{y}.png'],
76
76
  lineWidthStops: { 1: 0.5, 2: 0.6, 3: 0.7, 4: 1.0, 10: 3.75 },
77
77
  lineStyles: [
78
- { color: 'rgb(180, 200, 180)' },
79
- { color: 'rgb(121, 146, 127)', widthFraction: 1/3, dashArray: [30, 2, 8, 2] },
78
+ // layerSuffix determines which PMTiles layer to use
79
+ { color: 'rgb(180, 200, 180)', layerSuffix: 'osm' },
80
+ { color: 'rgb(121, 146, 127)', layerSuffix: 'osm', widthFraction: 1/3, dashArray: [30, 2, 8, 2] },
80
81
  ],
81
82
  }));
82
83
  ```
@@ -126,7 +127,7 @@ importScripts('https://cdn.jsdelivr.net/npm/@india-boundary-corrector/service-wo
126
127
  await sw.addLayerConfig(new LayerConfig({
127
128
  id: 'my-tiles',
128
129
  tileUrlTemplates: ['https://mytiles.example.com/{z}/{x}/{y}.png'],
129
- lineStyles: [{ color: 'rgb(165, 180, 165)' }],
130
+ lineStyles: [{ color: 'rgb(165, 180, 165)', layerSuffix: 'osm' }],
130
131
  }));
131
132
  });
132
133
  </script>
@@ -148,6 +149,18 @@ Register the service worker and wait for it to take control.
148
149
 
149
150
  Returns: `Promise<CorrectionServiceWorker>`
150
151
 
152
+ #### PMTiles URL
153
+
154
+ **Important:** The service worker cannot auto-detect the PMTiles URL from `import.meta.url` or `document.currentScript` since it runs in a worker context. If you don't specify `pmtilesUrl`, it will default to fetching from the jsDelivr CDN.
155
+
156
+ For local development or self-hosted deployments, always specify the PMTiles URL:
157
+
158
+ ```javascript
159
+ const sw = await registerCorrectionServiceWorker('./sw.js', {
160
+ pmtilesUrl: '/path/to/india_boundary_corrections.pmtiles'
161
+ });
162
+ ```
163
+
151
164
  #### Development Mode
152
165
 
153
166
  Use `forceReinstall: true` during development to ensure you always get a fresh service worker:
@@ -33,7 +33,6 @@ var IndiaBoundaryCorrector = (() => {
33
33
  var configs_default = [
34
34
  {
35
35
  id: "cartodb-dark",
36
- zoomThreshold: 5,
37
36
  tileUrlTemplates: [
38
37
  "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
39
38
  "https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
@@ -42,15 +41,17 @@ var IndiaBoundaryCorrector = (() => {
42
41
  "https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png",
43
42
  "https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png"
44
43
  ],
45
- lineWidthStops: { "1": 0.5, "10": 2.5 },
44
+ lineWidthStops: { "0": 1, "2": 1, "3": 1, "10": 2.5 },
46
45
  lineStyles: [
47
- { color: "rgb(40, 40, 40)" }
46
+ { color: "rgb(40, 40, 40)", layerSuffix: "ne", endZoom: 4 },
47
+ { color: "rgb(40, 40, 40)", layerSuffix: "ne-disp", endZoom: 4 },
48
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm", startZoom: 5 },
49
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm-disp", startZoom: 5 },
50
+ { color: "rgb(40, 40, 40)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.3, dashArray: [2, 2] }
48
51
  ]
49
52
  },
50
53
  {
51
54
  id: "cartodb-light",
52
- startZoom: 0,
53
- zoomThreshold: 5,
54
55
  tileUrlTemplates: [
55
56
  "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
56
57
  "https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
@@ -65,56 +66,62 @@ var IndiaBoundaryCorrector = (() => {
65
66
  "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
66
67
  "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png"
67
68
  ],
68
- lineWidthStops: { "1": 0.25, "2": 0.25, "3": 0.5, "4": 0.75, "5": 1 },
69
+ lineWidthStops: { "1": 0.5, "2": 0.5, "3": 0.5, "4": 1, "5": 1.25, "7": 1.5, "16": 2.5 },
69
70
  lineStyles: [
70
- { color: "rgb(235, 214, 214)", alpha: 0.2, startZoom: 6, widthFraction: 5 },
71
- { color: "rgb(235, 214, 214)" }
71
+ { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2 },
72
+ { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, lineExtensionFactor: 0 },
73
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 6, widthFraction: 3, lineExtensionFactor: 0.1 },
74
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
75
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", alpha: 0.2, startZoom: 6, widthFraction: 3, lineExtensionFactor: 0 },
76
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, lineExtensionFactor: 0 },
77
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.75, dashArray: [2, 2], delWidthFactor: 2 }
72
78
  ]
73
79
  },
74
80
  {
75
81
  id: "open-topo",
76
- startZoom: 4,
77
- zoomThreshold: 4,
78
82
  tileUrlTemplates: [
79
83
  "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
80
84
  "https://tile.opentopomap.org/{z}/{x}/{y}.png"
81
85
  ],
82
86
  lineWidthStops: { "4": 0.75, "5": 1, "6": 1.25, "7": 1.5, "8": 1.75, "9": 1.25, "10": 1.25, "13": 1.5 },
83
87
  lineStyles: [
84
- { color: "rgb(83, 83, 83)", startZoom: 7, endZoom: 8, alpha: 0.4, widthFraction: 4 },
85
- { color: "rgb(83, 83, 83)", endZoom: 8 },
86
- { color: "rgb(140, 20, 180)", startZoom: 9, widthFraction: 9, alpha: 0.2 },
87
- { color: "rgb(140, 20, 180)", startZoom: 9 }
88
+ { color: "rgb(83, 83, 83)", layerSuffix: "ne", startZoom: 4, endZoom: 6 },
89
+ { color: "rgb(173, 173, 173)", layerSuffix: "osm", startZoom: 7, endZoom: 8, alpha: 0.5, widthFraction: 4 },
90
+ { color: "rgb(83, 83, 83)", layerSuffix: "osm", startZoom: 7, endZoom: 8 },
91
+ { color: "rgb(199, 158, 204)", layerSuffix: "osm", startZoom: 9, widthFraction: 7, alpha: 0.6, lineExtensionFactor: 0.2 },
92
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 }
88
93
  ]
89
94
  },
90
95
  {
91
96
  id: "osm-carto",
92
- startZoom: 1,
93
- zoomThreshold: 1,
94
97
  tileUrlTemplates: [
95
98
  "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
96
99
  "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
97
100
  ],
98
101
  lineWidthStops: { "1": 0.5, "2": 0.6, "3": 0.7, "4": 1, "10": 3.75 },
99
102
  lineStyles: [
100
- { color: "rgb(200, 180, 200)" },
101
- { color: "rgb(160, 120, 160)", widthFraction: 0.333, dashArray: [30, 2, 8, 2] }
103
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 1, endZoom: 3, delWidthFactor: 2.5, widthFraction: 1.5 },
104
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 4 },
105
+ { color: "rgb(160, 120, 160)", layerSuffix: "osm", startZoom: 4, widthFraction: 0.333, dashArray: [30, 2, 8, 2] },
106
+ { color: "rgb(200, 180, 200)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.45 },
107
+ { color: "rgb(160, 120, 160)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.15, dashArray: [4, 2, 10] }
102
108
  ]
103
109
  },
104
110
  {
105
111
  id: "osm-hot",
106
- startZoom: 2,
107
- zoomThreshold: 2,
108
112
  tileUrlTemplates: [
109
- "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
113
+ "https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png",
114
+ "https://tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"
110
115
  ],
111
116
  lineWidthStops: { "2": 3, "3": 3, "7": 3, "8": 3.5, "9": 3.5 },
112
117
  lineStyles: [
113
- { color: "rgb(149, 175, 180)" },
114
- { color: "rgb(89, 117, 123)", widthFraction: 0.33 }
118
+ { color: "rgb(149, 175, 180)", layerSuffix: "osm", lineExtensionFactor: 0.25, startZoom: 2 },
119
+ { color: "rgb(89, 117, 123)", layerSuffix: "osm", widthFraction: 0.33, lineExtensionFactor: 0.25, startZoom: 2 },
120
+ { color: "rgb(172, 163, 163)", layerSuffix: "osm-internal", widthFraction: 0.33, startZoom: 4, dashArray: [7, 7, 7] }
115
121
  ]
116
122
  }
117
123
  ];
124
+ var INFINITY = -1;
118
125
  function templateToRegex(template) {
119
126
  const groups = [];
120
127
  let pattern = template.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
@@ -152,89 +159,201 @@ var IndiaBoundaryCorrector = (() => {
152
159
  });
153
160
  return new RegExp("^" + pattern + "(\\?.*)?$", "i");
154
161
  }
162
+ function isValidColor(color) {
163
+ if (typeof color !== "string" || !color.trim()) return false;
164
+ if (typeof CSS !== "undefined" && CSS.supports) {
165
+ return CSS.supports("color", color);
166
+ }
167
+ const trimmed = color.trim().toLowerCase();
168
+ if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;
169
+ if (/^(rgb|hsl)a?\(/.test(trimmed)) return true;
170
+ if (/^[a-z]+$/.test(trimmed)) return true;
171
+ return false;
172
+ }
173
+ var LineStyle = class _LineStyle {
174
+ /**
175
+ * Validate a LineStyle configuration object.
176
+ * @param {Object} obj - The object to validate
177
+ * @param {number} [index] - Optional index for error messages (when validating in an array)
178
+ * @throws {Error} If validation fails
179
+ */
180
+ static validateJSON(obj, index) {
181
+ const prefix = index !== void 0 ? `lineStyles[${index}]` : "LineStyle";
182
+ if (!obj || typeof obj !== "object") {
183
+ throw new Error(`${prefix}: must be an object`);
184
+ }
185
+ if (!obj.color || typeof obj.color !== "string") {
186
+ throw new Error(`${prefix}: color must be a non-empty string`);
187
+ }
188
+ if (!isValidColor(obj.color)) {
189
+ throw new Error(`${prefix}: color "${obj.color}" is not a valid CSS color`);
190
+ }
191
+ if (!obj.layerSuffix || typeof obj.layerSuffix !== "string") {
192
+ throw new Error(`${prefix}: layerSuffix must be a non-empty string`);
193
+ }
194
+ if (obj.widthFraction !== void 0 && (typeof obj.widthFraction !== "number" || obj.widthFraction <= 0)) {
195
+ throw new Error(`${prefix}: widthFraction must be a positive number`);
196
+ }
197
+ if (obj.dashArray !== void 0 && !Array.isArray(obj.dashArray)) {
198
+ throw new Error(`${prefix}: dashArray must be an array`);
199
+ }
200
+ if (obj.alpha !== void 0 && (typeof obj.alpha !== "number" || obj.alpha < 0 || obj.alpha > 1)) {
201
+ throw new Error(`${prefix}: alpha must be a number between 0 and 1`);
202
+ }
203
+ if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
204
+ throw new Error(`${prefix}: startZoom must be a non-negative number`);
205
+ }
206
+ if (obj.endZoom !== void 0 && (typeof obj.endZoom !== "number" || obj.endZoom < 0 && obj.endZoom !== INFINITY)) {
207
+ throw new Error(`${prefix}: endZoom must be a non-negative number or INFINITY (${INFINITY})`);
208
+ }
209
+ if (obj.lineExtensionFactor !== void 0 && (typeof obj.lineExtensionFactor !== "number" || obj.lineExtensionFactor < 0)) {
210
+ throw new Error(`${prefix}: lineExtensionFactor must be a non-negative number`);
211
+ }
212
+ if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor < 0)) {
213
+ throw new Error(`${prefix}: delWidthFactor must be a non-negative number`);
214
+ }
215
+ }
216
+ /**
217
+ * @param {Object} options
218
+ * @param {string} options.color - CSS color string
219
+ * @param {string} options.layerSuffix - Layer suffix (e.g., 'osm', 'ne', 'osm-disp')
220
+ * @param {number} [options.widthFraction=1.0] - Multiplier for base line width
221
+ * @param {number[]} [options.dashArray] - Dash pattern for dashed lines
222
+ * @param {number} [options.alpha=1.0] - Opacity (0-1)
223
+ * @param {number} [options.startZoom=0] - Minimum zoom level for this style
224
+ * @param {number} [options.endZoom=INFINITY] - Maximum zoom level for this style (INFINITY means no limit)
225
+ * @param {number} [options.lineExtensionFactor=0.5] - Factor to extend lines by (multiplied by deletion line width)
226
+ * @param {number} [options.delWidthFactor=1.5] - Factor to multiply line width for deletion blur
227
+ */
228
+ constructor({ color, layerSuffix, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY, lineExtensionFactor = 0.5, delWidthFactor = 1.5 }) {
229
+ this.color = color;
230
+ this.layerSuffix = layerSuffix;
231
+ this.widthFraction = widthFraction;
232
+ this.dashArray = dashArray;
233
+ this.alpha = alpha;
234
+ this.startZoom = startZoom;
235
+ this.endZoom = endZoom;
236
+ this.lineExtensionFactor = lineExtensionFactor;
237
+ this.delWidthFactor = delWidthFactor;
238
+ }
239
+ /**
240
+ * Check if this style is active at the given zoom level.
241
+ * @param {number} z - Zoom level
242
+ * @returns {boolean}
243
+ */
244
+ isActiveAtZoom(z) {
245
+ return z >= this.startZoom && (this.endZoom === INFINITY || z <= this.endZoom);
246
+ }
247
+ /**
248
+ * Serialize to plain object.
249
+ * @returns {Object}
250
+ */
251
+ toJSON() {
252
+ return {
253
+ color: this.color,
254
+ layerSuffix: this.layerSuffix,
255
+ widthFraction: this.widthFraction,
256
+ dashArray: this.dashArray,
257
+ alpha: this.alpha,
258
+ startZoom: this.startZoom,
259
+ endZoom: this.endZoom,
260
+ lineExtensionFactor: this.lineExtensionFactor,
261
+ delWidthFactor: this.delWidthFactor
262
+ };
263
+ }
264
+ /**
265
+ * Create from plain object with validation.
266
+ * @param {Object} obj
267
+ * @param {number} [index] - Optional index for error messages
268
+ * @returns {LineStyle}
269
+ */
270
+ static fromJSON(obj, index) {
271
+ _LineStyle.validateJSON(obj, index);
272
+ return new _LineStyle(obj);
273
+ }
274
+ };
155
275
  var LayerConfig = class _LayerConfig {
276
+ /**
277
+ * Validate a LayerConfig configuration object.
278
+ * Also validates all lineStyles within the config.
279
+ * @param {Object} obj - The object to validate
280
+ * @throws {Error} If validation fails
281
+ */
282
+ static validateJSON(obj) {
283
+ if (!obj || typeof obj !== "object") {
284
+ throw new Error("LayerConfig: must be an object");
285
+ }
286
+ if (!obj.id || typeof obj.id !== "string") {
287
+ throw new Error("LayerConfig: id must be a non-empty string");
288
+ }
289
+ if (obj.id.includes("/")) {
290
+ throw new Error(`LayerConfig: id cannot contain slashes: "${obj.id}"`);
291
+ }
292
+ const id = obj.id;
293
+ if (obj.lineWidthStops !== void 0) {
294
+ if (!obj.lineWidthStops || typeof obj.lineWidthStops !== "object" || Array.isArray(obj.lineWidthStops)) {
295
+ throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
296
+ }
297
+ const stopKeys = Object.keys(obj.lineWidthStops);
298
+ if (stopKeys.length < 2) {
299
+ throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
300
+ }
301
+ for (const key of stopKeys) {
302
+ const zoom = Number(key);
303
+ if (!Number.isInteger(zoom) || zoom < 0) {
304
+ throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
305
+ }
306
+ if (typeof obj.lineWidthStops[key] !== "number" || obj.lineWidthStops[key] <= 0) {
307
+ throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
308
+ }
309
+ }
310
+ }
311
+ if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
312
+ throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
313
+ }
314
+ for (let i = 0; i < obj.lineStyles.length; i++) {
315
+ LineStyle.validateJSON(obj.lineStyles[i], i);
316
+ }
317
+ }
156
318
  constructor({
157
319
  id,
158
- startZoom = 0,
159
- zoomThreshold = 5,
160
320
  // Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
161
321
  tileUrlTemplates = [],
162
322
  // Line width stops: map of zoom level to line width (at least 2 entries)
163
323
  // Note: interpolated/extrapolated line width is capped at a minimum of 0.5
164
324
  lineWidthStops = { 1: 0.5, 10: 2.5 },
165
- // Line styles array - each element describes a line to draw
166
- // { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }
167
- // Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity
168
- lineStyles = [{ color: "green", widthFraction: 1 }],
169
- // Factor to multiply line width for deletion blur (default 1.5)
170
- // Higher values leave gaps where wiped lines meet existing lines
171
- // Lower values mean wiped lines show through
172
- delWidthFactor = 1.5,
173
- // Factor to extend add lines by (multiplied by deletion line width)
174
- // Helps cover gaps where deleted lines meet the new boundary
175
- // Set to 0 to disable extension
176
- lineExtensionFactor = 0.5
325
+ // Line styles array - each element describes a line to draw from a specific layer
326
+ // { color: string, layerSuffix: string, widthFraction?: number, dashArray?: number[],
327
+ // startZoom?: number, endZoom?: number, lineExtensionFactor?: number, delWidthFactor?: number }
328
+ // Lines are drawn in array order. layerSuffix determines which PMTiles layer to use.
329
+ lineStyles
177
330
  }) {
178
- if (!id || typeof id !== "string") {
179
- throw new Error("LayerConfig requires a non-empty string id");
180
- }
181
- if (id.includes("/")) {
182
- throw new Error(`LayerConfig id cannot contain slashes: "${id}"`);
183
- }
184
331
  this.id = id;
185
- this.startZoom = startZoom;
186
- this.zoomThreshold = zoomThreshold;
187
- if (startZoom > zoomThreshold) {
188
- throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
189
- }
190
332
  const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : tileUrlTemplates ? [tileUrlTemplates] : [];
191
333
  this.tileUrlTemplates = templates;
192
334
  this._compiledPatterns = templates.map((t) => templateToRegex(t));
193
335
  this._templatePatterns = templates.map((t) => templateToTemplateRegex(t));
194
- if (!lineWidthStops || typeof lineWidthStops !== "object" || Array.isArray(lineWidthStops)) {
195
- throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
196
- }
197
- const stopKeys = Object.keys(lineWidthStops);
198
- if (stopKeys.length < 2) {
199
- throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
200
- }
201
- for (const key of stopKeys) {
202
- const zoom = Number(key);
203
- if (!Number.isInteger(zoom) || zoom < 0) {
204
- throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
205
- }
206
- if (typeof lineWidthStops[key] !== "number" || lineWidthStops[key] <= 0) {
207
- throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
208
- }
209
- }
210
336
  this.lineWidthStops = lineWidthStops;
211
- if (!Array.isArray(lineStyles) || lineStyles.length === 0) {
212
- throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
213
- }
214
- for (let i = 0; i < lineStyles.length; i++) {
215
- const style = lineStyles[i];
216
- if (!style || typeof style !== "object") {
217
- throw new Error(`LayerConfig "${id}": lineStyles[${i}] must be an object`);
218
- }
219
- if (!style.color || typeof style.color !== "string") {
220
- throw new Error(`LayerConfig "${id}": lineStyles[${i}].color must be a non-empty string`);
221
- }
222
- }
223
- this.lineStyles = lineStyles.map((style) => ({
224
- ...style,
225
- startZoom: style.startZoom ?? startZoom,
226
- endZoom: style.endZoom ?? Infinity
227
- }));
228
- this.delWidthFactor = delWidthFactor;
229
- this.lineExtensionFactor = lineExtensionFactor;
337
+ this.lineStyles = lineStyles.map(
338
+ (style) => style instanceof LineStyle ? style : new LineStyle(style)
339
+ );
230
340
  }
231
341
  /**
232
342
  * Get line styles active at a given zoom level
233
343
  * @param {number} z - Zoom level
234
- * @returns {Array<{color: string, widthFraction?: number, dashArray?: number[]}>}
344
+ * @returns {LineStyle[]}
235
345
  */
236
346
  getLineStylesForZoom(z) {
237
- return this.lineStyles.filter((style) => z >= style.startZoom && z <= style.endZoom);
347
+ return this.lineStyles.filter((style) => style.isActiveAtZoom(z));
348
+ }
349
+ /**
350
+ * Get unique layer suffixes from styles active at a given zoom level
351
+ * @param {number} z - Zoom level
352
+ * @returns {string[]}
353
+ */
354
+ getLayerSuffixesForZoom(z) {
355
+ const activeStyles = this.getLineStylesForZoom(z);
356
+ return [...new Set(activeStyles.map((s) => s.layerSuffix))];
238
357
  }
239
358
  /**
240
359
  * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
@@ -293,21 +412,19 @@ var IndiaBoundaryCorrector = (() => {
293
412
  toJSON() {
294
413
  return {
295
414
  id: this.id,
296
- startZoom: this.startZoom,
297
- zoomThreshold: this.zoomThreshold,
298
415
  tileUrlTemplates: this.tileUrlTemplates,
299
416
  lineWidthStops: this.lineWidthStops,
300
- lineStyles: this.lineStyles,
301
- delWidthFactor: this.delWidthFactor,
302
- lineExtensionFactor: this.lineExtensionFactor
417
+ lineStyles: this.lineStyles.map((s) => s.toJSON())
303
418
  };
304
419
  }
305
420
  /**
306
- * Create a LayerConfig from a plain object (e.g., from postMessage)
421
+ * Create a LayerConfig from a plain object with validation.
307
422
  * @param {Object} obj
308
423
  * @returns {LayerConfig}
424
+ * @throws {Error} If validation fails
309
425
  */
310
426
  static fromJSON(obj) {
427
+ _LayerConfig.validateJSON(obj);
311
428
  return new _LayerConfig(obj);
312
429
  }
313
430
  };
@@ -403,13 +520,13 @@ var IndiaBoundaryCorrector = (() => {
403
520
  }
404
521
 
405
522
  // ../data/version.js
406
- var packageVersion = "0.0.4";
523
+ var packageVersion = "0.1.0";
407
524
 
408
525
  // ../data/index.js
409
526
  var import_meta = {};
410
527
  var PACKAGE_NAME = "@india-boundary-corrector/data";
411
- var PMTILES_FILENAME = "india_boundary_corrections.pmtiles";
412
- var FALLBACK_CDNS = /* @__PURE__ */ new Set(["esm.sh", "skypack.dev", "cdn.skypack.dev", "unpkg.com"]);
528
+ var PMTILES_FILENAME = "india_boundary_corrections.pmtiles.gz";
529
+ var FALLBACK_CDNS = /* @__PURE__ */ new Set(["esm.sh", "skypack.dev", "cdn.skypack.dev"]);
413
530
  var DEFAULT_CDN_URL = `https://cdn.jsdelivr.net/npm/${PACKAGE_NAME}@${packageVersion}/${PMTILES_FILENAME}`;
414
531
  var CURRENT_SCRIPT_URL = typeof document !== "undefined" && document.currentScript && document.currentScript.src || null;
415
532
  function detectPmtilesUrl() {
@@ -424,14 +541,17 @@ var IndiaBoundaryCorrector = (() => {
424
541
  scriptUrl = CURRENT_SCRIPT_URL;
425
542
  }
426
543
  if (scriptUrl) {
427
- const moduleUrl = new URL(".", scriptUrl);
428
- if (FALLBACK_CDNS.has(moduleUrl.hostname)) {
429
- return DEFAULT_CDN_URL;
430
- }
431
- return new URL(PMTILES_FILENAME, moduleUrl).href;
544
+ return resolvePmtilesUrl(scriptUrl);
432
545
  }
433
546
  return DEFAULT_CDN_URL;
434
547
  }
548
+ function resolvePmtilesUrl(scriptUrl) {
549
+ const moduleUrl = new URL(".", scriptUrl);
550
+ if (FALLBACK_CDNS.has(moduleUrl.hostname)) {
551
+ return DEFAULT_CDN_URL;
552
+ }
553
+ return new URL(PMTILES_FILENAME, moduleUrl).href;
554
+ }
435
555
  var cachedPmtilesUrl = null;
436
556
  function getPmtilesUrl() {
437
557
  if (cachedPmtilesUrl === null) {