@india-boundary-corrector/leaflet-layer 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Leaflet TileLayer extension that automatically applies India boundary corrections.
6
6
 
7
- [Try it on JSFiddle](https://jsfiddle.net/z8d76em5/1/)
7
+ [Try it on JSFiddle](https://jsfiddle.net/ak3fpn7z/)
8
8
 
9
9
  ## Installation
10
10
 
package/dist/index.cjs CHANGED
@@ -86,12 +86,11 @@ var configs_default = [
86
86
  "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}.png",
87
87
  "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}.png"
88
88
  ],
89
- lineWidthStops: { "1": 0.5, "2": 0.75, "3": 0.75, "4": 1, "5": 1, "7": 1.5, "16": 2.5 },
89
+ lineWidthStops: { "1": 0.5, "2": 0.75, "3": 0.75, "4": 1, "5": 1, "7": 1.75, "16": 2.5 },
90
90
  lineStyles: [
91
91
  { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2.5 },
92
92
  { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, delWidthFactor: 0 },
93
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 6, endZoom: 11, widthFraction: 5, lineExtensionFactor: 0.1 },
94
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 12, widthFraction: 4, lineExtensionFactor: 0.1 },
93
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 6, lineWidthStops: { "6": 9, "10": 8, "14": 8 }, lineExtensionFactor: 0.1 },
95
94
  { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
96
95
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, delWidthFactor: 0 },
97
96
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 5, widthFraction: 0.5, dashArray: [2, 2], delWidthFactor: 2 },
@@ -114,12 +113,11 @@ var configs_default = [
114
113
  "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
115
114
  "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png"
116
115
  ],
117
- lineWidthStops: { "1": 0.75, "2": 0.75, "3": 0.75, "4": 1, "5": 1, "7": 1.5, "11": 2 },
116
+ lineWidthStops: { "1": 1, "3": 1, "4": 2, "5": 2, "6": 2.5, "7": 3, "8": 3, "10": 4, "11": 4, "12": 4 },
118
117
  lineStyles: [
119
118
  { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2.5 },
120
119
  { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, delWidthFactor: 0 },
121
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 6, endZoom: 11, widthFraction: 5, lineExtensionFactor: 0.1 },
122
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 12, widthFraction: 4, lineExtensionFactor: 0.1 },
120
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 6, lineWidthStops: { "6": 20, "8": 19, "10": 18, "12": 17 }, lineExtensionFactor: 0.1 },
123
121
  { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
124
122
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, delWidthFactor: 0 },
125
123
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 5, widthFraction: 0.5, dashArray: [4, 4], delWidthFactor: 2 },
@@ -130,15 +128,20 @@ var configs_default = [
130
128
  id: "open-topo",
131
129
  tileUrlTemplates: [
132
130
  "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
133
- "https://tile.opentopomap.org/{z}/{x}/{y}.png"
131
+ "https://tile.opentopomap.org/{z}/{x}/{y}.png",
132
+ "https://{s}.tile.top-o-map.de/{z}/{x}/{y}.png",
133
+ "https://tile.top-o-map.de/{z}/{x}/{y}.png"
134
134
  ],
135
135
  lineWidthStops: { "4": 0.75, "5": 1, "6": 1.25, "7": 1.5, "8": 1.75, "9": 1.25, "10": 1.25, "13": 1.5 },
136
136
  lineStyles: [
137
137
  { color: "rgb(83, 83, 83)", layerSuffix: "ne", startZoom: 4, endZoom: 6 },
138
138
  { color: "rgb(173, 173, 173)", layerSuffix: "osm", startZoom: 7, endZoom: 8, alpha: 0.5, widthFraction: 4 },
139
139
  { color: "rgb(83, 83, 83)", layerSuffix: "osm", startZoom: 7, endZoom: 8 },
140
+ { color: "rgb(83, 83, 83)", layerSuffix: "osm-internal", widthFraction: 0.75, startZoom: 7, endZoom: 8 },
140
141
  { color: "rgb(199, 158, 204)", layerSuffix: "osm", startZoom: 9, widthFraction: 7, alpha: 0.6, lineExtensionFactor: 0.2 },
141
- { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 }
142
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 },
143
+ { color: "rgb(199, 158, 204)", layerSuffix: "osm-internal", startZoom: 9, widthFraction: 2.25, alpha: 0.6, lineExtensionFactor: 0.2 },
144
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm-internal", startZoom: 9, widthFactor: 0.75, lineExtensionFactor: 0.2 }
142
145
  ]
143
146
  },
