@india-boundary-corrector/layer-configs 0.0.3 → 0.0.5
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 +1 -1
- package/dist/index.cjs +176 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +174 -46
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +3 -0
- package/src/index.js +5 -2
- package/src/layerconfig.js +217 -52
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ layerConfigs.remove('my-custom-style');
|
|
|
70
70
|
| `startZoom` | number | 0 | Minimum zoom level to start rendering corrections |
|
|
71
71
|
| `zoomThreshold` | number | 5 | Zoom level to switch between NE and OSM data |
|
|
72
72
|
| `tileUrlTemplates` | string \| string[] | [] | URL templates for matching tiles (e.g., `https://{s}.tile.example.com/{z}/{x}/{y}.png`) |
|
|
73
|
-
| `lineWidthStops` | object | { 1: 0.5, 10: 2.5 } | Zoom-to-width interpolation map |
|
|
73
|
+
| `lineWidthStops` | object | { 1: 0.5, 10: 2.5 } | Zoom-to-width interpolation map. Interpolated/extrapolated values are capped at a minimum of 0.5. |
|
|
74
74
|
| `lineStyles` | array | [{ color: 'green' }] | Array of line styles to draw |
|
|
75
75
|
| `delWidthFactor` | number | 1.5 | Multiplier for deletion line width |
|
|
76
76
|
| `lineExtensionFactor` | number | 0.5 | Factor to extend add lines by (multiplied by deletion line width). Helps cover gaps where deleted lines meet the new boundary. Set to 0 to disable. |
|
package/dist/index.cjs
CHANGED
|
@@ -22,6 +22,8 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
LayerConfig: () => LayerConfig,
|
|
24
24
|
LayerConfigRegistry: () => LayerConfigRegistry,
|
|
25
|
+
LineStyle: () => LineStyle,
|
|
26
|
+
configsJson: () => configs_default,
|
|
25
27
|
layerConfigs: () => layerConfigs
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -151,7 +153,166 @@ function templateToTemplateRegex(template) {
|
|
|
151
153
|
});
|
|
152
154
|
return new RegExp("^" + pattern + "(\\?.*)?$", "i");
|
|
153
155
|
}
|
|
156
|
+
function isValidColor(color) {
|
|
157
|
+
if (typeof color !== "string" || !color.trim()) return false;
|
|
158
|
+
if (typeof CSS !== "undefined" && CSS.supports) {
|
|
159
|
+
return CSS.supports("color", color);
|
|
160
|
+
}
|
|
161
|
+
const trimmed = color.trim().toLowerCase();
|
|
162
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;
|
|
163
|
+
if (/^(rgb|hsl)a?\(/.test(trimmed)) return true;
|
|
164
|
+
if (/^[a-z]+$/.test(trimmed)) return true;
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
var LineStyle = class _LineStyle {
|
|
168
|
+
/**
|
|
169
|
+
* Validate a LineStyle configuration object.
|
|
170
|
+
* @param {Object} obj - The object to validate
|
|
171
|
+
* @param {number} [index] - Optional index for error messages (when validating in an array)
|
|
172
|
+
* @throws {Error} If validation fails
|
|
173
|
+
*/
|
|
174
|
+
static validateJSON(obj, index) {
|
|
175
|
+
const prefix = index !== void 0 ? `lineStyles[${index}]` : "LineStyle";
|
|
176
|
+
if (!obj || typeof obj !== "object") {
|
|
177
|
+
throw new Error(`${prefix}: must be an object`);
|
|
178
|
+
}
|
|
179
|
+
if (!obj.color || typeof obj.color !== "string") {
|
|
180
|
+
throw new Error(`${prefix}: color must be a non-empty string`);
|
|
181
|
+
}
|
|
182
|
+
if (!isValidColor(obj.color)) {
|
|
183
|
+
throw new Error(`${prefix}: color "${obj.color}" is not a valid CSS color`);
|
|
184
|
+
}
|
|
185
|
+
if (obj.widthFraction !== void 0 && (typeof obj.widthFraction !== "number" || obj.widthFraction <= 0)) {
|
|
186
|
+
throw new Error(`${prefix}: widthFraction must be a positive number`);
|
|
187
|
+
}
|
|
188
|
+
if (obj.dashArray !== void 0 && !Array.isArray(obj.dashArray)) {
|
|
189
|
+
throw new Error(`${prefix}: dashArray must be an array`);
|
|
190
|
+
}
|
|
191
|
+
if (obj.alpha !== void 0 && (typeof obj.alpha !== "number" || obj.alpha < 0 || obj.alpha > 1)) {
|
|
192
|
+
throw new Error(`${prefix}: alpha must be a number between 0 and 1`);
|
|
193
|
+
}
|
|
194
|
+
if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
|
|
195
|
+
throw new Error(`${prefix}: startZoom must be a non-negative number`);
|
|
196
|
+
}
|
|
197
|
+
if (obj.endZoom !== void 0 && (typeof obj.endZoom !== "number" || obj.endZoom < 0)) {
|
|
198
|
+
throw new Error(`${prefix}: endZoom must be a non-negative number`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* @param {Object} options
|
|
203
|
+
* @param {string} options.color - CSS color string
|
|
204
|
+
* @param {number} [options.widthFraction=1.0] - Multiplier for base line width
|
|
205
|
+
* @param {number[]} [options.dashArray] - Dash pattern for dashed lines
|
|
206
|
+
* @param {number} [options.alpha=1.0] - Opacity (0-1)
|
|
207
|
+
* @param {number} [options.startZoom] - Minimum zoom level for this style
|
|
208
|
+
* @param {number} [options.endZoom=Infinity] - Maximum zoom level for this style
|
|
209
|
+
*/
|
|
210
|
+
constructor({ color, widthFraction = 1, dashArray, alpha = 1, startZoom, endZoom = Infinity }) {
|
|
211
|
+
this.color = color;
|
|
212
|
+
this.widthFraction = widthFraction;
|
|
213
|
+
this.dashArray = dashArray;
|
|
214
|
+
this.alpha = alpha;
|
|
215
|
+
this.startZoom = startZoom;
|
|
216
|
+
this.endZoom = endZoom;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Check if this style is active at the given zoom level.
|
|
220
|
+
* @param {number} z - Zoom level
|
|
221
|
+
* @returns {boolean}
|
|
222
|
+
*/
|
|
223
|
+
isActiveAtZoom(z) {
|
|
224
|
+
return z >= this.startZoom && z <= this.endZoom;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Serialize to plain object.
|
|
228
|
+
* @returns {Object}
|
|
229
|
+
*/
|
|
230
|
+
toJSON() {
|
|
231
|
+
const obj = { color: this.color };
|
|
232
|
+
if (this.widthFraction !== 1) obj.widthFraction = this.widthFraction;
|
|
233
|
+
if (this.dashArray) obj.dashArray = this.dashArray;
|
|
234
|
+
if (this.alpha !== 1) obj.alpha = this.alpha;
|
|
235
|
+
if (this.startZoom !== void 0) obj.startZoom = this.startZoom;
|
|
236
|
+
if (this.endZoom !== Infinity) obj.endZoom = this.endZoom;
|
|
237
|
+
return obj;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Create from plain object with validation.
|
|
241
|
+
* @param {Object} obj
|
|
242
|
+
* @param {number} [defaultStartZoom=0] - Default startZoom if not specified
|
|
243
|
+
* @param {number} [index] - Optional index for error messages
|
|
244
|
+
* @returns {LineStyle}
|
|
245
|
+
*/
|
|
246
|
+
static fromJSON(obj, defaultStartZoom = 0, index) {
|
|
247
|
+
_LineStyle.validateJSON(obj, index);
|
|
248
|
+
return new _LineStyle({
|
|
249
|
+
...obj,
|
|
250
|
+
startZoom: obj.startZoom ?? defaultStartZoom
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
};
|
|
154
254
|
var LayerConfig = class _LayerConfig {
|
|
255
|
+
/**
|
|
256
|
+
* Validate a LayerConfig configuration object.
|
|
257
|
+
* Also validates all lineStyles within the config.
|
|
258
|
+
* @param {Object} obj - The object to validate
|
|
259
|
+
* @throws {Error} If validation fails
|
|
260
|
+
*/
|
|
261
|
+
static validateJSON(obj) {
|
|
262
|
+
if (!obj || typeof obj !== "object") {
|
|
263
|
+
throw new Error("LayerConfig: must be an object");
|
|
264
|
+
}
|
|
265
|
+
if (!obj.id || typeof obj.id !== "string") {
|
|
266
|
+
throw new Error("LayerConfig: id must be a non-empty string");
|
|
267
|
+
}
|
|
268
|
+
if (obj.id.includes("/")) {
|
|
269
|
+
throw new Error(`LayerConfig: id cannot contain slashes: "${obj.id}"`);
|
|
270
|
+
}
|
|
271
|
+
const id = obj.id;
|
|
272
|
+
if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
|
|
273
|
+
throw new Error(`LayerConfig "${id}": startZoom must be a non-negative number`);
|
|
274
|
+
}
|
|
275
|
+
if (obj.zoomThreshold !== void 0 && (typeof obj.zoomThreshold !== "number" || obj.zoomThreshold < 0)) {
|
|
276
|
+
throw new Error(`LayerConfig "${id}": zoomThreshold must be a non-negative number`);
|
|
277
|
+
}
|
|
278
|
+
const startZoom = obj.startZoom ?? 0;
|
|
279
|
+
const zoomThreshold = obj.zoomThreshold ?? 5;
|
|
280
|
+
if (startZoom > zoomThreshold) {
|
|
281
|
+
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
282
|
+
}
|
|
283
|
+
if (obj.lineWidthStops !== void 0) {
|
|
284
|
+
if (!obj.lineWidthStops || typeof obj.lineWidthStops !== "object" || Array.isArray(obj.lineWidthStops)) {
|
|
285
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
286
|
+
}
|
|
287
|
+
const stopKeys = Object.keys(obj.lineWidthStops);
|
|
288
|
+
if (stopKeys.length < 2) {
|
|
289
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
290
|
+
}
|
|
291
|
+
for (const key of stopKeys) {
|
|
292
|
+
const zoom = Number(key);
|
|
293
|
+
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
294
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
295
|
+
}
|
|
296
|
+
if (typeof obj.lineWidthStops[key] !== "number" || obj.lineWidthStops[key] <= 0) {
|
|
297
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (obj.lineStyles !== void 0) {
|
|
302
|
+
if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
|
|
303
|
+
throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
|
|
304
|
+
}
|
|
305
|
+
for (let i = 0; i < obj.lineStyles.length; i++) {
|
|
306
|
+
LineStyle.validateJSON(obj.lineStyles[i], i);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor <= 0)) {
|
|
310
|
+
throw new Error(`LayerConfig "${id}": delWidthFactor must be a positive number`);
|
|
311
|
+
}
|
|
312
|
+
if (obj.lineExtensionFactor !== void 0 && (typeof obj.lineExtensionFactor !== "number" || obj.lineExtensionFactor < 0)) {
|
|
313
|
+
throw new Error(`LayerConfig "${id}": lineExtensionFactor must be a non-negative number`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
155
316
|
constructor({
|
|
156
317
|
id,
|
|
157
318
|
startZoom = 0,
|
|
@@ -159,6 +320,7 @@ var LayerConfig = class _LayerConfig {
|
|
|
159
320
|
// Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
|
|
160
321
|
tileUrlTemplates = [],
|
|
161
322
|
// Line width stops: map of zoom level to line width (at least 2 entries)
|
|
323
|
+
// Note: interpolated/extrapolated line width is capped at a minimum of 0.5
|
|
162
324
|
lineWidthStops = { 1: 0.5, 10: 2.5 },
|
|
163
325
|
// Line styles array - each element describes a line to draw
|
|
164
326
|
// { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }
|
|
@@ -173,66 +335,30 @@ var LayerConfig = class _LayerConfig {
|
|
|
173
335
|
// Set to 0 to disable extension
|
|
174
336
|
lineExtensionFactor = 0.5
|
|
175
337
|
}) {
|
|
176
|
-
if (!id || typeof id !== "string") {
|
|
177
|
-
throw new Error("LayerConfig requires a non-empty string id");
|
|
178
|
-
}
|
|
179
|
-
if (id.includes("/")) {
|
|
180
|
-
throw new Error(`LayerConfig id cannot contain slashes: "${id}"`);
|
|
181
|
-
}
|
|
182
338
|
this.id = id;
|
|
183
339
|
this.startZoom = startZoom;
|
|
184
340
|
this.zoomThreshold = zoomThreshold;
|
|
185
|
-
if (startZoom > zoomThreshold) {
|
|
186
|
-
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
187
|
-
}
|
|
188
341
|
const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : tileUrlTemplates ? [tileUrlTemplates] : [];
|
|
189
342
|
this.tileUrlTemplates = templates;
|
|
190
343
|
this._compiledPatterns = templates.map((t) => templateToRegex(t));
|
|
191
344
|
this._templatePatterns = templates.map((t) => templateToTemplateRegex(t));
|
|
192
|
-
if (!lineWidthStops || typeof lineWidthStops !== "object" || Array.isArray(lineWidthStops)) {
|
|
193
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
194
|
-
}
|
|
195
|
-
const stopKeys = Object.keys(lineWidthStops);
|
|
196
|
-
if (stopKeys.length < 2) {
|
|
197
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
198
|
-
}
|
|
199
|
-
for (const key of stopKeys) {
|
|
200
|
-
const zoom = Number(key);
|
|
201
|
-
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
202
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
203
|
-
}
|
|
204
|
-
if (typeof lineWidthStops[key] !== "number" || lineWidthStops[key] <= 0) {
|
|
205
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
345
|
this.lineWidthStops = lineWidthStops;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
throw new Error(`LayerConfig "${id}": lineStyles[${i}] must be an object`);
|
|
216
|
-
}
|
|
217
|
-
if (!style.color || typeof style.color !== "string") {
|
|
218
|
-
throw new Error(`LayerConfig "${id}": lineStyles[${i}].color must be a non-empty string`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
this.lineStyles = lineStyles.map((style) => ({
|
|
222
|
-
...style,
|
|
223
|
-
startZoom: style.startZoom ?? startZoom,
|
|
224
|
-
endZoom: style.endZoom ?? Infinity
|
|
225
|
-
}));
|
|
346
|
+
this.lineStyles = lineStyles.map(
|
|
347
|
+
(style) => style instanceof LineStyle ? style : new LineStyle({
|
|
348
|
+
...style,
|
|
349
|
+
startZoom: style.startZoom ?? startZoom
|
|
350
|
+
})
|
|
351
|
+
);
|
|
226
352
|
this.delWidthFactor = delWidthFactor;
|
|
227
353
|
this.lineExtensionFactor = lineExtensionFactor;
|
|
228
354
|
}
|
|
229
355
|
/**
|
|
230
356
|
* Get line styles active at a given zoom level
|
|
231
357
|
* @param {number} z - Zoom level
|
|
232
|
-
* @returns {
|
|
358
|
+
* @returns {LineStyle[]}
|
|
233
359
|
*/
|
|
234
360
|
getLineStylesForZoom(z) {
|
|
235
|
-
return this.lineStyles.filter((style) =>
|
|
361
|
+
return this.lineStyles.filter((style) => style.isActiveAtZoom(z));
|
|
236
362
|
}
|
|
237
363
|
/**
|
|
238
364
|
* Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
|
|
@@ -295,17 +421,19 @@ var LayerConfig = class _LayerConfig {
|
|
|
295
421
|
zoomThreshold: this.zoomThreshold,
|
|
296
422
|
tileUrlTemplates: this.tileUrlTemplates,
|
|
297
423
|
lineWidthStops: this.lineWidthStops,
|
|
298
|
-
lineStyles: this.lineStyles,
|
|
424
|
+
lineStyles: this.lineStyles.map((s) => s.toJSON()),
|
|
299
425
|
delWidthFactor: this.delWidthFactor,
|
|
300
426
|
lineExtensionFactor: this.lineExtensionFactor
|
|
301
427
|
};
|
|
302
428
|
}
|
|
303
429
|
/**
|
|
304
|
-
* Create a LayerConfig from a plain object
|
|
430
|
+
* Create a LayerConfig from a plain object with validation.
|
|
305
431
|
* @param {Object} obj
|
|
306
432
|
* @returns {LayerConfig}
|
|
433
|
+
* @throws {Error} If validation fails
|
|
307
434
|
*/
|
|
308
435
|
static fromJSON(obj) {
|
|
436
|
+
_LayerConfig.validateJSON(obj);
|
|
309
437
|
return new _LayerConfig(obj);
|
|
310
438
|
}
|
|
311
439
|
};
|
|
@@ -405,6 +533,8 @@ for (const configData of configs_default) {
|
|
|
405
533
|
0 && (module.exports = {
|
|
406
534
|
LayerConfig,
|
|
407
535
|
LayerConfigRegistry,
|
|
536
|
+
LineStyle,
|
|
537
|
+
configsJson,
|
|
408
538
|
layerConfigs
|
|
409
539
|
});
|
|
410
540
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.js","../src/configs.json","../src/layerconfig.js"],"sourcesContent":["import configsJson from './configs.json' with { type: 'json' };\nimport { LayerConfig } from './layerconfig.js';\n\nexport { LayerConfig } from './layerconfig.js';\n\n/**\n * Layer configuration registry\n */\nexport class LayerConfigRegistry {\n constructor() {\n this.registry = {};\n }\n\n /**\n * Get a layer config by id\n */\n get(id) {\n return this.registry[id];\n }\n\n /**\n * Register a new layer config\n */\n register(config) {\n this.registry[config.id] = config;\n }\n\n /**\n * Remove a layer config by id\n */\n remove(id) {\n if (!this.registry[id]) return false;\n delete this.registry[id];\n return true;\n }\n\n /**\n * Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n */\n detectFromTemplates(templates) {\n if (!templates || (Array.isArray(templates) && templates.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTemplate(templates)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Detect layer config from actual tile URLs (with numeric coordinates)\n * @param {string | string[]} urls - Single tile URL or array of tile URLs\n */\n detectFromTileUrls(urls) {\n if (!urls || (Array.isArray(urls) && urls.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTileUrl(urls)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Get all available layer config ids\n */\n getAvailableIds() {\n return Object.keys(this.registry);\n }\n\n /**\n * Create a new registry with all configs from this registry plus extra configs.\n * @param {LayerConfig[]} extraLayerConfigs - Additional configs to add\n * @returns {LayerConfigRegistry} A new registry with merged configs\n */\n createMergedRegistry(extraLayerConfigs) {\n const registry = new LayerConfigRegistry();\n \n for (const id of this.getAvailableIds()) {\n registry.register(this.get(id));\n }\n \n if (extraLayerConfigs && extraLayerConfigs.length > 0) {\n for (const config of extraLayerConfigs) {\n registry.register(config);\n }\n }\n \n return registry;\n }\n\n /**\n * Parse a tile URL into its components: layer config and coordinates\n * @param {string} url - Tile URL to parse\n * @returns {{ layerConfig: LayerConfig, coords: { z: number, x: number, y: number } } | null}\n */\n parseTileUrl(url) {\n // Check if URL matches any layer config\n const layerConfig = this.detectFromTileUrls([url]);\n if (!layerConfig) return null;\n \n // Extract tile coordinates using the matched config\n const coords = layerConfig.extractCoords(url);\n if (!coords) return null;\n \n return { layerConfig, coords };\n }\n}\n\n// Default registry with built-in configs loaded from JSON\nexport const layerConfigs = new LayerConfigRegistry();\nfor (const configData of configsJson) {\n layerConfigs.register(new LayerConfig(configData));\n}\n\n","[\n {\n \"id\": \"cartodb-dark\",\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"10\": 2.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(40, 40, 40)\" }\n ]\n },\n {\n \"id\": \"cartodb-light\",\n \"startZoom\": 0,\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.25, \"2\": 0.25, \"3\": 0.5, \"4\": 0.75, \"5\": 1.0 },\n \"lineStyles\": [\n { \"color\": \"rgb(235, 214, 214)\", \"alpha\": 0.2, \"startZoom\": 6, \"widthFraction\": 5 },\n { \"color\": \"rgb(235, 214, 214)\" }\n ]\n },\n {\n \"id\": \"open-topo\",\n \"startZoom\": 4,\n \"zoomThreshold\": 4,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png\",\n \"https://tile.opentopomap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"4\": 0.75, \"5\": 1.0, \"6\": 1.25, \"7\": 1.5, \"8\": 1.75, \"9\": 1.25, \"10\": 1.25, \"13\": 1.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(83, 83, 83)\", \"startZoom\": 7, \"endZoom\": 8, \"alpha\": 0.4, \"widthFraction\": 4 },\n { \"color\": \"rgb(83, 83, 83)\", \"endZoom\": 8 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9, \"widthFraction\": 9, \"alpha\": 0.2 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9 }\n ]\n },\n {\n \"id\": \"osm-carto\",\n \"startZoom\": 1,\n \"zoomThreshold\": 1,\n \"tileUrlTemplates\": [\n \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"2\": 0.6, \"3\": 0.7, \"4\": 1.0, \"10\": 3.75 },\n \"lineStyles\": [\n { \"color\": \"rgb(200, 180, 200)\" },\n { \"color\": \"rgb(160, 120, 160)\", \"widthFraction\": 0.333, \"dashArray\": [30, 2, 8, 2] }\n ]\n },\n {\n \"id\": \"osm-hot\",\n \"startZoom\": 2,\n \"zoomThreshold\": 2,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"2\": 3.0, \"3\": 3.0, \"7\": 3.0, \"8\": 3.5, \"9\": 3.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(149, 175, 180)\" },\n { \"color\": \"rgb(89, 117, 123)\", \"widthFraction\": 0.33 }\n ]\n }\n]\n","/**\n * Convert a tile URL template to a regex pattern and capture group names.\n * Supports {z}, {x}, {y}, {s} (Leaflet subdomain), {a-c}/{1-4} (OpenLayers subdomain), and {r} (retina) placeholders.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {{ pattern: RegExp, groups: string[] }}\n */\nfunction templateToRegex(template) {\n const groups = [];\n // Escape regex special chars, then replace placeholders\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n // Don't escape our placeholders\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} etc (OpenLayers style subdomain)\n .replace(/\\{[a-z0-9]-[a-z0-9]\\}/gi, () => {\n groups.push('s');\n return '([a-z0-9]+)';\n })\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n groups.push(lowerName);\n if (lowerName === 's') {\n // Subdomain: single letter or short string\n return '([a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Retina suffix: optional @2x or similar\n return '(@\\\\d+x)?';\n }\n // z, x, y: numeric\n return '(\\\\d+)';\n });\n \n // Allow optional query string at end\n return { pattern: new RegExp('^' + pattern + '(\\\\?.*)?$', 'i'), groups };\n}\n\n/**\n * Convert a tile URL template to a regex that matches the template itself.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {RegExp}\n */\nfunction templateToTemplateRegex(template) {\n // Escape regex special chars, then replace placeholders with literal match\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} (OpenLayers style subdomain)\n .replace(/\\{([a-z0-9])-([a-z0-9])\\}/gi, (_, start, end) => `(\\\\{${start}-${end}\\\\}|\\\\{s\\\\}|[a-z0-9]+)`)\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n if (lowerName === 's') {\n // Match {s} placeholder, {a-c} style placeholder, or actual subdomain\n return '(\\\\{s\\\\}|\\\\{[a-z0-9]-[a-z0-9]\\\\}|[a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Match {r} placeholder or actual retina or empty\n return '(\\\\{r\\\\}|@\\\\d+x)?';\n }\n // Match {z}, {x}, {y} placeholders\n return `\\\\{${lowerName}\\\\}`;\n });\n \n // Allow optional query string at end\n return new RegExp('^' + pattern + '(\\\\?.*)?$', 'i');\n}\n\n/**\n * Base class for layer configurations\n * \n * Supports separate styling for NE (Natural Earth) data at low zoom levels\n * and OSM data at higher zoom levels, split by zoomThreshold.\n */\nexport class LayerConfig {\n constructor({\n id,\n startZoom = 0,\n zoomThreshold = 5,\n // Tile URL templates for matching (e.g., \"https://{s}.tile.example.com/{z}/{x}/{y}.png\")\n tileUrlTemplates = [],\n // Line width stops: map of zoom level to line width (at least 2 entries)\n lineWidthStops = { 1: 0.5, 10: 2.5 },\n // Line styles array - each element describes a line to draw\n // { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }\n // Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity\n lineStyles = [{ color: 'green', widthFraction: 1.0 }],\n // Factor to multiply line width for deletion blur (default 1.5)\n // Higher values leave gaps where wiped lines meet existing lines\n // Lower values mean wiped lines show through\n delWidthFactor = 1.5,\n // Factor to extend add lines by (multiplied by deletion line width)\n // Helps cover gaps where deleted lines meet the new boundary\n // Set to 0 to disable extension\n lineExtensionFactor = 0.5,\n }) {\n if (!id || typeof id !== 'string') {\n throw new Error('LayerConfig requires a non-empty string id');\n }\n if (id.includes('/')) {\n throw new Error(`LayerConfig id cannot contain slashes: \"${id}\"`);\n }\n\n this.id = id;\n this.startZoom = startZoom;\n this.zoomThreshold = zoomThreshold;\n\n if (startZoom > zoomThreshold) {\n throw new Error(`LayerConfig \"${id}\": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);\n }\n\n // Normalize to array\n const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : \n (tileUrlTemplates ? [tileUrlTemplates] : []);\n this.tileUrlTemplates = templates;\n \n // Pre-compile regex patterns for matching tile URLs (with actual coords)\n this._compiledPatterns = templates.map(t => templateToRegex(t));\n \n // Pre-compile regex patterns for matching template URLs (with {z}/{x}/{y} placeholders)\n this._templatePatterns = templates.map(t => templateToTemplateRegex(t));\n\n // Validate lineWidthStops\n if (!lineWidthStops || typeof lineWidthStops !== 'object' || Array.isArray(lineWidthStops)) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must be an object`);\n }\n const stopKeys = Object.keys(lineWidthStops);\n if (stopKeys.length < 2) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must have at least 2 entries`);\n }\n for (const key of stopKeys) {\n const zoom = Number(key);\n if (!Number.isInteger(zoom) || zoom < 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops keys must be non-negative integers, got \"${key}\"`);\n }\n if (typeof lineWidthStops[key] !== 'number' || lineWidthStops[key] <= 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops values must be positive numbers`);\n }\n }\n this.lineWidthStops = lineWidthStops;\n\n // Validate lineStyles\n if (!Array.isArray(lineStyles) || lineStyles.length === 0) {\n throw new Error(`LayerConfig \"${id}\": lineStyles must be a non-empty array`);\n }\n for (let i = 0; i < lineStyles.length; i++) {\n const style = lineStyles[i];\n if (!style || typeof style !== 'object') {\n throw new Error(`LayerConfig \"${id}\": lineStyles[${i}] must be an object`);\n }\n if (!style.color || typeof style.color !== 'string') {\n throw new Error(`LayerConfig \"${id}\": lineStyles[${i}].color must be a non-empty string`);\n }\n }\n \n // Line styles - normalize startZoom/endZoom defaults\n this.lineStyles = lineStyles.map(style => ({\n ...style,\n startZoom: style.startZoom ?? startZoom,\n endZoom: style.endZoom ?? Infinity,\n }));\n \n // Deletion width factor\n this.delWidthFactor = delWidthFactor;\n \n // Line extension factor\n this.lineExtensionFactor = lineExtensionFactor;\n }\n\n /**\n * Get line styles active at a given zoom level\n * @param {number} z - Zoom level\n * @returns {Array<{color: string, widthFraction?: number, dashArray?: number[]}>}\n */\n getLineStylesForZoom(z) {\n return this.lineStyles.filter(style => z >= style.startZoom && z <= style.endZoom);\n }\n\n /**\n * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n * @returns {boolean}\n */\n matchTemplate(templates) {\n if (this._templatePatterns.length === 0) return false;\n \n const urls = Array.isArray(templates) ? templates : [templates];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._templatePatterns.some(pattern => pattern.test(url))\n );\n }\n\n /**\n * Check if this config matches the given tile URLs (with actual coordinates)\n * @param {string | string[]} tiles - Single tile URL or array of tile URLs\n * @returns {boolean}\n */\n matchTileUrl(tiles) {\n if (this._compiledPatterns.length === 0) return false;\n \n const urls = Array.isArray(tiles) ? tiles : [tiles];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._compiledPatterns.some(({ pattern }) => pattern.test(url))\n );\n }\n\n /**\n * Extract tile coordinates (z, x, y) from a URL using this config's templates\n * @param {string} url - Tile URL to extract coordinates from\n * @returns {{ z: number, x: number, y: number } | null}\n */\n extractCoords(url) {\n for (const { pattern, groups } of this._compiledPatterns) {\n const match = url.match(pattern);\n if (match) {\n const result = {};\n for (let i = 0; i < groups.length; i++) {\n const name = groups[i];\n const value = match[i + 1];\n if (name === 'z' || name === 'x' || name === 'y') {\n result[name] = parseInt(value, 10);\n }\n }\n if ('z' in result && 'x' in result && 'y' in result) {\n return { z: result.z, x: result.x, y: result.y };\n }\n }\n }\n return null;\n }\n\n /**\n * Serialize the config to a plain object for postMessage\n * @returns {Object}\n */\n toJSON() {\n return {\n id: this.id,\n startZoom: this.startZoom,\n zoomThreshold: this.zoomThreshold,\n tileUrlTemplates: this.tileUrlTemplates,\n lineWidthStops: this.lineWidthStops,\n lineStyles: this.lineStyles,\n delWidthFactor: this.delWidthFactor,\n lineExtensionFactor: this.lineExtensionFactor,\n };\n }\n\n /**\n * Create a LayerConfig from a plain object (e.g., from postMessage)\n * @param {Object} obj\n * @returns {LayerConfig}\n */\n static fromJSON(obj) {\n return new LayerConfig(obj);\n }\n}\n\nexport default LayerConfig;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.js","../src/configs.json","../src/layerconfig.js"],"sourcesContent":["import configsJson from './configs.json' with { type: 'json' };\nimport { LayerConfig, LineStyle } from './layerconfig.js';\n\nexport { LayerConfig, LineStyle } from './layerconfig.js';\n\n// Export raw configs for testing/inspection\nexport { configsJson };\n\n/**\n * Layer configuration registry\n */\nexport class LayerConfigRegistry {\n constructor() {\n this.registry = {};\n }\n\n /**\n * Get a layer config by id\n */\n get(id) {\n return this.registry[id];\n }\n\n /**\n * Register a new layer config\n */\n register(config) {\n this.registry[config.id] = config;\n }\n\n /**\n * Remove a layer config by id\n */\n remove(id) {\n if (!this.registry[id]) return false;\n delete this.registry[id];\n return true;\n }\n\n /**\n * Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n */\n detectFromTemplates(templates) {\n if (!templates || (Array.isArray(templates) && templates.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTemplate(templates)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Detect layer config from actual tile URLs (with numeric coordinates)\n * @param {string | string[]} urls - Single tile URL or array of tile URLs\n */\n detectFromTileUrls(urls) {\n if (!urls || (Array.isArray(urls) && urls.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTileUrl(urls)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Get all available layer config ids\n */\n getAvailableIds() {\n return Object.keys(this.registry);\n }\n\n /**\n * Create a new registry with all configs from this registry plus extra configs.\n * @param {LayerConfig[]} extraLayerConfigs - Additional configs to add\n * @returns {LayerConfigRegistry} A new registry with merged configs\n */\n createMergedRegistry(extraLayerConfigs) {\n const registry = new LayerConfigRegistry();\n \n for (const id of this.getAvailableIds()) {\n registry.register(this.get(id));\n }\n \n if (extraLayerConfigs && extraLayerConfigs.length > 0) {\n for (const config of extraLayerConfigs) {\n registry.register(config);\n }\n }\n \n return registry;\n }\n\n /**\n * Parse a tile URL into its components: layer config and coordinates\n * @param {string} url - Tile URL to parse\n * @returns {{ layerConfig: LayerConfig, coords: { z: number, x: number, y: number } } | null}\n */\n parseTileUrl(url) {\n // Check if URL matches any layer config\n const layerConfig = this.detectFromTileUrls([url]);\n if (!layerConfig) return null;\n \n // Extract tile coordinates using the matched config\n const coords = layerConfig.extractCoords(url);\n if (!coords) return null;\n \n return { layerConfig, coords };\n }\n}\n\n// Default registry with built-in configs loaded from JSON\nexport const layerConfigs = new LayerConfigRegistry();\nfor (const configData of configsJson) {\n layerConfigs.register(new LayerConfig(configData));\n}\n\n","[\n {\n \"id\": \"cartodb-dark\",\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"10\": 2.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(40, 40, 40)\" }\n ]\n },\n {\n \"id\": \"cartodb-light\",\n \"startZoom\": 0,\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.25, \"2\": 0.25, \"3\": 0.5, \"4\": 0.75, \"5\": 1.0 },\n \"lineStyles\": [\n { \"color\": \"rgb(235, 214, 214)\", \"alpha\": 0.2, \"startZoom\": 6, \"widthFraction\": 5 },\n { \"color\": \"rgb(235, 214, 214)\" }\n ]\n },\n {\n \"id\": \"open-topo\",\n \"startZoom\": 4,\n \"zoomThreshold\": 4,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png\",\n \"https://tile.opentopomap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"4\": 0.75, \"5\": 1.0, \"6\": 1.25, \"7\": 1.5, \"8\": 1.75, \"9\": 1.25, \"10\": 1.25, \"13\": 1.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(83, 83, 83)\", \"startZoom\": 7, \"endZoom\": 8, \"alpha\": 0.4, \"widthFraction\": 4 },\n { \"color\": \"rgb(83, 83, 83)\", \"endZoom\": 8 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9, \"widthFraction\": 9, \"alpha\": 0.2 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9 }\n ]\n },\n {\n \"id\": \"osm-carto\",\n \"startZoom\": 1,\n \"zoomThreshold\": 1,\n \"tileUrlTemplates\": [\n \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"2\": 0.6, \"3\": 0.7, \"4\": 1.0, \"10\": 3.75 },\n \"lineStyles\": [\n { \"color\": \"rgb(200, 180, 200)\" },\n { \"color\": \"rgb(160, 120, 160)\", \"widthFraction\": 0.333, \"dashArray\": [30, 2, 8, 2] }\n ]\n },\n {\n \"id\": \"osm-hot\",\n \"startZoom\": 2,\n \"zoomThreshold\": 2,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"2\": 3.0, \"3\": 3.0, \"7\": 3.0, \"8\": 3.5, \"9\": 3.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(149, 175, 180)\" },\n { \"color\": \"rgb(89, 117, 123)\", \"widthFraction\": 0.33 }\n ]\n }\n]\n","/**\n * Convert a tile URL template to a regex pattern and capture group names.\n * Supports {z}, {x}, {y}, {s} (Leaflet subdomain), {a-c}/{1-4} (OpenLayers subdomain), and {r} (retina) placeholders.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {{ pattern: RegExp, groups: string[] }}\n */\nfunction templateToRegex(template) {\n const groups = [];\n // Escape regex special chars, then replace placeholders\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n // Don't escape our placeholders\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} etc (OpenLayers style subdomain)\n .replace(/\\{[a-z0-9]-[a-z0-9]\\}/gi, () => {\n groups.push('s');\n return '([a-z0-9]+)';\n })\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n groups.push(lowerName);\n if (lowerName === 's') {\n // Subdomain: single letter or short string\n return '([a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Retina suffix: optional @2x or similar\n return '(@\\\\d+x)?';\n }\n // z, x, y: numeric\n return '(\\\\d+)';\n });\n \n // Allow optional query string at end\n return { pattern: new RegExp('^' + pattern + '(\\\\?.*)?$', 'i'), groups };\n}\n\n/**\n * Convert a tile URL template to a regex that matches the template itself.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {RegExp}\n */\nfunction templateToTemplateRegex(template) {\n // Escape regex special chars, then replace placeholders with literal match\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} (OpenLayers style subdomain)\n .replace(/\\{([a-z0-9])-([a-z0-9])\\}/gi, (_, start, end) => `(\\\\{${start}-${end}\\\\}|\\\\{s\\\\}|[a-z0-9]+)`)\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n if (lowerName === 's') {\n // Match {s} placeholder, {a-c} style placeholder, or actual subdomain\n return '(\\\\{s\\\\}|\\\\{[a-z0-9]-[a-z0-9]\\\\}|[a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Match {r} placeholder or actual retina or empty\n return '(\\\\{r\\\\}|@\\\\d+x)?';\n }\n // Match {z}, {x}, {y} placeholders\n return `\\\\{${lowerName}\\\\}`;\n });\n \n // Allow optional query string at end\n return new RegExp('^' + pattern + '(\\\\?.*)?$', 'i');\n}\n\n/**\n * Check if a string is a valid CSS color using the browser's CSS parser.\n * Falls back to a basic regex check in non-browser environments.\n * @param {string} color\n * @returns {boolean}\n */\nfunction isValidColor(color) {\n if (typeof color !== 'string' || !color.trim()) return false;\n \n // Use CSS.supports if available (modern browsers)\n if (typeof CSS !== 'undefined' && CSS.supports) {\n return CSS.supports('color', color);\n }\n \n // Fallback: basic validation for common formats\n const trimmed = color.trim().toLowerCase();\n // Hex colors\n if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;\n // rgb/rgba/hsl/hsla functions\n if (/^(rgb|hsl)a?\\(/.test(trimmed)) return true;\n // Named colors (just check it's alphabetic)\n if (/^[a-z]+$/.test(trimmed)) return true;\n return false;\n}\n\n/**\n * Represents a line style for drawing boundaries.\n */\nexport class LineStyle {\n /**\n * Validate a LineStyle configuration object.\n * @param {Object} obj - The object to validate\n * @param {number} [index] - Optional index for error messages (when validating in an array)\n * @throws {Error} If validation fails\n */\n static validateJSON(obj, index) {\n const prefix = index !== undefined ? `lineStyles[${index}]` : 'LineStyle';\n \n if (!obj || typeof obj !== 'object') {\n throw new Error(`${prefix}: must be an object`);\n }\n \n if (!obj.color || typeof obj.color !== 'string') {\n throw new Error(`${prefix}: color must be a non-empty string`);\n }\n \n if (!isValidColor(obj.color)) {\n throw new Error(`${prefix}: color \"${obj.color}\" is not a valid CSS color`);\n }\n \n if (obj.widthFraction !== undefined && (typeof obj.widthFraction !== 'number' || obj.widthFraction <= 0)) {\n throw new Error(`${prefix}: widthFraction must be a positive number`);\n }\n \n if (obj.dashArray !== undefined && !Array.isArray(obj.dashArray)) {\n throw new Error(`${prefix}: dashArray must be an array`);\n }\n \n if (obj.alpha !== undefined && (typeof obj.alpha !== 'number' || obj.alpha < 0 || obj.alpha > 1)) {\n throw new Error(`${prefix}: alpha must be a number between 0 and 1`);\n }\n \n if (obj.startZoom !== undefined && (typeof obj.startZoom !== 'number' || obj.startZoom < 0)) {\n throw new Error(`${prefix}: startZoom must be a non-negative number`);\n }\n \n if (obj.endZoom !== undefined && (typeof obj.endZoom !== 'number' || obj.endZoom < 0)) {\n throw new Error(`${prefix}: endZoom must be a non-negative number`);\n }\n }\n\n /**\n * @param {Object} options\n * @param {string} options.color - CSS color string\n * @param {number} [options.widthFraction=1.0] - Multiplier for base line width\n * @param {number[]} [options.dashArray] - Dash pattern for dashed lines\n * @param {number} [options.alpha=1.0] - Opacity (0-1)\n * @param {number} [options.startZoom] - Minimum zoom level for this style\n * @param {number} [options.endZoom=Infinity] - Maximum zoom level for this style\n */\n constructor({ color, widthFraction = 1.0, dashArray, alpha = 1.0, startZoom, endZoom = Infinity }) {\n this.color = color;\n this.widthFraction = widthFraction;\n this.dashArray = dashArray;\n this.alpha = alpha;\n this.startZoom = startZoom;\n this.endZoom = endZoom;\n }\n\n /**\n * Check if this style is active at the given zoom level.\n * @param {number} z - Zoom level\n * @returns {boolean}\n */\n isActiveAtZoom(z) {\n return z >= this.startZoom && z <= this.endZoom;\n }\n\n /**\n * Serialize to plain object.\n * @returns {Object}\n */\n toJSON() {\n const obj = { color: this.color };\n if (this.widthFraction !== 1.0) obj.widthFraction = this.widthFraction;\n if (this.dashArray) obj.dashArray = this.dashArray;\n if (this.alpha !== 1.0) obj.alpha = this.alpha;\n if (this.startZoom !== undefined) obj.startZoom = this.startZoom;\n if (this.endZoom !== Infinity) obj.endZoom = this.endZoom;\n return obj;\n }\n\n /**\n * Create from plain object with validation.\n * @param {Object} obj\n * @param {number} [defaultStartZoom=0] - Default startZoom if not specified\n * @param {number} [index] - Optional index for error messages\n * @returns {LineStyle}\n */\n static fromJSON(obj, defaultStartZoom = 0, index) {\n LineStyle.validateJSON(obj, index);\n return new LineStyle({\n ...obj,\n startZoom: obj.startZoom ?? defaultStartZoom,\n });\n }\n}\n\n/**\n * Base class for layer configurations\n * \n * Supports separate styling for NE (Natural Earth) data at low zoom levels\n * and OSM data at higher zoom levels, split by zoomThreshold.\n */\nexport class LayerConfig {\n /**\n * Validate a LayerConfig configuration object.\n * Also validates all lineStyles within the config.\n * @param {Object} obj - The object to validate\n * @throws {Error} If validation fails\n */\n static validateJSON(obj) {\n if (!obj || typeof obj !== 'object') {\n throw new Error('LayerConfig: must be an object');\n }\n \n // Validate id (required)\n if (!obj.id || typeof obj.id !== 'string') {\n throw new Error('LayerConfig: id must be a non-empty string');\n }\n if (obj.id.includes('/')) {\n throw new Error(`LayerConfig: id cannot contain slashes: \"${obj.id}\"`);\n }\n \n const id = obj.id;\n \n // Validate zoom parameters (optional, but if provided must be valid)\n if (obj.startZoom !== undefined && (typeof obj.startZoom !== 'number' || obj.startZoom < 0)) {\n throw new Error(`LayerConfig \"${id}\": startZoom must be a non-negative number`);\n }\n if (obj.zoomThreshold !== undefined && (typeof obj.zoomThreshold !== 'number' || obj.zoomThreshold < 0)) {\n throw new Error(`LayerConfig \"${id}\": zoomThreshold must be a non-negative number`);\n }\n // Check startZoom <= zoomThreshold (using defaults if not provided)\n const startZoom = obj.startZoom ?? 0;\n const zoomThreshold = obj.zoomThreshold ?? 5;\n if (startZoom > zoomThreshold) {\n throw new Error(`LayerConfig \"${id}\": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);\n }\n \n // Validate lineWidthStops (optional, but if provided must be valid)\n if (obj.lineWidthStops !== undefined) {\n if (!obj.lineWidthStops || typeof obj.lineWidthStops !== 'object' || Array.isArray(obj.lineWidthStops)) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must be an object`);\n }\n const stopKeys = Object.keys(obj.lineWidthStops);\n if (stopKeys.length < 2) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must have at least 2 entries`);\n }\n for (const key of stopKeys) {\n const zoom = Number(key);\n if (!Number.isInteger(zoom) || zoom < 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops keys must be non-negative integers, got \"${key}\"`);\n }\n if (typeof obj.lineWidthStops[key] !== 'number' || obj.lineWidthStops[key] <= 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops values must be positive numbers`);\n }\n }\n }\n \n // Validate lineStyles (optional, but if provided must be valid)\n if (obj.lineStyles !== undefined) {\n if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {\n throw new Error(`LayerConfig \"${id}\": lineStyles must be a non-empty array`);\n }\n // Validate each lineStyle\n for (let i = 0; i < obj.lineStyles.length; i++) {\n LineStyle.validateJSON(obj.lineStyles[i], i);\n }\n }\n \n // Validate optional numeric fields\n if (obj.delWidthFactor !== undefined && (typeof obj.delWidthFactor !== 'number' || obj.delWidthFactor <= 0)) {\n throw new Error(`LayerConfig \"${id}\": delWidthFactor must be a positive number`);\n }\n if (obj.lineExtensionFactor !== undefined && (typeof obj.lineExtensionFactor !== 'number' || obj.lineExtensionFactor < 0)) {\n throw new Error(`LayerConfig \"${id}\": lineExtensionFactor must be a non-negative number`);\n }\n }\n\n constructor({\n id,\n startZoom = 0,\n zoomThreshold = 5,\n // Tile URL templates for matching (e.g., \"https://{s}.tile.example.com/{z}/{x}/{y}.png\")\n tileUrlTemplates = [],\n // Line width stops: map of zoom level to line width (at least 2 entries)\n // Note: interpolated/extrapolated line width is capped at a minimum of 0.5\n lineWidthStops = { 1: 0.5, 10: 2.5 },\n // Line styles array - each element describes a line to draw\n // { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }\n // Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity\n lineStyles = [{ color: 'green', widthFraction: 1.0 }],\n // Factor to multiply line width for deletion blur (default 1.5)\n // Higher values leave gaps where wiped lines meet existing lines\n // Lower values mean wiped lines show through\n delWidthFactor = 1.5,\n // Factor to extend add lines by (multiplied by deletion line width)\n // Helps cover gaps where deleted lines meet the new boundary\n // Set to 0 to disable extension\n lineExtensionFactor = 0.5,\n }) {\n this.id = id;\n this.startZoom = startZoom;\n this.zoomThreshold = zoomThreshold;\n\n // Normalize to array\n const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : \n (tileUrlTemplates ? [tileUrlTemplates] : []);\n this.tileUrlTemplates = templates;\n \n // Pre-compile regex patterns for matching tile URLs (with actual coords)\n this._compiledPatterns = templates.map(t => templateToRegex(t));\n \n // Pre-compile regex patterns for matching template URLs (with {z}/{x}/{y} placeholders)\n this._templatePatterns = templates.map(t => templateToTemplateRegex(t));\n\n this.lineWidthStops = lineWidthStops;\n \n // Convert to LineStyle instances with defaults\n this.lineStyles = lineStyles.map(style => \n style instanceof LineStyle ? style : new LineStyle({\n ...style,\n startZoom: style.startZoom ?? startZoom,\n })\n );\n \n // Deletion width factor\n this.delWidthFactor = delWidthFactor;\n \n // Line extension factor\n this.lineExtensionFactor = lineExtensionFactor;\n }\n\n /**\n * Get line styles active at a given zoom level\n * @param {number} z - Zoom level\n * @returns {LineStyle[]}\n */\n getLineStylesForZoom(z) {\n return this.lineStyles.filter(style => style.isActiveAtZoom(z));\n }\n\n /**\n * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n * @returns {boolean}\n */\n matchTemplate(templates) {\n if (this._templatePatterns.length === 0) return false;\n \n const urls = Array.isArray(templates) ? templates : [templates];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._templatePatterns.some(pattern => pattern.test(url))\n );\n }\n\n /**\n * Check if this config matches the given tile URLs (with actual coordinates)\n * @param {string | string[]} tiles - Single tile URL or array of tile URLs\n * @returns {boolean}\n */\n matchTileUrl(tiles) {\n if (this._compiledPatterns.length === 0) return false;\n \n const urls = Array.isArray(tiles) ? tiles : [tiles];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._compiledPatterns.some(({ pattern }) => pattern.test(url))\n );\n }\n\n /**\n * Extract tile coordinates (z, x, y) from a URL using this config's templates\n * @param {string} url - Tile URL to extract coordinates from\n * @returns {{ z: number, x: number, y: number } | null}\n */\n extractCoords(url) {\n for (const { pattern, groups } of this._compiledPatterns) {\n const match = url.match(pattern);\n if (match) {\n const result = {};\n for (let i = 0; i < groups.length; i++) {\n const name = groups[i];\n const value = match[i + 1];\n if (name === 'z' || name === 'x' || name === 'y') {\n result[name] = parseInt(value, 10);\n }\n }\n if ('z' in result && 'x' in result && 'y' in result) {\n return { z: result.z, x: result.x, y: result.y };\n }\n }\n }\n return null;\n }\n\n /**\n * Serialize the config to a plain object for postMessage\n * @returns {Object}\n */\n toJSON() {\n return {\n id: this.id,\n startZoom: this.startZoom,\n zoomThreshold: this.zoomThreshold,\n tileUrlTemplates: this.tileUrlTemplates,\n lineWidthStops: this.lineWidthStops,\n lineStyles: this.lineStyles.map(s => s.toJSON()),\n delWidthFactor: this.delWidthFactor,\n lineExtensionFactor: this.lineExtensionFactor,\n };\n }\n\n /**\n * Create a LayerConfig from a plain object with validation.\n * @param {Object} obj\n * @returns {LayerConfig}\n * @throws {Error} If validation fails\n */\n static fromJSON(obj) {\n LayerConfig.validateJSON(obj);\n return new LayerConfig(obj);\n }\n}\n\nexport default LayerConfig;\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -123,7 +123,166 @@ function templateToTemplateRegex(template) {
|
|
|
123
123
|
});
|
|
124
124
|
return new RegExp("^" + pattern + "(\\?.*)?$", "i");
|
|
125
125
|
}
|
|
126
|
+
function isValidColor(color) {
|
|
127
|
+
if (typeof color !== "string" || !color.trim()) return false;
|
|
128
|
+
if (typeof CSS !== "undefined" && CSS.supports) {
|
|
129
|
+
return CSS.supports("color", color);
|
|
130
|
+
}
|
|
131
|
+
const trimmed = color.trim().toLowerCase();
|
|
132
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;
|
|
133
|
+
if (/^(rgb|hsl)a?\(/.test(trimmed)) return true;
|
|
134
|
+
if (/^[a-z]+$/.test(trimmed)) return true;
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
var LineStyle = class _LineStyle {
|
|
138
|
+
/**
|
|
139
|
+
* Validate a LineStyle configuration object.
|
|
140
|
+
* @param {Object} obj - The object to validate
|
|
141
|
+
* @param {number} [index] - Optional index for error messages (when validating in an array)
|
|
142
|
+
* @throws {Error} If validation fails
|
|
143
|
+
*/
|
|
144
|
+
static validateJSON(obj, index) {
|
|
145
|
+
const prefix = index !== void 0 ? `lineStyles[${index}]` : "LineStyle";
|
|
146
|
+
if (!obj || typeof obj !== "object") {
|
|
147
|
+
throw new Error(`${prefix}: must be an object`);
|
|
148
|
+
}
|
|
149
|
+
if (!obj.color || typeof obj.color !== "string") {
|
|
150
|
+
throw new Error(`${prefix}: color must be a non-empty string`);
|
|
151
|
+
}
|
|
152
|
+
if (!isValidColor(obj.color)) {
|
|
153
|
+
throw new Error(`${prefix}: color "${obj.color}" is not a valid CSS color`);
|
|
154
|
+
}
|
|
155
|
+
if (obj.widthFraction !== void 0 && (typeof obj.widthFraction !== "number" || obj.widthFraction <= 0)) {
|
|
156
|
+
throw new Error(`${prefix}: widthFraction must be a positive number`);
|
|
157
|
+
}
|
|
158
|
+
if (obj.dashArray !== void 0 && !Array.isArray(obj.dashArray)) {
|
|
159
|
+
throw new Error(`${prefix}: dashArray must be an array`);
|
|
160
|
+
}
|
|
161
|
+
if (obj.alpha !== void 0 && (typeof obj.alpha !== "number" || obj.alpha < 0 || obj.alpha > 1)) {
|
|
162
|
+
throw new Error(`${prefix}: alpha must be a number between 0 and 1`);
|
|
163
|
+
}
|
|
164
|
+
if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
|
|
165
|
+
throw new Error(`${prefix}: startZoom must be a non-negative number`);
|
|
166
|
+
}
|
|
167
|
+
if (obj.endZoom !== void 0 && (typeof obj.endZoom !== "number" || obj.endZoom < 0)) {
|
|
168
|
+
throw new Error(`${prefix}: endZoom must be a non-negative number`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* @param {Object} options
|
|
173
|
+
* @param {string} options.color - CSS color string
|
|
174
|
+
* @param {number} [options.widthFraction=1.0] - Multiplier for base line width
|
|
175
|
+
* @param {number[]} [options.dashArray] - Dash pattern for dashed lines
|
|
176
|
+
* @param {number} [options.alpha=1.0] - Opacity (0-1)
|
|
177
|
+
* @param {number} [options.startZoom] - Minimum zoom level for this style
|
|
178
|
+
* @param {number} [options.endZoom=Infinity] - Maximum zoom level for this style
|
|
179
|
+
*/
|
|
180
|
+
constructor({ color, widthFraction = 1, dashArray, alpha = 1, startZoom, endZoom = Infinity }) {
|
|
181
|
+
this.color = color;
|
|
182
|
+
this.widthFraction = widthFraction;
|
|
183
|
+
this.dashArray = dashArray;
|
|
184
|
+
this.alpha = alpha;
|
|
185
|
+
this.startZoom = startZoom;
|
|
186
|
+
this.endZoom = endZoom;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Check if this style is active at the given zoom level.
|
|
190
|
+
* @param {number} z - Zoom level
|
|
191
|
+
* @returns {boolean}
|
|
192
|
+
*/
|
|
193
|
+
isActiveAtZoom(z) {
|
|
194
|
+
return z >= this.startZoom && z <= this.endZoom;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Serialize to plain object.
|
|
198
|
+
* @returns {Object}
|
|
199
|
+
*/
|
|
200
|
+
toJSON() {
|
|
201
|
+
const obj = { color: this.color };
|
|
202
|
+
if (this.widthFraction !== 1) obj.widthFraction = this.widthFraction;
|
|
203
|
+
if (this.dashArray) obj.dashArray = this.dashArray;
|
|
204
|
+
if (this.alpha !== 1) obj.alpha = this.alpha;
|
|
205
|
+
if (this.startZoom !== void 0) obj.startZoom = this.startZoom;
|
|
206
|
+
if (this.endZoom !== Infinity) obj.endZoom = this.endZoom;
|
|
207
|
+
return obj;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Create from plain object with validation.
|
|
211
|
+
* @param {Object} obj
|
|
212
|
+
* @param {number} [defaultStartZoom=0] - Default startZoom if not specified
|
|
213
|
+
* @param {number} [index] - Optional index for error messages
|
|
214
|
+
* @returns {LineStyle}
|
|
215
|
+
*/
|
|
216
|
+
static fromJSON(obj, defaultStartZoom = 0, index) {
|
|
217
|
+
_LineStyle.validateJSON(obj, index);
|
|
218
|
+
return new _LineStyle({
|
|
219
|
+
...obj,
|
|
220
|
+
startZoom: obj.startZoom ?? defaultStartZoom
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
126
224
|
var LayerConfig = class _LayerConfig {
|
|
225
|
+
/**
|
|
226
|
+
* Validate a LayerConfig configuration object.
|
|
227
|
+
* Also validates all lineStyles within the config.
|
|
228
|
+
* @param {Object} obj - The object to validate
|
|
229
|
+
* @throws {Error} If validation fails
|
|
230
|
+
*/
|
|
231
|
+
static validateJSON(obj) {
|
|
232
|
+
if (!obj || typeof obj !== "object") {
|
|
233
|
+
throw new Error("LayerConfig: must be an object");
|
|
234
|
+
}
|
|
235
|
+
if (!obj.id || typeof obj.id !== "string") {
|
|
236
|
+
throw new Error("LayerConfig: id must be a non-empty string");
|
|
237
|
+
}
|
|
238
|
+
if (obj.id.includes("/")) {
|
|
239
|
+
throw new Error(`LayerConfig: id cannot contain slashes: "${obj.id}"`);
|
|
240
|
+
}
|
|
241
|
+
const id = obj.id;
|
|
242
|
+
if (obj.startZoom !== void 0 && (typeof obj.startZoom !== "number" || obj.startZoom < 0)) {
|
|
243
|
+
throw new Error(`LayerConfig "${id}": startZoom must be a non-negative number`);
|
|
244
|
+
}
|
|
245
|
+
if (obj.zoomThreshold !== void 0 && (typeof obj.zoomThreshold !== "number" || obj.zoomThreshold < 0)) {
|
|
246
|
+
throw new Error(`LayerConfig "${id}": zoomThreshold must be a non-negative number`);
|
|
247
|
+
}
|
|
248
|
+
const startZoom = obj.startZoom ?? 0;
|
|
249
|
+
const zoomThreshold = obj.zoomThreshold ?? 5;
|
|
250
|
+
if (startZoom > zoomThreshold) {
|
|
251
|
+
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
252
|
+
}
|
|
253
|
+
if (obj.lineWidthStops !== void 0) {
|
|
254
|
+
if (!obj.lineWidthStops || typeof obj.lineWidthStops !== "object" || Array.isArray(obj.lineWidthStops)) {
|
|
255
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
256
|
+
}
|
|
257
|
+
const stopKeys = Object.keys(obj.lineWidthStops);
|
|
258
|
+
if (stopKeys.length < 2) {
|
|
259
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
260
|
+
}
|
|
261
|
+
for (const key of stopKeys) {
|
|
262
|
+
const zoom = Number(key);
|
|
263
|
+
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
264
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
265
|
+
}
|
|
266
|
+
if (typeof obj.lineWidthStops[key] !== "number" || obj.lineWidthStops[key] <= 0) {
|
|
267
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (obj.lineStyles !== void 0) {
|
|
272
|
+
if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
|
|
273
|
+
throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
|
|
274
|
+
}
|
|
275
|
+
for (let i = 0; i < obj.lineStyles.length; i++) {
|
|
276
|
+
LineStyle.validateJSON(obj.lineStyles[i], i);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor <= 0)) {
|
|
280
|
+
throw new Error(`LayerConfig "${id}": delWidthFactor must be a positive number`);
|
|
281
|
+
}
|
|
282
|
+
if (obj.lineExtensionFactor !== void 0 && (typeof obj.lineExtensionFactor !== "number" || obj.lineExtensionFactor < 0)) {
|
|
283
|
+
throw new Error(`LayerConfig "${id}": lineExtensionFactor must be a non-negative number`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
127
286
|
constructor({
|
|
128
287
|
id,
|
|
129
288
|
startZoom = 0,
|
|
@@ -131,6 +290,7 @@ var LayerConfig = class _LayerConfig {
|
|
|
131
290
|
// Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
|
|
132
291
|
tileUrlTemplates = [],
|
|
133
292
|
// Line width stops: map of zoom level to line width (at least 2 entries)
|
|
293
|
+
// Note: interpolated/extrapolated line width is capped at a minimum of 0.5
|
|
134
294
|
lineWidthStops = { 1: 0.5, 10: 2.5 },
|
|
135
295
|
// Line styles array - each element describes a line to draw
|
|
136
296
|
// { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }
|
|
@@ -145,66 +305,30 @@ var LayerConfig = class _LayerConfig {
|
|
|
145
305
|
// Set to 0 to disable extension
|
|
146
306
|
lineExtensionFactor = 0.5
|
|
147
307
|
}) {
|
|
148
|
-
if (!id || typeof id !== "string") {
|
|
149
|
-
throw new Error("LayerConfig requires a non-empty string id");
|
|
150
|
-
}
|
|
151
|
-
if (id.includes("/")) {
|
|
152
|
-
throw new Error(`LayerConfig id cannot contain slashes: "${id}"`);
|
|
153
|
-
}
|
|
154
308
|
this.id = id;
|
|
155
309
|
this.startZoom = startZoom;
|
|
156
310
|
this.zoomThreshold = zoomThreshold;
|
|
157
|
-
if (startZoom > zoomThreshold) {
|
|
158
|
-
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
159
|
-
}
|
|
160
311
|
const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : tileUrlTemplates ? [tileUrlTemplates] : [];
|
|
161
312
|
this.tileUrlTemplates = templates;
|
|
162
313
|
this._compiledPatterns = templates.map((t) => templateToRegex(t));
|
|
163
314
|
this._templatePatterns = templates.map((t) => templateToTemplateRegex(t));
|
|
164
|
-
if (!lineWidthStops || typeof lineWidthStops !== "object" || Array.isArray(lineWidthStops)) {
|
|
165
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
166
|
-
}
|
|
167
|
-
const stopKeys = Object.keys(lineWidthStops);
|
|
168
|
-
if (stopKeys.length < 2) {
|
|
169
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
170
|
-
}
|
|
171
|
-
for (const key of stopKeys) {
|
|
172
|
-
const zoom = Number(key);
|
|
173
|
-
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
174
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
175
|
-
}
|
|
176
|
-
if (typeof lineWidthStops[key] !== "number" || lineWidthStops[key] <= 0) {
|
|
177
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
315
|
this.lineWidthStops = lineWidthStops;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
throw new Error(`LayerConfig "${id}": lineStyles[${i}] must be an object`);
|
|
188
|
-
}
|
|
189
|
-
if (!style.color || typeof style.color !== "string") {
|
|
190
|
-
throw new Error(`LayerConfig "${id}": lineStyles[${i}].color must be a non-empty string`);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
this.lineStyles = lineStyles.map((style) => ({
|
|
194
|
-
...style,
|
|
195
|
-
startZoom: style.startZoom ?? startZoom,
|
|
196
|
-
endZoom: style.endZoom ?? Infinity
|
|
197
|
-
}));
|
|
316
|
+
this.lineStyles = lineStyles.map(
|
|
317
|
+
(style) => style instanceof LineStyle ? style : new LineStyle({
|
|
318
|
+
...style,
|
|
319
|
+
startZoom: style.startZoom ?? startZoom
|
|
320
|
+
})
|
|
321
|
+
);
|
|
198
322
|
this.delWidthFactor = delWidthFactor;
|
|
199
323
|
this.lineExtensionFactor = lineExtensionFactor;
|
|
200
324
|
}
|
|
201
325
|
/**
|
|
202
326
|
* Get line styles active at a given zoom level
|
|
203
327
|
* @param {number} z - Zoom level
|
|
204
|
-
* @returns {
|
|
328
|
+
* @returns {LineStyle[]}
|
|
205
329
|
*/
|
|
206
330
|
getLineStylesForZoom(z) {
|
|
207
|
-
return this.lineStyles.filter((style) =>
|
|
331
|
+
return this.lineStyles.filter((style) => style.isActiveAtZoom(z));
|
|
208
332
|
}
|
|
209
333
|
/**
|
|
210
334
|
* Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
|
|
@@ -267,17 +391,19 @@ var LayerConfig = class _LayerConfig {
|
|
|
267
391
|
zoomThreshold: this.zoomThreshold,
|
|
268
392
|
tileUrlTemplates: this.tileUrlTemplates,
|
|
269
393
|
lineWidthStops: this.lineWidthStops,
|
|
270
|
-
lineStyles: this.lineStyles,
|
|
394
|
+
lineStyles: this.lineStyles.map((s) => s.toJSON()),
|
|
271
395
|
delWidthFactor: this.delWidthFactor,
|
|
272
396
|
lineExtensionFactor: this.lineExtensionFactor
|
|
273
397
|
};
|
|
274
398
|
}
|
|
275
399
|
/**
|
|
276
|
-
* Create a LayerConfig from a plain object
|
|
400
|
+
* Create a LayerConfig from a plain object with validation.
|
|
277
401
|
* @param {Object} obj
|
|
278
402
|
* @returns {LayerConfig}
|
|
403
|
+
* @throws {Error} If validation fails
|
|
279
404
|
*/
|
|
280
405
|
static fromJSON(obj) {
|
|
406
|
+
_LayerConfig.validateJSON(obj);
|
|
281
407
|
return new _LayerConfig(obj);
|
|
282
408
|
}
|
|
283
409
|
};
|
|
@@ -376,6 +502,8 @@ for (const configData of configs_default) {
|
|
|
376
502
|
export {
|
|
377
503
|
LayerConfig,
|
|
378
504
|
LayerConfigRegistry,
|
|
505
|
+
LineStyle,
|
|
506
|
+
configs_default as configsJson,
|
|
379
507
|
layerConfigs
|
|
380
508
|
};
|
|
381
509
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/configs.json","../src/layerconfig.js","../src/index.js"],"sourcesContent":["[\n {\n \"id\": \"cartodb-dark\",\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"10\": 2.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(40, 40, 40)\" }\n ]\n },\n {\n \"id\": \"cartodb-light\",\n \"startZoom\": 0,\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.25, \"2\": 0.25, \"3\": 0.5, \"4\": 0.75, \"5\": 1.0 },\n \"lineStyles\": [\n { \"color\": \"rgb(235, 214, 214)\", \"alpha\": 0.2, \"startZoom\": 6, \"widthFraction\": 5 },\n { \"color\": \"rgb(235, 214, 214)\" }\n ]\n },\n {\n \"id\": \"open-topo\",\n \"startZoom\": 4,\n \"zoomThreshold\": 4,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png\",\n \"https://tile.opentopomap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"4\": 0.75, \"5\": 1.0, \"6\": 1.25, \"7\": 1.5, \"8\": 1.75, \"9\": 1.25, \"10\": 1.25, \"13\": 1.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(83, 83, 83)\", \"startZoom\": 7, \"endZoom\": 8, \"alpha\": 0.4, \"widthFraction\": 4 },\n { \"color\": \"rgb(83, 83, 83)\", \"endZoom\": 8 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9, \"widthFraction\": 9, \"alpha\": 0.2 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9 }\n ]\n },\n {\n \"id\": \"osm-carto\",\n \"startZoom\": 1,\n \"zoomThreshold\": 1,\n \"tileUrlTemplates\": [\n \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"2\": 0.6, \"3\": 0.7, \"4\": 1.0, \"10\": 3.75 },\n \"lineStyles\": [\n { \"color\": \"rgb(200, 180, 200)\" },\n { \"color\": \"rgb(160, 120, 160)\", \"widthFraction\": 0.333, \"dashArray\": [30, 2, 8, 2] }\n ]\n },\n {\n \"id\": \"osm-hot\",\n \"startZoom\": 2,\n \"zoomThreshold\": 2,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"2\": 3.0, \"3\": 3.0, \"7\": 3.0, \"8\": 3.5, \"9\": 3.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(149, 175, 180)\" },\n { \"color\": \"rgb(89, 117, 123)\", \"widthFraction\": 0.33 }\n ]\n }\n]\n","/**\n * Convert a tile URL template to a regex pattern and capture group names.\n * Supports {z}, {x}, {y}, {s} (Leaflet subdomain), {a-c}/{1-4} (OpenLayers subdomain), and {r} (retina) placeholders.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {{ pattern: RegExp, groups: string[] }}\n */\nfunction templateToRegex(template) {\n const groups = [];\n // Escape regex special chars, then replace placeholders\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n // Don't escape our placeholders\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} etc (OpenLayers style subdomain)\n .replace(/\\{[a-z0-9]-[a-z0-9]\\}/gi, () => {\n groups.push('s');\n return '([a-z0-9]+)';\n })\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n groups.push(lowerName);\n if (lowerName === 's') {\n // Subdomain: single letter or short string\n return '([a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Retina suffix: optional @2x or similar\n return '(@\\\\d+x)?';\n }\n // z, x, y: numeric\n return '(\\\\d+)';\n });\n \n // Allow optional query string at end\n return { pattern: new RegExp('^' + pattern + '(\\\\?.*)?$', 'i'), groups };\n}\n\n/**\n * Convert a tile URL template to a regex that matches the template itself.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {RegExp}\n */\nfunction templateToTemplateRegex(template) {\n // Escape regex special chars, then replace placeholders with literal match\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} (OpenLayers style subdomain)\n .replace(/\\{([a-z0-9])-([a-z0-9])\\}/gi, (_, start, end) => `(\\\\{${start}-${end}\\\\}|\\\\{s\\\\}|[a-z0-9]+)`)\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n if (lowerName === 's') {\n // Match {s} placeholder, {a-c} style placeholder, or actual subdomain\n return '(\\\\{s\\\\}|\\\\{[a-z0-9]-[a-z0-9]\\\\}|[a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Match {r} placeholder or actual retina or empty\n return '(\\\\{r\\\\}|@\\\\d+x)?';\n }\n // Match {z}, {x}, {y} placeholders\n return `\\\\{${lowerName}\\\\}`;\n });\n \n // Allow optional query string at end\n return new RegExp('^' + pattern + '(\\\\?.*)?$', 'i');\n}\n\n/**\n * Base class for layer configurations\n * \n * Supports separate styling for NE (Natural Earth) data at low zoom levels\n * and OSM data at higher zoom levels, split by zoomThreshold.\n */\nexport class LayerConfig {\n constructor({\n id,\n startZoom = 0,\n zoomThreshold = 5,\n // Tile URL templates for matching (e.g., \"https://{s}.tile.example.com/{z}/{x}/{y}.png\")\n tileUrlTemplates = [],\n // Line width stops: map of zoom level to line width (at least 2 entries)\n lineWidthStops = { 1: 0.5, 10: 2.5 },\n // Line styles array - each element describes a line to draw\n // { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }\n // Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity\n lineStyles = [{ color: 'green', widthFraction: 1.0 }],\n // Factor to multiply line width for deletion blur (default 1.5)\n // Higher values leave gaps where wiped lines meet existing lines\n // Lower values mean wiped lines show through\n delWidthFactor = 1.5,\n // Factor to extend add lines by (multiplied by deletion line width)\n // Helps cover gaps where deleted lines meet the new boundary\n // Set to 0 to disable extension\n lineExtensionFactor = 0.5,\n }) {\n if (!id || typeof id !== 'string') {\n throw new Error('LayerConfig requires a non-empty string id');\n }\n if (id.includes('/')) {\n throw new Error(`LayerConfig id cannot contain slashes: \"${id}\"`);\n }\n\n this.id = id;\n this.startZoom = startZoom;\n this.zoomThreshold = zoomThreshold;\n\n if (startZoom > zoomThreshold) {\n throw new Error(`LayerConfig \"${id}\": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);\n }\n\n // Normalize to array\n const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : \n (tileUrlTemplates ? [tileUrlTemplates] : []);\n this.tileUrlTemplates = templates;\n \n // Pre-compile regex patterns for matching tile URLs (with actual coords)\n this._compiledPatterns = templates.map(t => templateToRegex(t));\n \n // Pre-compile regex patterns for matching template URLs (with {z}/{x}/{y} placeholders)\n this._templatePatterns = templates.map(t => templateToTemplateRegex(t));\n\n // Validate lineWidthStops\n if (!lineWidthStops || typeof lineWidthStops !== 'object' || Array.isArray(lineWidthStops)) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must be an object`);\n }\n const stopKeys = Object.keys(lineWidthStops);\n if (stopKeys.length < 2) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must have at least 2 entries`);\n }\n for (const key of stopKeys) {\n const zoom = Number(key);\n if (!Number.isInteger(zoom) || zoom < 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops keys must be non-negative integers, got \"${key}\"`);\n }\n if (typeof lineWidthStops[key] !== 'number' || lineWidthStops[key] <= 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops values must be positive numbers`);\n }\n }\n this.lineWidthStops = lineWidthStops;\n\n // Validate lineStyles\n if (!Array.isArray(lineStyles) || lineStyles.length === 0) {\n throw new Error(`LayerConfig \"${id}\": lineStyles must be a non-empty array`);\n }\n for (let i = 0; i < lineStyles.length; i++) {\n const style = lineStyles[i];\n if (!style || typeof style !== 'object') {\n throw new Error(`LayerConfig \"${id}\": lineStyles[${i}] must be an object`);\n }\n if (!style.color || typeof style.color !== 'string') {\n throw new Error(`LayerConfig \"${id}\": lineStyles[${i}].color must be a non-empty string`);\n }\n }\n \n // Line styles - normalize startZoom/endZoom defaults\n this.lineStyles = lineStyles.map(style => ({\n ...style,\n startZoom: style.startZoom ?? startZoom,\n endZoom: style.endZoom ?? Infinity,\n }));\n \n // Deletion width factor\n this.delWidthFactor = delWidthFactor;\n \n // Line extension factor\n this.lineExtensionFactor = lineExtensionFactor;\n }\n\n /**\n * Get line styles active at a given zoom level\n * @param {number} z - Zoom level\n * @returns {Array<{color: string, widthFraction?: number, dashArray?: number[]}>}\n */\n getLineStylesForZoom(z) {\n return this.lineStyles.filter(style => z >= style.startZoom && z <= style.endZoom);\n }\n\n /**\n * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n * @returns {boolean}\n */\n matchTemplate(templates) {\n if (this._templatePatterns.length === 0) return false;\n \n const urls = Array.isArray(templates) ? templates : [templates];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._templatePatterns.some(pattern => pattern.test(url))\n );\n }\n\n /**\n * Check if this config matches the given tile URLs (with actual coordinates)\n * @param {string | string[]} tiles - Single tile URL or array of tile URLs\n * @returns {boolean}\n */\n matchTileUrl(tiles) {\n if (this._compiledPatterns.length === 0) return false;\n \n const urls = Array.isArray(tiles) ? tiles : [tiles];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._compiledPatterns.some(({ pattern }) => pattern.test(url))\n );\n }\n\n /**\n * Extract tile coordinates (z, x, y) from a URL using this config's templates\n * @param {string} url - Tile URL to extract coordinates from\n * @returns {{ z: number, x: number, y: number } | null}\n */\n extractCoords(url) {\n for (const { pattern, groups } of this._compiledPatterns) {\n const match = url.match(pattern);\n if (match) {\n const result = {};\n for (let i = 0; i < groups.length; i++) {\n const name = groups[i];\n const value = match[i + 1];\n if (name === 'z' || name === 'x' || name === 'y') {\n result[name] = parseInt(value, 10);\n }\n }\n if ('z' in result && 'x' in result && 'y' in result) {\n return { z: result.z, x: result.x, y: result.y };\n }\n }\n }\n return null;\n }\n\n /**\n * Serialize the config to a plain object for postMessage\n * @returns {Object}\n */\n toJSON() {\n return {\n id: this.id,\n startZoom: this.startZoom,\n zoomThreshold: this.zoomThreshold,\n tileUrlTemplates: this.tileUrlTemplates,\n lineWidthStops: this.lineWidthStops,\n lineStyles: this.lineStyles,\n delWidthFactor: this.delWidthFactor,\n lineExtensionFactor: this.lineExtensionFactor,\n };\n }\n\n /**\n * Create a LayerConfig from a plain object (e.g., from postMessage)\n * @param {Object} obj\n * @returns {LayerConfig}\n */\n static fromJSON(obj) {\n return new LayerConfig(obj);\n }\n}\n\nexport default LayerConfig;\n","import configsJson from './configs.json' with { type: 'json' };\nimport { LayerConfig } from './layerconfig.js';\n\nexport { LayerConfig } from './layerconfig.js';\n\n/**\n * Layer configuration registry\n */\nexport class LayerConfigRegistry {\n constructor() {\n this.registry = {};\n }\n\n /**\n * Get a layer config by id\n */\n get(id) {\n return this.registry[id];\n }\n\n /**\n * Register a new layer config\n */\n register(config) {\n this.registry[config.id] = config;\n }\n\n /**\n * Remove a layer config by id\n */\n remove(id) {\n if (!this.registry[id]) return false;\n delete this.registry[id];\n return true;\n }\n\n /**\n * Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n */\n detectFromTemplates(templates) {\n if (!templates || (Array.isArray(templates) && templates.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTemplate(templates)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Detect layer config from actual tile URLs (with numeric coordinates)\n * @param {string | string[]} urls - Single tile URL or array of tile URLs\n */\n detectFromTileUrls(urls) {\n if (!urls || (Array.isArray(urls) && urls.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTileUrl(urls)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Get all available layer config ids\n */\n getAvailableIds() {\n return Object.keys(this.registry);\n }\n\n /**\n * Create a new registry with all configs from this registry plus extra configs.\n * @param {LayerConfig[]} extraLayerConfigs - Additional configs to add\n * @returns {LayerConfigRegistry} A new registry with merged configs\n */\n createMergedRegistry(extraLayerConfigs) {\n const registry = new LayerConfigRegistry();\n \n for (const id of this.getAvailableIds()) {\n registry.register(this.get(id));\n }\n \n if (extraLayerConfigs && extraLayerConfigs.length > 0) {\n for (const config of extraLayerConfigs) {\n registry.register(config);\n }\n }\n \n return registry;\n }\n\n /**\n * Parse a tile URL into its components: layer config and coordinates\n * @param {string} url - Tile URL to parse\n * @returns {{ layerConfig: LayerConfig, coords: { z: number, x: number, y: number } } | null}\n */\n parseTileUrl(url) {\n // Check if URL matches any layer config\n const layerConfig = this.detectFromTileUrls([url]);\n if (!layerConfig) return null;\n \n // Extract tile coordinates using the matched config\n const coords = layerConfig.extractCoords(url);\n if (!coords) return null;\n \n return { layerConfig, coords };\n }\n}\n\n// Default registry with built-in configs loaded from JSON\nexport const layerConfigs = new LayerConfigRegistry();\nfor (const configData of configsJson) {\n layerConfigs.register(new LayerConfig(configData));\n}\n\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/configs.json","../src/layerconfig.js","../src/index.js"],"sourcesContent":["[\n {\n \"id\": \"cartodb-dark\",\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_nolabels/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"10\": 2.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(40, 40, 40)\" }\n ]\n },\n {\n \"id\": \"cartodb-light\",\n \"startZoom\": 0,\n \"zoomThreshold\": 5,\n \"tileUrlTemplates\": [\n \"https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}{r}.png\",\n \"https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\",\n \"https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.25, \"2\": 0.25, \"3\": 0.5, \"4\": 0.75, \"5\": 1.0 },\n \"lineStyles\": [\n { \"color\": \"rgb(235, 214, 214)\", \"alpha\": 0.2, \"startZoom\": 6, \"widthFraction\": 5 },\n { \"color\": \"rgb(235, 214, 214)\" }\n ]\n },\n {\n \"id\": \"open-topo\",\n \"startZoom\": 4,\n \"zoomThreshold\": 4,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png\",\n \"https://tile.opentopomap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"4\": 0.75, \"5\": 1.0, \"6\": 1.25, \"7\": 1.5, \"8\": 1.75, \"9\": 1.25, \"10\": 1.25, \"13\": 1.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(83, 83, 83)\", \"startZoom\": 7, \"endZoom\": 8, \"alpha\": 0.4, \"widthFraction\": 4 },\n { \"color\": \"rgb(83, 83, 83)\", \"endZoom\": 8 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9, \"widthFraction\": 9, \"alpha\": 0.2 },\n { \"color\": \"rgb(140, 20, 180)\", \"startZoom\": 9 }\n ]\n },\n {\n \"id\": \"osm-carto\",\n \"startZoom\": 1,\n \"zoomThreshold\": 1,\n \"tileUrlTemplates\": [\n \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n \"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"1\": 0.5, \"2\": 0.6, \"3\": 0.7, \"4\": 1.0, \"10\": 3.75 },\n \"lineStyles\": [\n { \"color\": \"rgb(200, 180, 200)\" },\n { \"color\": \"rgb(160, 120, 160)\", \"widthFraction\": 0.333, \"dashArray\": [30, 2, 8, 2] }\n ]\n },\n {\n \"id\": \"osm-hot\",\n \"startZoom\": 2,\n \"zoomThreshold\": 2,\n \"tileUrlTemplates\": [\n \"https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png\"\n ],\n \"lineWidthStops\": { \"2\": 3.0, \"3\": 3.0, \"7\": 3.0, \"8\": 3.5, \"9\": 3.5 },\n \"lineStyles\": [\n { \"color\": \"rgb(149, 175, 180)\" },\n { \"color\": \"rgb(89, 117, 123)\", \"widthFraction\": 0.33 }\n ]\n }\n]\n","/**\n * Convert a tile URL template to a regex pattern and capture group names.\n * Supports {z}, {x}, {y}, {s} (Leaflet subdomain), {a-c}/{1-4} (OpenLayers subdomain), and {r} (retina) placeholders.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {{ pattern: RegExp, groups: string[] }}\n */\nfunction templateToRegex(template) {\n const groups = [];\n // Escape regex special chars, then replace placeholders\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n // Don't escape our placeholders\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} etc (OpenLayers style subdomain)\n .replace(/\\{[a-z0-9]-[a-z0-9]\\}/gi, () => {\n groups.push('s');\n return '([a-z0-9]+)';\n })\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n groups.push(lowerName);\n if (lowerName === 's') {\n // Subdomain: single letter or short string\n return '([a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Retina suffix: optional @2x or similar\n return '(@\\\\d+x)?';\n }\n // z, x, y: numeric\n return '(\\\\d+)';\n });\n \n // Allow optional query string at end\n return { pattern: new RegExp('^' + pattern + '(\\\\?.*)?$', 'i'), groups };\n}\n\n/**\n * Convert a tile URL template to a regex that matches the template itself.\n * @param {string} template - URL template like \"https://{s}.tile.example.com/{z}/{x}/{y}.png\"\n * @returns {RegExp}\n */\nfunction templateToTemplateRegex(template) {\n // Escape regex special chars, then replace placeholders with literal match\n let pattern = template\n .replace(/[.*+?^${}()|[\\]\\\\]/g, (char) => {\n if (char === '{' || char === '}') return char;\n return '\\\\' + char;\n })\n // Make protocol flexible (http/https)\n .replace(/^https:\\/\\//, 'https?://')\n .replace(/^http:\\/\\//, 'https?://')\n // Handle {a-c} or {1-4} (OpenLayers style subdomain)\n .replace(/\\{([a-z0-9])-([a-z0-9])\\}/gi, (_, start, end) => `(\\\\{${start}-${end}\\\\}|\\\\{s\\\\}|[a-z0-9]+)`)\n .replace(/\\{(z|x|y|s|r)\\}/gi, (_, name) => {\n const lowerName = name.toLowerCase();\n if (lowerName === 's') {\n // Match {s} placeholder, {a-c} style placeholder, or actual subdomain\n return '(\\\\{s\\\\}|\\\\{[a-z0-9]-[a-z0-9]\\\\}|[a-z0-9]+)';\n }\n if (lowerName === 'r') {\n // Match {r} placeholder or actual retina or empty\n return '(\\\\{r\\\\}|@\\\\d+x)?';\n }\n // Match {z}, {x}, {y} placeholders\n return `\\\\{${lowerName}\\\\}`;\n });\n \n // Allow optional query string at end\n return new RegExp('^' + pattern + '(\\\\?.*)?$', 'i');\n}\n\n/**\n * Check if a string is a valid CSS color using the browser's CSS parser.\n * Falls back to a basic regex check in non-browser environments.\n * @param {string} color\n * @returns {boolean}\n */\nfunction isValidColor(color) {\n if (typeof color !== 'string' || !color.trim()) return false;\n \n // Use CSS.supports if available (modern browsers)\n if (typeof CSS !== 'undefined' && CSS.supports) {\n return CSS.supports('color', color);\n }\n \n // Fallback: basic validation for common formats\n const trimmed = color.trim().toLowerCase();\n // Hex colors\n if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;\n // rgb/rgba/hsl/hsla functions\n if (/^(rgb|hsl)a?\\(/.test(trimmed)) return true;\n // Named colors (just check it's alphabetic)\n if (/^[a-z]+$/.test(trimmed)) return true;\n return false;\n}\n\n/**\n * Represents a line style for drawing boundaries.\n */\nexport class LineStyle {\n /**\n * Validate a LineStyle configuration object.\n * @param {Object} obj - The object to validate\n * @param {number} [index] - Optional index for error messages (when validating in an array)\n * @throws {Error} If validation fails\n */\n static validateJSON(obj, index) {\n const prefix = index !== undefined ? `lineStyles[${index}]` : 'LineStyle';\n \n if (!obj || typeof obj !== 'object') {\n throw new Error(`${prefix}: must be an object`);\n }\n \n if (!obj.color || typeof obj.color !== 'string') {\n throw new Error(`${prefix}: color must be a non-empty string`);\n }\n \n if (!isValidColor(obj.color)) {\n throw new Error(`${prefix}: color \"${obj.color}\" is not a valid CSS color`);\n }\n \n if (obj.widthFraction !== undefined && (typeof obj.widthFraction !== 'number' || obj.widthFraction <= 0)) {\n throw new Error(`${prefix}: widthFraction must be a positive number`);\n }\n \n if (obj.dashArray !== undefined && !Array.isArray(obj.dashArray)) {\n throw new Error(`${prefix}: dashArray must be an array`);\n }\n \n if (obj.alpha !== undefined && (typeof obj.alpha !== 'number' || obj.alpha < 0 || obj.alpha > 1)) {\n throw new Error(`${prefix}: alpha must be a number between 0 and 1`);\n }\n \n if (obj.startZoom !== undefined && (typeof obj.startZoom !== 'number' || obj.startZoom < 0)) {\n throw new Error(`${prefix}: startZoom must be a non-negative number`);\n }\n \n if (obj.endZoom !== undefined && (typeof obj.endZoom !== 'number' || obj.endZoom < 0)) {\n throw new Error(`${prefix}: endZoom must be a non-negative number`);\n }\n }\n\n /**\n * @param {Object} options\n * @param {string} options.color - CSS color string\n * @param {number} [options.widthFraction=1.0] - Multiplier for base line width\n * @param {number[]} [options.dashArray] - Dash pattern for dashed lines\n * @param {number} [options.alpha=1.0] - Opacity (0-1)\n * @param {number} [options.startZoom] - Minimum zoom level for this style\n * @param {number} [options.endZoom=Infinity] - Maximum zoom level for this style\n */\n constructor({ color, widthFraction = 1.0, dashArray, alpha = 1.0, startZoom, endZoom = Infinity }) {\n this.color = color;\n this.widthFraction = widthFraction;\n this.dashArray = dashArray;\n this.alpha = alpha;\n this.startZoom = startZoom;\n this.endZoom = endZoom;\n }\n\n /**\n * Check if this style is active at the given zoom level.\n * @param {number} z - Zoom level\n * @returns {boolean}\n */\n isActiveAtZoom(z) {\n return z >= this.startZoom && z <= this.endZoom;\n }\n\n /**\n * Serialize to plain object.\n * @returns {Object}\n */\n toJSON() {\n const obj = { color: this.color };\n if (this.widthFraction !== 1.0) obj.widthFraction = this.widthFraction;\n if (this.dashArray) obj.dashArray = this.dashArray;\n if (this.alpha !== 1.0) obj.alpha = this.alpha;\n if (this.startZoom !== undefined) obj.startZoom = this.startZoom;\n if (this.endZoom !== Infinity) obj.endZoom = this.endZoom;\n return obj;\n }\n\n /**\n * Create from plain object with validation.\n * @param {Object} obj\n * @param {number} [defaultStartZoom=0] - Default startZoom if not specified\n * @param {number} [index] - Optional index for error messages\n * @returns {LineStyle}\n */\n static fromJSON(obj, defaultStartZoom = 0, index) {\n LineStyle.validateJSON(obj, index);\n return new LineStyle({\n ...obj,\n startZoom: obj.startZoom ?? defaultStartZoom,\n });\n }\n}\n\n/**\n * Base class for layer configurations\n * \n * Supports separate styling for NE (Natural Earth) data at low zoom levels\n * and OSM data at higher zoom levels, split by zoomThreshold.\n */\nexport class LayerConfig {\n /**\n * Validate a LayerConfig configuration object.\n * Also validates all lineStyles within the config.\n * @param {Object} obj - The object to validate\n * @throws {Error} If validation fails\n */\n static validateJSON(obj) {\n if (!obj || typeof obj !== 'object') {\n throw new Error('LayerConfig: must be an object');\n }\n \n // Validate id (required)\n if (!obj.id || typeof obj.id !== 'string') {\n throw new Error('LayerConfig: id must be a non-empty string');\n }\n if (obj.id.includes('/')) {\n throw new Error(`LayerConfig: id cannot contain slashes: \"${obj.id}\"`);\n }\n \n const id = obj.id;\n \n // Validate zoom parameters (optional, but if provided must be valid)\n if (obj.startZoom !== undefined && (typeof obj.startZoom !== 'number' || obj.startZoom < 0)) {\n throw new Error(`LayerConfig \"${id}\": startZoom must be a non-negative number`);\n }\n if (obj.zoomThreshold !== undefined && (typeof obj.zoomThreshold !== 'number' || obj.zoomThreshold < 0)) {\n throw new Error(`LayerConfig \"${id}\": zoomThreshold must be a non-negative number`);\n }\n // Check startZoom <= zoomThreshold (using defaults if not provided)\n const startZoom = obj.startZoom ?? 0;\n const zoomThreshold = obj.zoomThreshold ?? 5;\n if (startZoom > zoomThreshold) {\n throw new Error(`LayerConfig \"${id}\": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);\n }\n \n // Validate lineWidthStops (optional, but if provided must be valid)\n if (obj.lineWidthStops !== undefined) {\n if (!obj.lineWidthStops || typeof obj.lineWidthStops !== 'object' || Array.isArray(obj.lineWidthStops)) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must be an object`);\n }\n const stopKeys = Object.keys(obj.lineWidthStops);\n if (stopKeys.length < 2) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops must have at least 2 entries`);\n }\n for (const key of stopKeys) {\n const zoom = Number(key);\n if (!Number.isInteger(zoom) || zoom < 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops keys must be non-negative integers, got \"${key}\"`);\n }\n if (typeof obj.lineWidthStops[key] !== 'number' || obj.lineWidthStops[key] <= 0) {\n throw new Error(`LayerConfig \"${id}\": lineWidthStops values must be positive numbers`);\n }\n }\n }\n \n // Validate lineStyles (optional, but if provided must be valid)\n if (obj.lineStyles !== undefined) {\n if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {\n throw new Error(`LayerConfig \"${id}\": lineStyles must be a non-empty array`);\n }\n // Validate each lineStyle\n for (let i = 0; i < obj.lineStyles.length; i++) {\n LineStyle.validateJSON(obj.lineStyles[i], i);\n }\n }\n \n // Validate optional numeric fields\n if (obj.delWidthFactor !== undefined && (typeof obj.delWidthFactor !== 'number' || obj.delWidthFactor <= 0)) {\n throw new Error(`LayerConfig \"${id}\": delWidthFactor must be a positive number`);\n }\n if (obj.lineExtensionFactor !== undefined && (typeof obj.lineExtensionFactor !== 'number' || obj.lineExtensionFactor < 0)) {\n throw new Error(`LayerConfig \"${id}\": lineExtensionFactor must be a non-negative number`);\n }\n }\n\n constructor({\n id,\n startZoom = 0,\n zoomThreshold = 5,\n // Tile URL templates for matching (e.g., \"https://{s}.tile.example.com/{z}/{x}/{y}.png\")\n tileUrlTemplates = [],\n // Line width stops: map of zoom level to line width (at least 2 entries)\n // Note: interpolated/extrapolated line width is capped at a minimum of 0.5\n lineWidthStops = { 1: 0.5, 10: 2.5 },\n // Line styles array - each element describes a line to draw\n // { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }\n // Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity\n lineStyles = [{ color: 'green', widthFraction: 1.0 }],\n // Factor to multiply line width for deletion blur (default 1.5)\n // Higher values leave gaps where wiped lines meet existing lines\n // Lower values mean wiped lines show through\n delWidthFactor = 1.5,\n // Factor to extend add lines by (multiplied by deletion line width)\n // Helps cover gaps where deleted lines meet the new boundary\n // Set to 0 to disable extension\n lineExtensionFactor = 0.5,\n }) {\n this.id = id;\n this.startZoom = startZoom;\n this.zoomThreshold = zoomThreshold;\n\n // Normalize to array\n const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates : \n (tileUrlTemplates ? [tileUrlTemplates] : []);\n this.tileUrlTemplates = templates;\n \n // Pre-compile regex patterns for matching tile URLs (with actual coords)\n this._compiledPatterns = templates.map(t => templateToRegex(t));\n \n // Pre-compile regex patterns for matching template URLs (with {z}/{x}/{y} placeholders)\n this._templatePatterns = templates.map(t => templateToTemplateRegex(t));\n\n this.lineWidthStops = lineWidthStops;\n \n // Convert to LineStyle instances with defaults\n this.lineStyles = lineStyles.map(style => \n style instanceof LineStyle ? style : new LineStyle({\n ...style,\n startZoom: style.startZoom ?? startZoom,\n })\n );\n \n // Deletion width factor\n this.delWidthFactor = delWidthFactor;\n \n // Line extension factor\n this.lineExtensionFactor = lineExtensionFactor;\n }\n\n /**\n * Get line styles active at a given zoom level\n * @param {number} z - Zoom level\n * @returns {LineStyle[]}\n */\n getLineStylesForZoom(z) {\n return this.lineStyles.filter(style => style.isActiveAtZoom(z));\n }\n\n /**\n * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n * @returns {boolean}\n */\n matchTemplate(templates) {\n if (this._templatePatterns.length === 0) return false;\n \n const urls = Array.isArray(templates) ? templates : [templates];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._templatePatterns.some(pattern => pattern.test(url))\n );\n }\n\n /**\n * Check if this config matches the given tile URLs (with actual coordinates)\n * @param {string | string[]} tiles - Single tile URL or array of tile URLs\n * @returns {boolean}\n */\n matchTileUrl(tiles) {\n if (this._compiledPatterns.length === 0) return false;\n \n const urls = Array.isArray(tiles) ? tiles : [tiles];\n if (urls.length === 0) return false;\n \n return urls.some(url => \n this._compiledPatterns.some(({ pattern }) => pattern.test(url))\n );\n }\n\n /**\n * Extract tile coordinates (z, x, y) from a URL using this config's templates\n * @param {string} url - Tile URL to extract coordinates from\n * @returns {{ z: number, x: number, y: number } | null}\n */\n extractCoords(url) {\n for (const { pattern, groups } of this._compiledPatterns) {\n const match = url.match(pattern);\n if (match) {\n const result = {};\n for (let i = 0; i < groups.length; i++) {\n const name = groups[i];\n const value = match[i + 1];\n if (name === 'z' || name === 'x' || name === 'y') {\n result[name] = parseInt(value, 10);\n }\n }\n if ('z' in result && 'x' in result && 'y' in result) {\n return { z: result.z, x: result.x, y: result.y };\n }\n }\n }\n return null;\n }\n\n /**\n * Serialize the config to a plain object for postMessage\n * @returns {Object}\n */\n toJSON() {\n return {\n id: this.id,\n startZoom: this.startZoom,\n zoomThreshold: this.zoomThreshold,\n tileUrlTemplates: this.tileUrlTemplates,\n lineWidthStops: this.lineWidthStops,\n lineStyles: this.lineStyles.map(s => s.toJSON()),\n delWidthFactor: this.delWidthFactor,\n lineExtensionFactor: this.lineExtensionFactor,\n };\n }\n\n /**\n * Create a LayerConfig from a plain object with validation.\n * @param {Object} obj\n * @returns {LayerConfig}\n * @throws {Error} If validation fails\n */\n static fromJSON(obj) {\n LayerConfig.validateJSON(obj);\n return new LayerConfig(obj);\n }\n}\n\nexport default LayerConfig;\n","import configsJson from './configs.json' with { type: 'json' };\nimport { LayerConfig, LineStyle } from './layerconfig.js';\n\nexport { LayerConfig, LineStyle } from './layerconfig.js';\n\n// Export raw configs for testing/inspection\nexport { configsJson };\n\n/**\n * Layer configuration registry\n */\nexport class LayerConfigRegistry {\n constructor() {\n this.registry = {};\n }\n\n /**\n * Get a layer config by id\n */\n get(id) {\n return this.registry[id];\n }\n\n /**\n * Register a new layer config\n */\n register(config) {\n this.registry[config.id] = config;\n }\n\n /**\n * Remove a layer config by id\n */\n remove(id) {\n if (!this.registry[id]) return false;\n delete this.registry[id];\n return true;\n }\n\n /**\n * Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)\n * @param {string | string[]} templates - Single template URL or array of template URLs\n */\n detectFromTemplates(templates) {\n if (!templates || (Array.isArray(templates) && templates.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTemplate(templates)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Detect layer config from actual tile URLs (with numeric coordinates)\n * @param {string | string[]} urls - Single tile URL or array of tile URLs\n */\n detectFromTileUrls(urls) {\n if (!urls || (Array.isArray(urls) && urls.length === 0)) return undefined;\n \n for (const config of Object.values(this.registry)) {\n if (config.matchTileUrl(urls)) {\n return config;\n }\n }\n \n return undefined;\n }\n\n /**\n * Get all available layer config ids\n */\n getAvailableIds() {\n return Object.keys(this.registry);\n }\n\n /**\n * Create a new registry with all configs from this registry plus extra configs.\n * @param {LayerConfig[]} extraLayerConfigs - Additional configs to add\n * @returns {LayerConfigRegistry} A new registry with merged configs\n */\n createMergedRegistry(extraLayerConfigs) {\n const registry = new LayerConfigRegistry();\n \n for (const id of this.getAvailableIds()) {\n registry.register(this.get(id));\n }\n \n if (extraLayerConfigs && extraLayerConfigs.length > 0) {\n for (const config of extraLayerConfigs) {\n registry.register(config);\n }\n }\n \n return registry;\n }\n\n /**\n * Parse a tile URL into its components: layer config and coordinates\n * @param {string} url - Tile URL to parse\n * @returns {{ layerConfig: LayerConfig, coords: { z: number, x: number, y: number } } | null}\n */\n parseTileUrl(url) {\n // Check if URL matches any layer config\n const layerConfig = this.detectFromTileUrls([url]);\n if (!layerConfig) return null;\n \n // Extract tile coordinates using the matched config\n const coords = layerConfig.extractCoords(url);\n if (!coords) return null;\n \n return { layerConfig, coords };\n }\n}\n\n// Default registry with built-in configs loaded from JSON\nexport const layerConfigs = new LayerConfigRegistry();\nfor (const configData of configsJson) {\n layerConfigs.register(new LayerConfig(configData));\n}\n\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface LayerConfigOptions {
|
|
|
34
34
|
lineStyles?: LineStyle[];
|
|
35
35
|
/** Factor to multiply line width for deletion blur (default: 1.5) */
|
|
36
36
|
delWidthFactor?: number;
|
|
37
|
+
/** Factor to extend add lines by (multiplied by deletion line width) (default: 0.5) */
|
|
38
|
+
lineExtensionFactor?: number;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
/**
|
|
@@ -47,6 +49,7 @@ export class LayerConfig {
|
|
|
47
49
|
readonly lineWidthStops: Record<number, number>;
|
|
48
50
|
readonly lineStyles: LineStyle[];
|
|
49
51
|
readonly delWidthFactor: number;
|
|
52
|
+
readonly lineExtensionFactor: number;
|
|
50
53
|
|
|
51
54
|
constructor(options: LayerConfigOptions);
|
|
52
55
|
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import configsJson from './configs.json' with { type: 'json' };
|
|
2
|
-
import { LayerConfig } from './layerconfig.js';
|
|
2
|
+
import { LayerConfig, LineStyle } from './layerconfig.js';
|
|
3
3
|
|
|
4
|
-
export { LayerConfig } from './layerconfig.js';
|
|
4
|
+
export { LayerConfig, LineStyle } from './layerconfig.js';
|
|
5
|
+
|
|
6
|
+
// Export raw configs for testing/inspection
|
|
7
|
+
export { configsJson };
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Layer configuration registry
|
package/src/layerconfig.js
CHANGED
|
@@ -75,6 +75,134 @@ function templateToTemplateRegex(template) {
|
|
|
75
75
|
return new RegExp('^' + pattern + '(\\?.*)?$', 'i');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Check if a string is a valid CSS color using the browser's CSS parser.
|
|
80
|
+
* Falls back to a basic regex check in non-browser environments.
|
|
81
|
+
* @param {string} color
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
function isValidColor(color) {
|
|
85
|
+
if (typeof color !== 'string' || !color.trim()) return false;
|
|
86
|
+
|
|
87
|
+
// Use CSS.supports if available (modern browsers)
|
|
88
|
+
if (typeof CSS !== 'undefined' && CSS.supports) {
|
|
89
|
+
return CSS.supports('color', color);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fallback: basic validation for common formats
|
|
93
|
+
const trimmed = color.trim().toLowerCase();
|
|
94
|
+
// Hex colors
|
|
95
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(trimmed)) return true;
|
|
96
|
+
// rgb/rgba/hsl/hsla functions
|
|
97
|
+
if (/^(rgb|hsl)a?\(/.test(trimmed)) return true;
|
|
98
|
+
// Named colors (just check it's alphabetic)
|
|
99
|
+
if (/^[a-z]+$/.test(trimmed)) return true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Represents a line style for drawing boundaries.
|
|
105
|
+
*/
|
|
106
|
+
export class LineStyle {
|
|
107
|
+
/**
|
|
108
|
+
* Validate a LineStyle configuration object.
|
|
109
|
+
* @param {Object} obj - The object to validate
|
|
110
|
+
* @param {number} [index] - Optional index for error messages (when validating in an array)
|
|
111
|
+
* @throws {Error} If validation fails
|
|
112
|
+
*/
|
|
113
|
+
static validateJSON(obj, index) {
|
|
114
|
+
const prefix = index !== undefined ? `lineStyles[${index}]` : 'LineStyle';
|
|
115
|
+
|
|
116
|
+
if (!obj || typeof obj !== 'object') {
|
|
117
|
+
throw new Error(`${prefix}: must be an object`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!obj.color || typeof obj.color !== 'string') {
|
|
121
|
+
throw new Error(`${prefix}: color must be a non-empty string`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!isValidColor(obj.color)) {
|
|
125
|
+
throw new Error(`${prefix}: color "${obj.color}" is not a valid CSS color`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (obj.widthFraction !== undefined && (typeof obj.widthFraction !== 'number' || obj.widthFraction <= 0)) {
|
|
129
|
+
throw new Error(`${prefix}: widthFraction must be a positive number`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (obj.dashArray !== undefined && !Array.isArray(obj.dashArray)) {
|
|
133
|
+
throw new Error(`${prefix}: dashArray must be an array`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (obj.alpha !== undefined && (typeof obj.alpha !== 'number' || obj.alpha < 0 || obj.alpha > 1)) {
|
|
137
|
+
throw new Error(`${prefix}: alpha must be a number between 0 and 1`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (obj.startZoom !== undefined && (typeof obj.startZoom !== 'number' || obj.startZoom < 0)) {
|
|
141
|
+
throw new Error(`${prefix}: startZoom must be a non-negative number`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (obj.endZoom !== undefined && (typeof obj.endZoom !== 'number' || obj.endZoom < 0)) {
|
|
145
|
+
throw new Error(`${prefix}: endZoom must be a non-negative number`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {Object} options
|
|
151
|
+
* @param {string} options.color - CSS color string
|
|
152
|
+
* @param {number} [options.widthFraction=1.0] - Multiplier for base line width
|
|
153
|
+
* @param {number[]} [options.dashArray] - Dash pattern for dashed lines
|
|
154
|
+
* @param {number} [options.alpha=1.0] - Opacity (0-1)
|
|
155
|
+
* @param {number} [options.startZoom] - Minimum zoom level for this style
|
|
156
|
+
* @param {number} [options.endZoom=Infinity] - Maximum zoom level for this style
|
|
157
|
+
*/
|
|
158
|
+
constructor({ color, widthFraction = 1.0, dashArray, alpha = 1.0, startZoom, endZoom = Infinity }) {
|
|
159
|
+
this.color = color;
|
|
160
|
+
this.widthFraction = widthFraction;
|
|
161
|
+
this.dashArray = dashArray;
|
|
162
|
+
this.alpha = alpha;
|
|
163
|
+
this.startZoom = startZoom;
|
|
164
|
+
this.endZoom = endZoom;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if this style is active at the given zoom level.
|
|
169
|
+
* @param {number} z - Zoom level
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
isActiveAtZoom(z) {
|
|
173
|
+
return z >= this.startZoom && z <= this.endZoom;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Serialize to plain object.
|
|
178
|
+
* @returns {Object}
|
|
179
|
+
*/
|
|
180
|
+
toJSON() {
|
|
181
|
+
const obj = { color: this.color };
|
|
182
|
+
if (this.widthFraction !== 1.0) obj.widthFraction = this.widthFraction;
|
|
183
|
+
if (this.dashArray) obj.dashArray = this.dashArray;
|
|
184
|
+
if (this.alpha !== 1.0) obj.alpha = this.alpha;
|
|
185
|
+
if (this.startZoom !== undefined) obj.startZoom = this.startZoom;
|
|
186
|
+
if (this.endZoom !== Infinity) obj.endZoom = this.endZoom;
|
|
187
|
+
return obj;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create from plain object with validation.
|
|
192
|
+
* @param {Object} obj
|
|
193
|
+
* @param {number} [defaultStartZoom=0] - Default startZoom if not specified
|
|
194
|
+
* @param {number} [index] - Optional index for error messages
|
|
195
|
+
* @returns {LineStyle}
|
|
196
|
+
*/
|
|
197
|
+
static fromJSON(obj, defaultStartZoom = 0, index) {
|
|
198
|
+
LineStyle.validateJSON(obj, index);
|
|
199
|
+
return new LineStyle({
|
|
200
|
+
...obj,
|
|
201
|
+
startZoom: obj.startZoom ?? defaultStartZoom,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
78
206
|
/**
|
|
79
207
|
* Base class for layer configurations
|
|
80
208
|
*
|
|
@@ -82,6 +210,81 @@ function templateToTemplateRegex(template) {
|
|
|
82
210
|
* and OSM data at higher zoom levels, split by zoomThreshold.
|
|
83
211
|
*/
|
|
84
212
|
export class LayerConfig {
|
|
213
|
+
/**
|
|
214
|
+
* Validate a LayerConfig configuration object.
|
|
215
|
+
* Also validates all lineStyles within the config.
|
|
216
|
+
* @param {Object} obj - The object to validate
|
|
217
|
+
* @throws {Error} If validation fails
|
|
218
|
+
*/
|
|
219
|
+
static validateJSON(obj) {
|
|
220
|
+
if (!obj || typeof obj !== 'object') {
|
|
221
|
+
throw new Error('LayerConfig: must be an object');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Validate id (required)
|
|
225
|
+
if (!obj.id || typeof obj.id !== 'string') {
|
|
226
|
+
throw new Error('LayerConfig: id must be a non-empty string');
|
|
227
|
+
}
|
|
228
|
+
if (obj.id.includes('/')) {
|
|
229
|
+
throw new Error(`LayerConfig: id cannot contain slashes: "${obj.id}"`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const id = obj.id;
|
|
233
|
+
|
|
234
|
+
// Validate zoom parameters (optional, but if provided must be valid)
|
|
235
|
+
if (obj.startZoom !== undefined && (typeof obj.startZoom !== 'number' || obj.startZoom < 0)) {
|
|
236
|
+
throw new Error(`LayerConfig "${id}": startZoom must be a non-negative number`);
|
|
237
|
+
}
|
|
238
|
+
if (obj.zoomThreshold !== undefined && (typeof obj.zoomThreshold !== 'number' || obj.zoomThreshold < 0)) {
|
|
239
|
+
throw new Error(`LayerConfig "${id}": zoomThreshold must be a non-negative number`);
|
|
240
|
+
}
|
|
241
|
+
// Check startZoom <= zoomThreshold (using defaults if not provided)
|
|
242
|
+
const startZoom = obj.startZoom ?? 0;
|
|
243
|
+
const zoomThreshold = obj.zoomThreshold ?? 5;
|
|
244
|
+
if (startZoom > zoomThreshold) {
|
|
245
|
+
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Validate lineWidthStops (optional, but if provided must be valid)
|
|
249
|
+
if (obj.lineWidthStops !== undefined) {
|
|
250
|
+
if (!obj.lineWidthStops || typeof obj.lineWidthStops !== 'object' || Array.isArray(obj.lineWidthStops)) {
|
|
251
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
252
|
+
}
|
|
253
|
+
const stopKeys = Object.keys(obj.lineWidthStops);
|
|
254
|
+
if (stopKeys.length < 2) {
|
|
255
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
256
|
+
}
|
|
257
|
+
for (const key of stopKeys) {
|
|
258
|
+
const zoom = Number(key);
|
|
259
|
+
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
260
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
261
|
+
}
|
|
262
|
+
if (typeof obj.lineWidthStops[key] !== 'number' || obj.lineWidthStops[key] <= 0) {
|
|
263
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate lineStyles (optional, but if provided must be valid)
|
|
269
|
+
if (obj.lineStyles !== undefined) {
|
|
270
|
+
if (!Array.isArray(obj.lineStyles) || obj.lineStyles.length === 0) {
|
|
271
|
+
throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
|
|
272
|
+
}
|
|
273
|
+
// Validate each lineStyle
|
|
274
|
+
for (let i = 0; i < obj.lineStyles.length; i++) {
|
|
275
|
+
LineStyle.validateJSON(obj.lineStyles[i], i);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Validate optional numeric fields
|
|
280
|
+
if (obj.delWidthFactor !== undefined && (typeof obj.delWidthFactor !== 'number' || obj.delWidthFactor <= 0)) {
|
|
281
|
+
throw new Error(`LayerConfig "${id}": delWidthFactor must be a positive number`);
|
|
282
|
+
}
|
|
283
|
+
if (obj.lineExtensionFactor !== undefined && (typeof obj.lineExtensionFactor !== 'number' || obj.lineExtensionFactor < 0)) {
|
|
284
|
+
throw new Error(`LayerConfig "${id}": lineExtensionFactor must be a non-negative number`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
85
288
|
constructor({
|
|
86
289
|
id,
|
|
87
290
|
startZoom = 0,
|
|
@@ -89,6 +292,7 @@ export class LayerConfig {
|
|
|
89
292
|
// Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
|
|
90
293
|
tileUrlTemplates = [],
|
|
91
294
|
// Line width stops: map of zoom level to line width (at least 2 entries)
|
|
295
|
+
// Note: interpolated/extrapolated line width is capped at a minimum of 0.5
|
|
92
296
|
lineWidthStops = { 1: 0.5, 10: 2.5 },
|
|
93
297
|
// Line styles array - each element describes a line to draw
|
|
94
298
|
// { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }
|
|
@@ -103,21 +307,10 @@ export class LayerConfig {
|
|
|
103
307
|
// Set to 0 to disable extension
|
|
104
308
|
lineExtensionFactor = 0.5,
|
|
105
309
|
}) {
|
|
106
|
-
if (!id || typeof id !== 'string') {
|
|
107
|
-
throw new Error('LayerConfig requires a non-empty string id');
|
|
108
|
-
}
|
|
109
|
-
if (id.includes('/')) {
|
|
110
|
-
throw new Error(`LayerConfig id cannot contain slashes: "${id}"`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
310
|
this.id = id;
|
|
114
311
|
this.startZoom = startZoom;
|
|
115
312
|
this.zoomThreshold = zoomThreshold;
|
|
116
313
|
|
|
117
|
-
if (startZoom > zoomThreshold) {
|
|
118
|
-
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
314
|
// Normalize to array
|
|
122
315
|
const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates :
|
|
123
316
|
(tileUrlTemplates ? [tileUrlTemplates] : []);
|
|
@@ -129,45 +322,15 @@ export class LayerConfig {
|
|
|
129
322
|
// Pre-compile regex patterns for matching template URLs (with {z}/{x}/{y} placeholders)
|
|
130
323
|
this._templatePatterns = templates.map(t => templateToTemplateRegex(t));
|
|
131
324
|
|
|
132
|
-
// Validate lineWidthStops
|
|
133
|
-
if (!lineWidthStops || typeof lineWidthStops !== 'object' || Array.isArray(lineWidthStops)) {
|
|
134
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
135
|
-
}
|
|
136
|
-
const stopKeys = Object.keys(lineWidthStops);
|
|
137
|
-
if (stopKeys.length < 2) {
|
|
138
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
139
|
-
}
|
|
140
|
-
for (const key of stopKeys) {
|
|
141
|
-
const zoom = Number(key);
|
|
142
|
-
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
143
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
144
|
-
}
|
|
145
|
-
if (typeof lineWidthStops[key] !== 'number' || lineWidthStops[key] <= 0) {
|
|
146
|
-
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
325
|
this.lineWidthStops = lineWidthStops;
|
|
150
|
-
|
|
151
|
-
// Validate lineStyles
|
|
152
|
-
if (!Array.isArray(lineStyles) || lineStyles.length === 0) {
|
|
153
|
-
throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
|
|
154
|
-
}
|
|
155
|
-
for (let i = 0; i < lineStyles.length; i++) {
|
|
156
|
-
const style = lineStyles[i];
|
|
157
|
-
if (!style || typeof style !== 'object') {
|
|
158
|
-
throw new Error(`LayerConfig "${id}": lineStyles[${i}] must be an object`);
|
|
159
|
-
}
|
|
160
|
-
if (!style.color || typeof style.color !== 'string') {
|
|
161
|
-
throw new Error(`LayerConfig "${id}": lineStyles[${i}].color must be a non-empty string`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
326
|
|
|
165
|
-
//
|
|
166
|
-
this.lineStyles = lineStyles.map(style =>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
327
|
+
// Convert to LineStyle instances with defaults
|
|
328
|
+
this.lineStyles = lineStyles.map(style =>
|
|
329
|
+
style instanceof LineStyle ? style : new LineStyle({
|
|
330
|
+
...style,
|
|
331
|
+
startZoom: style.startZoom ?? startZoom,
|
|
332
|
+
})
|
|
333
|
+
);
|
|
171
334
|
|
|
172
335
|
// Deletion width factor
|
|
173
336
|
this.delWidthFactor = delWidthFactor;
|
|
@@ -179,10 +342,10 @@ export class LayerConfig {
|
|
|
179
342
|
/**
|
|
180
343
|
* Get line styles active at a given zoom level
|
|
181
344
|
* @param {number} z - Zoom level
|
|
182
|
-
* @returns {
|
|
345
|
+
* @returns {LineStyle[]}
|
|
183
346
|
*/
|
|
184
347
|
getLineStylesForZoom(z) {
|
|
185
|
-
return this.lineStyles.filter(style =>
|
|
348
|
+
return this.lineStyles.filter(style => style.isActiveAtZoom(z));
|
|
186
349
|
}
|
|
187
350
|
|
|
188
351
|
/**
|
|
@@ -253,18 +416,20 @@ export class LayerConfig {
|
|
|
253
416
|
zoomThreshold: this.zoomThreshold,
|
|
254
417
|
tileUrlTemplates: this.tileUrlTemplates,
|
|
255
418
|
lineWidthStops: this.lineWidthStops,
|
|
256
|
-
lineStyles: this.lineStyles,
|
|
419
|
+
lineStyles: this.lineStyles.map(s => s.toJSON()),
|
|
257
420
|
delWidthFactor: this.delWidthFactor,
|
|
258
421
|
lineExtensionFactor: this.lineExtensionFactor,
|
|
259
422
|
};
|
|
260
423
|
}
|
|
261
424
|
|
|
262
425
|
/**
|
|
263
|
-
* Create a LayerConfig from a plain object
|
|
426
|
+
* Create a LayerConfig from a plain object with validation.
|
|
264
427
|
* @param {Object} obj
|
|
265
428
|
* @returns {LayerConfig}
|
|
429
|
+
* @throws {Error} If validation fails
|
|
266
430
|
*/
|
|
267
431
|
static fromJSON(obj) {
|
|
432
|
+
LayerConfig.validateJSON(obj);
|
|
268
433
|
return new LayerConfig(obj);
|
|
269
434
|
}
|
|
270
435
|
}
|