144
147
  {
@@ -147,9 +150,8 @@ var configs_default = [
147
150
  "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
148
151
  "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
149
152
  ],
150
- lineWidthStops: { "1": 0.5, "2": 0.6, "3": 0.7, "4": 1, "10": 3.75 },
153
+ lineWidthStops: { "3": 0.7, "4": 1, "10": 3.75 },
151
154
  lineStyles: [
152
- { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 1, endZoom: 3, delWidthFactor: 2.5, widthFraction: 1.5 },
153
155
  { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 4, delWidthFactor: 1.5 },
154
156
  { color: "rgb(160, 120, 160)", layerSuffix: "osm", startZoom: 4, widthFraction: 0.333, dashArray: [30, 2, 8, 2], delWidthFactor: 0 },
155
157
  { color: "rgb(200, 180, 200)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.45 },
@@ -173,6 +175,39 @@ var configs_default = [
173
175
  var INFINITY = -1;
174
176
  var MIN_LINE_WIDTH = 0.1;
175
177
  var DEFAULT_LINE_WIDTH = 1;
178
+ function interpolateLineWidth(zoom, lineWidthStops) {
179
+ const zooms = Object.keys(lineWidthStops).map(Number).sort((a, b2) => a - b2);
180
+ if (lineWidthStops[zoom] !== void 0) {
181
+ return lineWidthStops[zoom];
182
+ }
183
+ if (zoom < zooms[0]) {
184
+ const z1 = zooms[0];
185
+ const z2 = zooms[1];
186
+ const w1 = lineWidthStops[z1];
187
+ const w2 = lineWidthStops[z2];
188
+ const slope = (w2 - w1) / (z2 - z1);
189
+ return Math.max(MIN_LINE_WIDTH, w1 + slope * (zoom - z1));
190
+ }
191
+ if (zoom > zooms[zooms.length - 1]) {
192
+ const z1 = zooms[zooms.length - 2];
193
+ const z2 = zooms[zooms.length - 1];
194
+ const w1 = lineWidthStops[z1];
195
+ const w2 = lineWidthStops[z2];
196
+ const slope = (w2 - w1) / (z2 - z1);
197
+ return Math.max(MIN_LINE_WIDTH, w2 + slope * (zoom - z2));
198
+ }
199
+ for (let i2 = 0; i2 < zooms.length - 1; i2++) {
200
+ if (zoom > zooms[i2] && zoom < zooms[i2 + 1]) {
201
+ const z1 = zooms[i2];
202
+ const z2 = zooms[i2 + 1];
203
+ const w1 = lineWidthStops[z1];
204
+ const w2 = lineWidthStops[z2];
205
+ const t = (zoom - z1) / (z2 - z1);
206
+ return w1 + t * (w2 - w1);
207
+ }
208
+ }
209
+ return DEFAULT_LINE_WIDTH;
210
+ }
176
211
  function templateToRegex(template) {
177
212
  const groups = [];
178
213
  const hasRetina = template.includes("{r}");
@@ -239,14 +274,33 @@ function isValidColor(color) {
239
274
  if (/^[a-z]+$/.test(trimmed)) return true;
240
275
  return false;
241
276
  }
277
+ function validateLineWidthStops(lineWidthStops, prefix) {
278
+ if (!lineWidthStops || typeof lineWidthStops !== "object" || Array.isArray(lineWidthStops)) {
279
+ throw new Error(`${prefix}: lineWidthStops must be an object`);
280
+ }
281
+ const stopKeys = Object.keys(lineWidthStops);
282
+ if (stopKeys.length < 2) {
283
+ throw new Error(`${prefix}: lineWidthStops must have at least 2 entries`);
284
+ }
285
+ for (const key of stopKeys) {
286
+ const zoom = Number(key);
287
+ if (!Number.isInteger(zoom) || zoom < 0) {
288
+ throw new Error(`${prefix}: lineWidthStops keys must be non-negative integers, got "${key}"`);
289
+ }
290
+ if (typeof lineWidthStops[key] !== "number" || lineWidthStops[key] <= 0) {
291
+ throw new Error(`${prefix}: lineWidthStops values must be positive numbers`);
292
+ }
293
+ }
294
+ }
242
295
  var LineStyle = class _LineStyle {
243
296
  /**
244
297
  * Validate a LineStyle configuration object.
245
298
  * @param {Object} obj - The object to validate
246
299
  * @param {number} [index] - Optional index for error messages (when validating in an array)
300
+ * @param {boolean} [requireLineWidthStops=false] - Whether lineWidthStops is required
247
301
  * @throws {Error} If validation fails
248
302
  */
249
- static validateJSON(obj, index) {
303
+ static validateJSON(obj, index, requireLineWidthStops = false) {
250
304
  const prefix = index !== void 0 ? `lineStyles[${index}]` : "LineStyle";
251
305
  if (!obj || typeof obj !== "object") {
252
306
  throw new Error(`${prefix}: must be an object`);
@@ -281,22 +335,30 @@ var LineStyle = class _LineStyle {
281
335
  if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor < 0)) {
282
336
  throw new Error(`${prefix}: delWidthFactor must be a non-negative number`);
283
337
  }
338
+ if (requireLineWidthStops && obj.lineWidthStops === void 0) {
339
+ throw new Error(`${prefix}: lineWidthStops is required`);
340
+ }
341
+ if (obj.lineWidthStops !== void 0) {
342
+ validateLineWidthStops(obj.lineWidthStops, prefix);
343
+ }
284
344
  }
285
345
  /**
286
346
  * @param {Object} options
287
347
  * @param {string} options.color - CSS color string
288
348
  * @param {string} options.layerSuffix - Layer suffix (e.g., 'osm', 'ne', 'osm-disp')
349
+ * @param {Object<number, number>} options.lineWidthStops - Line width stops for this style
289
350
  * @param {number} [options.widthFraction=1.0] - Multiplier for base line width
290
351
  * @param {number[]} [options.dashArray] - Dash pattern for dashed lines
291
352
  * @param {number} [options.alpha=1.0] - Opacity (0-1)
292
353
  * @param {number} [options.startZoom=0] - Minimum zoom level for this style
293
354
  * @param {number} [options.endZoom=INFINITY] - Maximum zoom level for this style (INFINITY means no limit)
294
- * @param {number} [options.lineExtensionFactor=0.5] - Factor to extend lines by (multiplied by deletion line width)
355
+ * @param {number} [options.lineExtensionFactor=0.0] - Factor to extend lines by (multiplied by deletion line width)
295
356
  * @param {number} [options.delWidthFactor=1.5] - Factor to multiply line width for deletion blur
296
357
  */
297
- constructor({ color, layerSuffix, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY, lineExtensionFactor = 0, delWidthFactor = 1.5 }) {
358
+ constructor({ color, layerSuffix, lineWidthStops, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY, lineExtensionFactor = 0, delWidthFactor = 1.5 }) {
298
359
  this.color = color;
299
360
  this.layerSuffix = layerSuffix;
361
+ this.lineWidthStops = lineWidthStops;
300
362
  this.widthFraction = widthFraction;
301
363
  this.dashArray = dashArray;
302
364
  this.alpha = alpha;
@@ -305,6 +367,14 @@ var LineStyle = class _LineStyle {
305
367
  this.lineExtensionFactor = lineExtensionFactor;
306
368
  this.delWidthFactor = delWidthFactor;
307
369
  }
370
+ /**
371
+ * Get base line width for this style at a given zoom level.
372
+ * @param {number} zoom - Zoom level
373
+ * @returns {number}
374
+ */
375
+ getLineWidth(zoom) {
376
+ return interpolateLineWidth(zoom, this.lineWidthStops);
377
+ }
308
378
  /**
309
379
  * Check if this style is active at the given zoom level.
310
380
  * @param {number} z - Zoom level
@@ -321,6 +391,7 @@ var LineStyle = class _LineStyle {
321
391
  return {
322
392
  color: this.color,
323
393
  layerSuffix: this.layerSuffix,
394
+ lineWidthStops: this.lineWidthStops,
324
395
  widthFraction: this.widthFraction,
325
396
  dashArray: this.dashArray,
326
397
  alpha: this.alpha,
@@ -337,7 +408,7 @@ var LineStyle = class _LineStyle {
337
408
  * @returns {LineStyle}
338
409
  */
339
410
  static fromJSON(obj, index) {
340
- _LineStyle.validateJSON(obj, index);
411
+ _LineStyle.validateJSON(obj, index, true);
341
412
  return new _LineStyle(obj);
342
413
  }
343
414
  };
@@ -403,9 +474,13 @@ var LayerConfig = class _LayerConfig {
403
474
  this._compiledPatterns = templates.map((t) => templateToRegex(t));
404
475
  this._templatePatterns = templates.map((t) => templateToTemplateRegex(t));
405
476
  this.lineWidthStops = lineWidthStops;
406
- this.lineStyles = lineStyles.map(
407
- (style) => style instanceof LineStyle ? style : new LineStyle(style)
408
- );
477
+ this.lineStyles = lineStyles.map((style) => {
478
+ if (style instanceof LineStyle) {
479
+ return style;
480
+ }
481
+ const styleWithStops = style.lineWidthStops ? style : { ...style, lineWidthStops };
482
+ return new LineStyle(styleWithStops);
483
+ });
409
484
  }
410
485
  /**
411
486
  * Get line styles active at a given zoom level
@@ -424,45 +499,6 @@ var LayerConfig = class _LayerConfig {
424
499
  const activeStyles = this.getLineStylesForZoom(z2);
425
500
  return [...new Set(activeStyles.map((s) => s.layerSuffix))];
426
501
  }
427
- /**
428
- * Interpolate or extrapolate line width for a given zoom level.
429
- * Uses the lineWidthStops map to calculate the appropriate width.
430
- * @param {number} zoom - Zoom level
431
- * @returns {number}
432
- */
433
- getLineWidth(zoom) {
434
- const zooms = Object.keys(this.lineWidthStops).map(Number).sort((a, b2) => a - b2);
435
- if (this.lineWidthStops[zoom] !== void 0) {
436
- return this.lineWidthStops[zoom];
437
- }
438
- if (zoom < zooms[0]) {
439
- const z1 = zooms[0];
440
- const z2 = zooms[1];
441
- const w1 = this.lineWidthStops[z1];
442
- const w2 = this.lineWidthStops[z2];
443
- const slope = (w2 - w1) / (z2 - z1);
444
- return Math.max(MIN_LINE_WIDTH, w1 + slope * (zoom - z1));
445
- }
446
- if (zoom > zooms[zooms.length - 1]) {
447
- const z1 = zooms[zooms.length - 2];
448
- const z2 = zooms[zooms.length - 1];
449
- const w1 = this.lineWidthStops[z1];
450
- const w2 = this.lineWidthStops[z2];
451
- const slope = (w2 - w1) / (z2 - z1);
452
- return Math.max(MIN_LINE_WIDTH, w2 + slope * (zoom - z2));
453
- }
454
- for (let i2 = 0; i2 < zooms.length - 1; i2++) {
455
- if (zoom > zooms[i2] && zoom < zooms[i2 + 1]) {
456
- const z1 = zooms[i2];
457
- const z2 = zooms[i2 + 1];
458
- const w1 = this.lineWidthStops[z1];
459
- const w2 = this.lineWidthStops[z2];
460
- const t = (zoom - z1) / (z2 - z1);
461
- return w1 + t * (w2 - w1);
462
- }
463
- }
464
- return DEFAULT_LINE_WIDTH;
465
- }
466
502
  /**
467
503
  * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
468
504
  * @param {string | string[]} templates - Single template URL or array of template URLs
@@ -2916,12 +2952,11 @@ var configs_default2 = [
2916
2952
  "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}.png",
2917
2953
  "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}.png"
2918
2954
  ],
2919
- lineWidthStops: { "1": 0.5, "2": 0.75, "3": 0.75, "4": 1, "5": 1, "7": 1.5, "16": 2.5 },
2955
+ lineWidthStops: { "1": 0.5, "2": 0.75, "3": 0.75, "4": 1, "5": 1, "7": 1.75, "16": 2.5 },
2920
2956
  lineStyles: [
2921
2957
  { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2.5 },
2922
2958
  { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, delWidthFactor: 0 },
2923
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 6, endZoom: 11, widthFraction: 5, lineExtensionFactor: 0.1 },
2924
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.2, startZoom: 12, widthFraction: 4, lineExtensionFactor: 0.1 },
2959
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 6, lineWidthStops: { "6": 9, "10": 8, "14": 8 }, lineExtensionFactor: 0.1 },
2925
2960
  { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
2926
2961
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, delWidthFactor: 0 },
2927
2962
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 5, widthFraction: 0.5, dashArray: [2, 2], delWidthFactor: 2 },
@@ -2944,12 +2979,11 @@ var configs_default2 = [
2944
2979
  "https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
2945
2980
  "https://basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png"
2946
2981
  ],
2947
- lineWidthStops: { "1": 0.75, "2": 0.75, "3": 0.75, "4": 1, "5": 1, "7": 1.5, "11": 2 },
2982
+ lineWidthStops: { "1": 1, "3": 1, "4": 2, "5": 2, "6": 2.5, "7": 3, "8": 3, "10": 4, "11": 4, "12": 4 },
2948
2983
  lineStyles: [
2949
2984
  { color: "rgb(235, 214, 214)", layerSuffix: "ne", endZoom: 4, lineExtensionFactor: 0.1, delWidthFactor: 2.5 },
2950
2985
  { color: "rgb(235, 214, 214)", layerSuffix: "ne-disp", endZoom: 4, delWidthFactor: 0 },
2951
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 6, endZoom: 11, widthFraction: 5, lineExtensionFactor: 0.1 },
2952
- { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 12, widthFraction: 4, lineExtensionFactor: 0.1 },
2986
+ { color: "rgb(235, 214, 214)", layerSuffix: "osm", alpha: 0.1, startZoom: 6, lineWidthStops: { "6": 20, "8": 19, "10": 18, "12": 17 }, lineExtensionFactor: 0.1 },
2953
2987
  { color: "rgb(235, 214, 214)", layerSuffix: "osm", startZoom: 5, lineExtensionFactor: 0.1 },
2954
2988
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-disp", startZoom: 5, delWidthFactor: 0 },
2955
2989
  { color: "rgb(235, 214, 214)", layerSuffix: "osm-internal", startZoom: 5, widthFraction: 0.5, dashArray: [4, 4], delWidthFactor: 2 },
@@ -2960,15 +2994,20 @@ var configs_default2 = [
2960
2994
  id: "open-topo",
2961
2995
  tileUrlTemplates: [
2962
2996
  "https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png",
2963
- "https://tile.opentopomap.org/{z}/{x}/{y}.png"
2997
+ "https://tile.opentopomap.org/{z}/{x}/{y}.png",
2998
+ "https://{s}.tile.top-o-map.de/{z}/{x}/{y}.png",
2999
+ "https://tile.top-o-map.de/{z}/{x}/{y}.png"
2964
3000
  ],
2965
3001
  lineWidthStops: { "4": 0.75, "5": 1, "6": 1.25, "7": 1.5, "8": 1.75, "9": 1.25, "10": 1.25, "13": 1.5 },
2966
3002
  lineStyles: [
2967
3003
  { color: "rgb(83, 83, 83)", layerSuffix: "ne", startZoom: 4, endZoom: 6 },
2968
3004
  { color: "rgb(173, 173, 173)", layerSuffix: "osm", startZoom: 7, endZoom: 8, alpha: 0.5, widthFraction: 4 },
2969
3005
  { color: "rgb(83, 83, 83)", layerSuffix: "osm", startZoom: 7, endZoom: 8 },
3006
+ { color: "rgb(83, 83, 83)", layerSuffix: "osm-internal", widthFraction: 0.75, startZoom: 7, endZoom: 8 },
2970
3007
  { color: "rgb(199, 158, 204)", layerSuffix: "osm", startZoom: 9, widthFraction: 7, alpha: 0.6, lineExtensionFactor: 0.2 },
2971
- { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 }
3008
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm", startZoom: 9, lineExtensionFactor: 0.2 },
3009
+ { color: "rgb(199, 158, 204)", layerSuffix: "osm-internal", startZoom: 9, widthFraction: 2.25, alpha: 0.6, lineExtensionFactor: 0.2 },
3010
+ { color: "rgb(175, 41, 203)", layerSuffix: "osm-internal", startZoom: 9, widthFactor: 0.75, lineExtensionFactor: 0.2 }
2972
3011
  ]
2973
3012
  },
2974
3013
  {
@@ -2977,9 +3016,8 @@ var configs_default2 = [
2977
3016
  "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
2978
3017
  "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
2979
3018
  ],
2980
- lineWidthStops: { "1": 0.5, "2": 0.6, "3": 0.7, "4": 1, "10": 3.75 },
3019
+ lineWidthStops: { "3": 0.7, "4": 1, "10": 3.75 },
2981
3020
  lineStyles: [
2982
- { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 1, endZoom: 3, delWidthFactor: 2.5, widthFraction: 1.5 },
2983
3021
  { color: "rgb(200, 180, 200)", layerSuffix: "osm", startZoom: 4, delWidthFactor: 1.5 },
2984
3022
  { color: "rgb(160, 120, 160)", layerSuffix: "osm", startZoom: 4, widthFraction: 0.333, dashArray: [30, 2, 8, 2], delWidthFactor: 0 },
2985
3023
  { color: "rgb(200, 180, 200)", layerSuffix: "osm-internal", startZoom: 4, widthFraction: 0.45 },
@@ -3003,6 +3041,39 @@ var configs_default2 = [
3003
3041
  var INFINITY2 = -1;
3004
3042
  var MIN_LINE_WIDTH2 = 0.1;
3005
3043
  var DEFAULT_LINE_WIDTH2 = 1;
3044
+ function interpolateLineWidth2(zoom, lineWidthStops) {
3045
+ const zooms = Object.keys(lineWidthStops).map(Number).sort((a, b2) => a - b2);
3046
+ if (lineWidthStops[zoom] !== void 0) {
3047
+ return lineWidthStops[zoom];
3048
+ }
3049
+ if (zoom < zooms[0]) {
3050
+ const z1 = zooms[0];
3051
+ const z2 = zooms[1];
3052
+ const w1 = lineWidthStops[z1];
3053
+ const w2 = lineWidthStops[z2];
3054
+ const slope = (w2 - w1) / (z2 - z1);
3055
+ return Math.max(MIN_LINE_WIDTH2, w1 + slope * (zoom - z1));
3056
+ }
3057
+ if (zoom > zooms[zooms.length - 1]) {
3058
+ const z1 = zooms[zooms.length - 2];
3059
+ const z2 = zooms[zooms.length - 1];
3060
+ const w1 = lineWidthStops[z1];
3061
+ const w2 = lineWidthStops[z2];
3062
+ const slope = (w2 - w1) / (z2 - z1);
3063
+ return Math.max(MIN_LINE_WIDTH2, w2 + slope * (zoom - z2));
3064
+ }
3065
+ for (let i2 = 0; i2 < zooms.length - 1; i2++) {
3066
+ if (zoom > zooms[i2] && zoom < zooms[i2 + 1]) {
3067
+ const z1 = zooms[i2];
3068
+ const z2 = zooms[i2 + 1];
3069
+ const w1 = lineWidthStops[z1];
3070
+ const w2 = lineWidthStops[z2];
3071
+ const t = (zoom - z1) / (z2 - z1);
3072
+ return w1 + t * (w2 - w1);
3073
+ }
3074
+ }
3075
+ return DEFAULT_LINE_WIDTH2;
3076
+ }
3006
3077
  function templateToRegex2(template) {
3007
3078
  const groups = [];
3008
3079
  const hasRetina = template.includes("{r}");
@@ -3069,14 +3140,33 @@ function isValidColor2(color) {
3069
3140
  if (/^[a-z]+$/.test(trimmed)) return true;
3070
3141
  return false;
3071
3142
  }
3143
+ function validateLineWidthStops2(lineWidthStops, prefix) {
3144
+ if (!lineWidthStops || typeof lineWidthStops !== "object" || Array.isArray(lineWidthStops)) {
3145
+ throw new Error(`${prefix}: lineWidthStops must be an object`);
3146
+ }
3147
+ const stopKeys = Object.keys(lineWidthStops);
3148
+ if (stopKeys.length < 2) {
3149
+ throw new Error(`${prefix}: lineWidthStops must have at least 2 entries`);
3150
+ }
3151
+ for (const key of stopKeys) {
3152
+ const zoom = Number(key);
3153
+ if (!Number.isInteger(zoom) || zoom < 0) {
3154
+ throw new Error(`${prefix}: lineWidthStops keys must be non-negative integers, got "${key}"`);
3155
+ }
3156
+ if (typeof lineWidthStops[key] !== "number" || lineWidthStops[key] <= 0) {
3157
+ throw new Error(`${prefix}: lineWidthStops values must be positive numbers`);
3158
+ }
3159
+ }
3160
+ }
3072
3161
  var LineStyle2 = class _LineStyle2 {
3073
3162
  /**
3074
3163
  * Validate a LineStyle configuration object.
3075
3164
  * @param {Object} obj - The object to validate
3076
3165
  * @param {number} [index] - Optional index for error messages (when validating in an array)
3166
+ * @param {boolean} [requireLineWidthStops=false] - Whether lineWidthStops is required
3077
3167
  * @throws {Error} If validation fails
3078
3168
  */
3079
- static validateJSON(obj, index) {
3169
+ static validateJSON(obj, index, requireLineWidthStops = false) {
3080
3170
  const prefix = index !== void 0 ? `lineStyles[${index}]` : "LineStyle";
3081
3171
  if (!obj || typeof obj !== "object") {
3082
3172
  throw new Error(`${prefix}: must be an object`);
@@ -3111,22 +3201,30 @@ var LineStyle2 = class _LineStyle2 {
3111
3201
  if (obj.delWidthFactor !== void 0 && (typeof obj.delWidthFactor !== "number" || obj.delWidthFactor < 0)) {
3112
3202
  throw new Error(`${prefix}: delWidthFactor must be a non-negative number`);
3113
3203
  }
3204
+ if (requireLineWidthStops && obj.lineWidthStops === void 0) {
3205
+ throw new Error(`${prefix}: lineWidthStops is required`);
3206
+ }
3207
+ if (obj.lineWidthStops !== void 0) {
3208
+ validateLineWidthStops2(obj.lineWidthStops, prefix);
3209
+ }
3114
3210
  }
3115
3211
  /**
3116
3212
  * @param {Object} options
3117
3213
  * @param {string} options.color - CSS color string
3118
3214
  * @param {string} options.layerSuffix - Layer suffix (e.g., 'osm', 'ne', 'osm-disp')
3215
+ * @param {Object<number, number>} options.lineWidthStops - Line width stops for this style
3119
3216
  * @param {number} [options.widthFraction=1.0] - Multiplier for base line width
3120
3217
  * @param {number[]} [options.dashArray] - Dash pattern for dashed lines
3121
3218
  * @param {number} [options.alpha=1.0] - Opacity (0-1)
3122
3219
  * @param {number} [options.startZoom=0] - Minimum zoom level for this style
3123
3220
  * @param {number} [options.endZoom=INFINITY] - Maximum zoom level for this style (INFINITY means no limit)
3124
- * @param {number} [options.lineExtensionFactor=0.5] - Factor to extend lines by (multiplied by deletion line width)
3221
+ * @param {number} [options.lineExtensionFactor=0.0] - Factor to extend lines by (multiplied by deletion line width)
3125
3222
  * @param {number} [options.delWidthFactor=1.5] - Factor to multiply line width for deletion blur
3126
3223
  */
3127
- constructor({ color, layerSuffix, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY2, lineExtensionFactor = 0, delWidthFactor = 1.5 }) {
3224
+ constructor({ color, layerSuffix, lineWidthStops, widthFraction = 1, dashArray, alpha = 1, startZoom = 0, endZoom = INFINITY2, lineExtensionFactor = 0, delWidthFactor = 1.5 }) {
3128
3225
  this.color = color;
3129
3226
  this.layerSuffix = layerSuffix;
3227
+ this.lineWidthStops = lineWidthStops;
3130
3228
  this.widthFraction = widthFraction;
3131
3229
  this.dashArray = dashArray;
3132
3230
  this.alpha = alpha;
@@ -3135,6 +3233,14 @@ var LineStyle2 = class _LineStyle2 {
3135
3233
  this.lineExtensionFactor = lineExtensionFactor;
3136
3234
  this.delWidthFactor = delWidthFactor;
3137
3235
  }
3236
+ /**
3237
+ * Get base line width for this style at a given zoom level.
3238
+ * @param {number} zoom - Zoom level
3239
+ * @returns {number}
3240
+ */
3241
+ getLineWidth(zoom) {
3242
+ return interpolateLineWidth2(zoom, this.lineWidthStops);
3243
+ }
3138
3244
  /**
3139
3245
  * Check if this style is active at the given zoom level.
3140
3246
  * @param {number} z - Zoom level
@@ -3151,6 +3257,7 @@ var LineStyle2 = class _LineStyle2 {
3151
3257
  return {
3152
3258
  color: this.color,
3153
3259
  layerSuffix: this.layerSuffix,
3260
+ lineWidthStops: this.lineWidthStops,
3154
3261
  widthFraction: this.widthFraction,
3155
3262
  dashArray: this.dashArray,
3156
3263
  alpha: this.alpha,
@@ -3167,7 +3274,7 @@ var LineStyle2 = class _LineStyle2 {
3167
3274
  * @returns {LineStyle}
3168
3275
  */
3169
3276
  static fromJSON(obj, index) {
3170
- _LineStyle2.validateJSON(obj, index);
3277
+ _LineStyle2.validateJSON(obj, index, true);
3171
3278
  return new _LineStyle2(obj);
3172
3279
  }
3173
3280
  };
@@ -3233,9 +3340,13 @@ var LayerConfig2 = class _LayerConfig2 {
3233
3340
  this._compiledPatterns = templates.map((t) => templateToRegex2(t));
3234
3341
  this._templatePatterns = templates.map((t) => templateToTemplateRegex2(t));
3235
3342
  this.lineWidthStops = lineWidthStops;
3236
- this.lineStyles = lineStyles.map(
3237
- (style) => style instanceof LineStyle2 ? style : new LineStyle2(style)
3238
- );
3343
+ this.lineStyles = lineStyles.map((style) => {
3344
+ if (style instanceof LineStyle2) {
3345
+ return style;
3346
+ }
3347
+ const styleWithStops = style.lineWidthStops ? style : { ...style, lineWidthStops };
3348
+ return new LineStyle2(styleWithStops);
3349
+ });
3239
3350
  }
3240
3351
  /**
3241
3352
  * Get line styles active at a given zoom level
@@ -3254,45 +3365,6 @@ var LayerConfig2 = class _LayerConfig2 {
3254
3365
  const activeStyles = this.getLineStylesForZoom(z2);
3255
3366
  return [...new Set(activeStyles.map((s) => s.layerSuffix))];
3256
3367
  }
3257
- /**
3258
- * Interpolate or extrapolate line width for a given zoom level.
3259
- * Uses the lineWidthStops map to calculate the appropriate width.
3260
- * @param {number} zoom - Zoom level
3261
- * @returns {number}
3262
- */
3263
- getLineWidth(zoom) {
3264
- const zooms = Object.keys(this.lineWidthStops).map(Number).sort((a, b2) => a - b2);
3265
- if (this.lineWidthStops[zoom] !== void 0) {
3266
- return this.lineWidthStops[zoom];
3267
- }
3268
- if (zoom < zooms[0]) {
3269
- const z1 = zooms[0];
3270
- const z2 = zooms[1];
3271
- const w1 = this.lineWidthStops[z1];
3272
- const w2 = this.lineWidthStops[z2];
3273
- const slope = (w2 - w1) / (z2 - z1);
3274
- return Math.max(MIN_LINE_WIDTH2, w1 + slope * (zoom - z1));
3275
- }
3276
- if (zoom > zooms[zooms.length - 1]) {
3277
- const z1 = zooms[zooms.length - 2];
3278
- const z2 = zooms[zooms.length - 1];
3279
- const w1 = this.lineWidthStops[z1];
3280
- const w2 = this.lineWidthStops[z2];
3281
- const slope = (w2 - w1) / (z2 - z1);
3282
- return Math.max(MIN_LINE_WIDTH2, w2 + slope * (zoom - z2));
3283
- }
3284
- for (let i2 = 0; i2 < zooms.length - 1; i2++) {
3285
- if (zoom > zooms[i2] && zoom < zooms[i2 + 1]) {
3286
- const z1 = zooms[i2];
3287
- const z2 = zooms[i2 + 1];
3288
- const w1 = this.lineWidthStops[z1];
3289
- const w2 = this.lineWidthStops[z2];
3290
- const t = (zoom - z1) / (z2 - z1);
3291
- return w1 + t * (w2 - w1);
3292
- }
3293
- }
3294
- return DEFAULT_LINE_WIDTH2;
3295
- }
3296
3368
  /**
3297
3369
  * Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
3298
3370
  * @param {string | string[]} templates - Single template URL or array of template URLs
@@ -3785,12 +3857,13 @@ var _TileFixer = class _TileFixer2 {
3785
3857
  const canvas = this._canvas;
3786
3858
  const ctx = canvas.getContext("2d", { willReadFrequently: true });
3787
3859
  ctx.drawImage(imageBitmap, 0, 0, tileSize, tileSize);
3788
- const baseLineWidth = layerConfig.getLineWidth(zoom);
3789
3860
  const delLineWidthBySuffix = {};
3790
3861
  for (const suffix of layerSuffixes) {
3791
3862
  const stylesForSuffix = activeLineStyles.filter((s) => s.layerSuffix === suffix);
3792
- const maxProduct = Math.max(...stylesForSuffix.map((s) => s.widthFraction * s.delWidthFactor));
3793
- delLineWidthBySuffix[suffix] = baseLineWidth * maxProduct;
3863
+ const maxDelWidth = Math.max(...stylesForSuffix.map(
3864
+ (s) => s.getLineWidth(zoom) * s.widthFraction * s.delWidthFactor
3865
+ ));
3866
+ delLineWidthBySuffix[suffix] = maxDelWidth;
3794
3867
  }
3795
3868
  for (const suffix of layerSuffixes) {
3796
3869
  const delLineWidth = delLineWidthBySuffix[suffix];
@@ -3805,7 +3878,7 @@ var _TileFixer = class _TileFixer2 {
3805
3878
  }
3806
3879
  }
3807
3880
  for (const style of activeLineStyles) {
3808
- const { color, layerSuffix, widthFraction, dashArray, alpha, lineExtensionFactor } = style;
3881
+ const { color, widthFraction, dashArray, alpha, lineExtensionFactor, layerSuffix } = style;
3809
3882
  const addLayerName = `to-add-${layerSuffix}`;
3810
3883
  let addFeatures = corrections[addLayerName] || [];
3811
3884
  if (addFeatures.length > 0) {
@@ -3813,7 +3886,7 @@ var _TileFixer = class _TileFixer2 {
3813
3886
  if (lineExtensionFactor > 0) {
3814
3887
  addFeatures = extendFeaturesByFactor(addFeatures, lineExtensionFactor, delLineWidth, tileSize);
3815
3888
  }
3816
- const lineWidth = baseLineWidth * widthFraction;
3889
+ const lineWidth = style.getLineWidth(zoom) * widthFraction;
3817
3890
  drawFeatures(ctx, addFeatures, color, lineWidth, tileSize, dashArray, alpha);
3818
3891
  }
3819
3892
  }