@india-boundary-corrector/layer-configs 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 +11 -7
- package/dist/index.cjs +94 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +93 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/configs.json +12 -10
- package/src/index.d.ts +78 -14
- package/src/index.js +2 -2
- package/src/layerconfig.js +109 -57
package/src/index.d.ts
CHANGED
|
@@ -9,14 +9,24 @@ export const INFINITY: -1;
|
|
|
9
9
|
*/
|
|
10
10
|
export const MIN_LINE_WIDTH: number;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Interpolate or extrapolate line width for a given zoom level.
|
|
14
|
+
* @param zoom - Zoom level
|
|
15
|
+
* @param lineWidthStops - Map of zoom level to line width (at least 2 entries)
|
|
16
|
+
* @returns Interpolated/extrapolated line width (minimum MIN_LINE_WIDTH)
|
|
17
|
+
*/
|
|
18
|
+
export function interpolateLineWidth(zoom: number, lineWidthStops: Record<number, number>): number;
|
|
19
|
+
|
|
12
20
|
/**
|
|
13
21
|
* Line style definition for drawing boundary lines
|
|
14
22
|
*/
|
|
15
|
-
export interface
|
|
23
|
+
export interface LineStyleOptions {
|
|
16
24
|
/** Line color (CSS color string) */
|
|
17
25
|
color: string;
|
|
18
26
|
/** Layer suffix (e.g., 'osm', 'ne', 'osm-disp') - determines PMTiles layer */
|
|
19
27
|
layerSuffix: string;
|
|
28
|
+
/** Line width stops: map of zoom level to line width (required) */
|
|
29
|
+
lineWidthStops: Record<number, number>;
|
|
20
30
|
/** Width as fraction of base line width (default: 1.0) */
|
|
21
31
|
widthFraction?: number;
|
|
22
32
|
/** Dash pattern array (omit for solid line) */
|
|
@@ -34,7 +44,59 @@ export interface LineStyle {
|
|
|
34
44
|
}
|
|
35
45
|
|
|
36
46
|
/**
|
|
37
|
-
*
|
|
47
|
+
* Line style class for drawing boundary lines
|
|
48
|
+
*/
|
|
49
|
+
export class LineStyle {
|
|
50
|
+
readonly color: string;
|
|
51
|
+
readonly layerSuffix: string;
|
|
52
|
+
readonly lineWidthStops: Record<number, number>;
|
|
53
|
+
readonly widthFraction: number;
|
|
54
|
+
readonly dashArray?: number[];
|
|
55
|
+
readonly alpha: number;
|
|
56
|
+
readonly startZoom: number;
|
|
57
|
+
readonly endZoom: number;
|
|
58
|
+
readonly lineExtensionFactor: number;
|
|
59
|
+
readonly delWidthFactor: number;
|
|
60
|
+
|
|
61
|
+
constructor(options: LineStyleOptions);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get base line width for this style at a given zoom level.
|
|
65
|
+
* @param zoom - Zoom level
|
|
66
|
+
*/
|
|
67
|
+
getLineWidth(zoom: number): number;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if this style is active at the given zoom level.
|
|
71
|
+
* @param z - Zoom level
|
|
72
|
+
*/
|
|
73
|
+
isActiveAtZoom(z: number): boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Serialize to plain object.
|
|
77
|
+
*/
|
|
78
|
+
toJSON(): LineStyleOptions;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create from plain object with validation.
|
|
82
|
+
*/
|
|
83
|
+
static fromJSON(obj: LineStyleOptions, index?: number): LineStyle;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Validate a LineStyle configuration object.
|
|
87
|
+
*/
|
|
88
|
+
static validateJSON(obj: unknown, index?: number, requireLineWidthStops?: boolean): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Line style input for LayerConfig (lineWidthStops is optional, inherited from config if not provided)
|
|
93
|
+
*/
|
|
94
|
+
export type LineStyleInput = Omit<LineStyleOptions, 'lineWidthStops'> & {
|
|
95
|
+
lineWidthStops?: Record<number, number>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Configuration options for LayerConfig input (constructor)
|
|
38
100
|
*/
|
|
39
101
|
export interface LayerConfigOptions {
|
|
40
102
|
/** Unique identifier for this config */
|
|
@@ -43,8 +105,18 @@ export interface LayerConfigOptions {
|
|
|
43
105
|
tileUrlTemplates?: string | string[];
|
|
44
106
|
/** Line width stops: map of zoom level to line width (at least 2 entries) */
|
|
45
107
|
lineWidthStops?: Record<number, number>;
|
|
46
|
-
/** Line styles array - lines are drawn in order (required) */
|
|
47
|
-
lineStyles:
|
|
108
|
+
/** Line styles array - lines are drawn in order (required). lineWidthStops is optional per style (inherited from config if not provided) */
|
|
109
|
+
lineStyles: LineStyleInput[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Serialized LayerConfig (from toJSON)
|
|
114
|
+
*/
|
|
115
|
+
export interface LayerConfigJSON {
|
|
116
|
+
id: string;
|
|
117
|
+
tileUrlTemplates: string[];
|
|
118
|
+
lineWidthStops: Record<number, number>;
|
|
119
|
+
lineStyles: LineStyleOptions[];
|
|
48
120
|
}
|
|
49
121
|
|
|
50
122
|
/**
|
|
@@ -70,14 +142,6 @@ export class LayerConfig {
|
|
|
70
142
|
*/
|
|
71
143
|
getLayerSuffixesForZoom(z: number): string[];
|
|
72
144
|
|
|
73
|
-
/**
|
|
74
|
-
* Interpolate or extrapolate line width for a given zoom level.
|
|
75
|
-
* Uses the lineWidthStops map to calculate the appropriate width.
|
|
76
|
-
* @param zoom - Zoom level
|
|
77
|
-
* @returns Interpolated/extrapolated line width (minimum MIN_LINE_WIDTH)
|
|
78
|
-
*/
|
|
79
|
-
getLineWidth(zoom: number): number;
|
|
80
|
-
|
|
81
145
|
/**
|
|
82
146
|
* Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
|
|
83
147
|
* @param templates - Single template URL or array of template URLs
|
|
@@ -100,12 +164,12 @@ export class LayerConfig {
|
|
|
100
164
|
/**
|
|
101
165
|
* Serialize the config to a plain object for postMessage
|
|
102
166
|
*/
|
|
103
|
-
toJSON():
|
|
167
|
+
toJSON(): LayerConfigJSON;
|
|
104
168
|
|
|
105
169
|
/**
|
|
106
170
|
* Create a LayerConfig from a plain object (e.g., from postMessage)
|
|
107
171
|
*/
|
|
108
|
-
static fromJSON(obj: LayerConfigOptions): LayerConfig;
|
|
172
|
+
static fromJSON(obj: LayerConfigOptions | LayerConfigJSON): LayerConfig;
|
|
109
173
|
}
|
|
110
174
|
|
|
111
175
|
/**
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import configsJson from './configs.json' with { type: 'json' };
|
|
2
|
-
import { LayerConfig, LineStyle, INFINITY, MIN_LINE_WIDTH } from './layerconfig.js';
|
|
2
|
+
import { LayerConfig, LineStyle, INFINITY, MIN_LINE_WIDTH, interpolateLineWidth } from './layerconfig.js';
|
|
3
3
|
|
|
4
|
-
export { LayerConfig, LineStyle, INFINITY, MIN_LINE_WIDTH } from './layerconfig.js';
|
|
4
|
+
export { LayerConfig, LineStyle, INFINITY, MIN_LINE_WIDTH, interpolateLineWidth } from './layerconfig.js';
|
|
5
5
|
|
|
6
6
|
// Export raw configs for testing/inspection
|
|
7
7
|
export { configsJson };
|
package/src/layerconfig.js
CHANGED
|
@@ -14,6 +14,56 @@ export const MIN_LINE_WIDTH = 0.1;
|
|
|
14
14
|
*/
|
|
15
15
|
const DEFAULT_LINE_WIDTH = 1;
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Interpolate or extrapolate line width for a given zoom level.
|
|
19
|
+
* Uses a lineWidthStops map to calculate the appropriate width.
|
|
20
|
+
* @param {number} zoom - Zoom level
|
|
21
|
+
* @param {Object<number, number>} lineWidthStops - Map of zoom level to line width (at least 2 entries)
|
|
22
|
+
* @returns {number}
|
|
23
|
+
*/
|
|
24
|
+
export function interpolateLineWidth(zoom, lineWidthStops) {
|
|
25
|
+
const zooms = Object.keys(lineWidthStops).map(Number).sort((a, b) => a - b);
|
|
26
|
+
|
|
27
|
+
// Exact match
|
|
28
|
+
if (lineWidthStops[zoom] !== undefined) {
|
|
29
|
+
return lineWidthStops[zoom];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Below lowest zoom - extrapolate
|
|
33
|
+
if (zoom < zooms[0]) {
|
|
34
|
+
const z1 = zooms[0];
|
|
35
|
+
const z2 = zooms[1];
|
|
36
|
+
const w1 = lineWidthStops[z1];
|
|
37
|
+
const w2 = lineWidthStops[z2];
|
|
38
|
+
const slope = (w2 - w1) / (z2 - z1);
|
|
39
|
+
return Math.max(MIN_LINE_WIDTH, w1 + slope * (zoom - z1));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Above highest zoom - extrapolate
|
|
43
|
+
if (zoom > zooms[zooms.length - 1]) {
|
|
44
|
+
const z1 = zooms[zooms.length - 2];
|
|
45
|
+
const z2 = zooms[zooms.length - 1];
|
|
46
|
+
const w1 = lineWidthStops[z1];
|
|
47
|
+
const w2 = lineWidthStops[z2];
|
|
48
|
+
const slope = (w2 - w1) / (z2 - z1);
|
|
49
|
+
return Math.max(MIN_LINE_WIDTH, w2 + slope * (zoom - z2));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Interpolate between two stops
|
|
53
|
+
for (let i = 0; i < zooms.length - 1; i++) {
|
|
54
|
+
if (zoom > zooms[i] && zoom < zooms[i + 1]) {
|
|
55
|
+
const z1 = zooms[i];
|
|
56
|
+
const z2 = zooms[i + 1];
|
|
57
|
+
const w1 = lineWidthStops[z1];
|
|
58
|
+
const w2 = lineWidthStops[z2];
|
|
59
|
+
const t = (zoom - z1) / (z2 - z1);
|
|
60
|
+
return w1 + t * (w2 - w1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return DEFAULT_LINE_WIDTH; // fallback
|
|
65
|
+
}
|
|
66
|
+
|
|
17
67
|
/**
|
|
18
68
|
* Convert a tile URL template to a regex pattern and capture group names.
|
|
19
69
|
* Supports {z}, {x}, {y}, {s} (Leaflet subdomain), {a-c}/{1-4} (OpenLayers subdomain), and {r} (retina) placeholders.
|
|
@@ -144,6 +194,31 @@ function isValidColor(color) {
|
|
|
144
194
|
return false;
|
|
145
195
|
}
|
|
146
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Validate lineWidthStops object.
|
|
199
|
+
* @param {Object} lineWidthStops - The lineWidthStops to validate
|
|
200
|
+
* @param {string} prefix - Error message prefix
|
|
201
|
+
* @throws {Error} If validation fails
|
|
202
|
+
*/
|
|
203
|
+
function validateLineWidthStops(lineWidthStops, prefix) {
|
|
204
|
+
if (!lineWidthStops || typeof lineWidthStops !== 'object' || Array.isArray(lineWidthStops)) {
|
|
205
|
+
throw new Error(`${prefix}: lineWidthStops must be an object`);
|
|
206
|
+
}
|
|
207
|
+
const stopKeys = Object.keys(lineWidthStops);
|
|
208
|
+
if (stopKeys.length < 2) {
|
|
209
|
+
throw new Error(`${prefix}: lineWidthStops must have at least 2 entries`);
|
|
210
|
+
}
|
|
211
|
+
for (const key of stopKeys) {
|
|
212
|
+
const zoom = Number(key);
|
|
213
|
+
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
214
|
+
throw new Error(`${prefix}: lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
215
|
+
}
|
|
216
|
+
if (typeof lineWidthStops[key] !== 'number' || lineWidthStops[key] <= 0) {
|
|
217
|
+
throw new Error(`${prefix}: lineWidthStops values must be positive numbers`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
147
222
|
/**
|
|
148
223
|
* Represents a line style for drawing boundaries.
|
|
149
224
|
*/
|
|
@@ -152,9 +227,10 @@ export class LineStyle {
|
|
|
152
227
|
* Validate a LineStyle configuration object.
|
|
153
228
|
* @param {Object} obj - The object to validate
|
|
154
229
|
* @param {number} [index] - Optional index for error messages (when validating in an array)
|
|
230
|
+
* @param {boolean} [requireLineWidthStops=false] - Whether lineWidthStops is required
|
|
155
231
|
* @throws {Error} If validation fails
|
|
156
232
|
*/
|
|
157
|
-
static validateJSON(obj, index) {
|
|
233
|
+
static validateJSON(obj, index, requireLineWidthStops = false) {
|
|
158
234
|
const prefix = index !== undefined ? `lineStyles[${index}]` : 'LineStyle';
|
|
159
235
|
|
|
160
236
|
if (!obj || typeof obj !== 'object') {
|
|
@@ -200,23 +276,33 @@ export class LineStyle {
|
|
|
200
276
|
if (obj.delWidthFactor !== undefined && (typeof obj.delWidthFactor !== 'number' || obj.delWidthFactor < 0)) {
|
|
201
277
|
throw new Error(`${prefix}: delWidthFactor must be a non-negative number`);
|
|
202
278
|
}
|
|
279
|
+
|
|
280
|
+
if (requireLineWidthStops && obj.lineWidthStops === undefined) {
|
|
281
|
+
throw new Error(`${prefix}: lineWidthStops is required`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (obj.lineWidthStops !== undefined) {
|
|
285
|
+
validateLineWidthStops(obj.lineWidthStops, prefix);
|
|
286
|
+
}
|
|
203
287
|
}
|
|
204
288
|
|
|
205
289
|
/**
|
|
206
290
|
* @param {Object} options
|
|
207
291
|
* @param {string} options.color - CSS color string
|
|
208
292
|
* @param {string} options.layerSuffix - Layer suffix (e.g., 'osm', 'ne', 'osm-disp')
|
|
293
|
+
* @param {Object<number, number>} options.lineWidthStops - Line width stops for this style
|
|
209
294
|
* @param {number} [options.widthFraction=1.0] - Multiplier for base line width
|
|
210
295
|
* @param {number[]} [options.dashArray] - Dash pattern for dashed lines
|
|
211
296
|
* @param {number} [options.alpha=1.0] - Opacity (0-1)
|
|
212
297
|
* @param {number} [options.startZoom=0] - Minimum zoom level for this style
|
|
213
298
|
* @param {number} [options.endZoom=INFINITY] - Maximum zoom level for this style (INFINITY means no limit)
|
|
214
|
-
* @param {number} [options.lineExtensionFactor=0.
|
|
299
|
+
* @param {number} [options.lineExtensionFactor=0.0] - Factor to extend lines by (multiplied by deletion line width)
|
|
215
300
|
* @param {number} [options.delWidthFactor=1.5] - Factor to multiply line width for deletion blur
|
|
216
301
|
*/
|
|
217
|
-
constructor({ color, layerSuffix, widthFraction = 1.0, dashArray, alpha = 1.0, startZoom = 0, endZoom = INFINITY, lineExtensionFactor = 0.0, delWidthFactor = 1.5 }) {
|
|
302
|
+
constructor({ color, layerSuffix, lineWidthStops, widthFraction = 1.0, dashArray, alpha = 1.0, startZoom = 0, endZoom = INFINITY, lineExtensionFactor = 0.0, delWidthFactor = 1.5 }) {
|
|
218
303
|
this.color = color;
|
|
219
304
|
this.layerSuffix = layerSuffix;
|
|
305
|
+
this.lineWidthStops = lineWidthStops;
|
|
220
306
|
this.widthFraction = widthFraction;
|
|
221
307
|
this.dashArray = dashArray;
|
|
222
308
|
this.alpha = alpha;
|
|
@@ -226,6 +312,15 @@ export class LineStyle {
|
|
|
226
312
|
this.delWidthFactor = delWidthFactor;
|
|
227
313
|
}
|
|
228
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Get base line width for this style at a given zoom level.
|
|
317
|
+
* @param {number} zoom - Zoom level
|
|
318
|
+
* @returns {number}
|
|
319
|
+
*/
|
|
320
|
+
getLineWidth(zoom) {
|
|
321
|
+
return interpolateLineWidth(zoom, this.lineWidthStops);
|
|
322
|
+
}
|
|
323
|
+
|
|
229
324
|
/**
|
|
230
325
|
* Check if this style is active at the given zoom level.
|
|
231
326
|
* @param {number} z - Zoom level
|
|
@@ -243,6 +338,7 @@ export class LineStyle {
|
|
|
243
338
|
return {
|
|
244
339
|
color: this.color,
|
|
245
340
|
layerSuffix: this.layerSuffix,
|
|
341
|
+
lineWidthStops: this.lineWidthStops,
|
|
246
342
|
widthFraction: this.widthFraction,
|
|
247
343
|
dashArray: this.dashArray,
|
|
248
344
|
alpha: this.alpha,
|
|
@@ -260,7 +356,7 @@ export class LineStyle {
|
|
|
260
356
|
* @returns {LineStyle}
|
|
261
357
|
*/
|
|
262
358
|
static fromJSON(obj, index) {
|
|
263
|
-
LineStyle.validateJSON(obj, index);
|
|
359
|
+
LineStyle.validateJSON(obj, index, true); // require lineWidthStops
|
|
264
360
|
return new LineStyle(obj);
|
|
265
361
|
}
|
|
266
362
|
}
|
|
@@ -351,10 +447,15 @@ export class LayerConfig {
|
|
|
351
447
|
|
|
352
448
|
this.lineWidthStops = lineWidthStops;
|
|
353
449
|
|
|
354
|
-
// Convert to LineStyle instances
|
|
355
|
-
this.lineStyles = lineStyles.map(style =>
|
|
356
|
-
style instanceof LineStyle
|
|
357
|
-
|
|
450
|
+
// Convert to LineStyle instances, inheriting lineWidthStops from config if not specified
|
|
451
|
+
this.lineStyles = lineStyles.map(style => {
|
|
452
|
+
if (style instanceof LineStyle) {
|
|
453
|
+
return style;
|
|
454
|
+
}
|
|
455
|
+
// If style doesn't have lineWidthStops, use the config's lineWidthStops
|
|
456
|
+
const styleWithStops = style.lineWidthStops ? style : { ...style, lineWidthStops };
|
|
457
|
+
return new LineStyle(styleWithStops);
|
|
458
|
+
});
|
|
358
459
|
}
|
|
359
460
|
|
|
360
461
|
/**
|
|
@@ -376,55 +477,6 @@ export class LayerConfig {
|
|
|
376
477
|
return [...new Set(activeStyles.map(s => s.layerSuffix))];
|
|
377
478
|
}
|
|
378
479
|
|
|
379
|
-
/**
|
|
380
|
-
* Interpolate or extrapolate line width for a given zoom level.
|
|
381
|
-
* Uses the lineWidthStops map to calculate the appropriate width.
|
|
382
|
-
* @param {number} zoom - Zoom level
|
|
383
|
-
* @returns {number}
|
|
384
|
-
*/
|
|
385
|
-
getLineWidth(zoom) {
|
|
386
|
-
const zooms = Object.keys(this.lineWidthStops).map(Number).sort((a, b) => a - b);
|
|
387
|
-
|
|
388
|
-
// Exact match
|
|
389
|
-
if (this.lineWidthStops[zoom] !== undefined) {
|
|
390
|
-
return this.lineWidthStops[zoom];
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Below lowest zoom - extrapolate
|
|
394
|
-
if (zoom < zooms[0]) {
|
|
395
|
-
const z1 = zooms[0];
|
|
396
|
-
const z2 = zooms[1];
|
|
397
|
-
const w1 = this.lineWidthStops[z1];
|
|
398
|
-
const w2 = this.lineWidthStops[z2];
|
|
399
|
-
const slope = (w2 - w1) / (z2 - z1);
|
|
400
|
-
return Math.max(MIN_LINE_WIDTH, w1 + slope * (zoom - z1));
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
// Above highest zoom - extrapolate
|
|
404
|
-
if (zoom > zooms[zooms.length - 1]) {
|
|
405
|
-
const z1 = zooms[zooms.length - 2];
|
|
406
|
-
const z2 = zooms[zooms.length - 1];
|
|
407
|
-
const w1 = this.lineWidthStops[z1];
|
|
408
|
-
const w2 = this.lineWidthStops[z2];
|
|
409
|
-
const slope = (w2 - w1) / (z2 - z1);
|
|
410
|
-
return Math.max(MIN_LINE_WIDTH, w2 + slope * (zoom - z2));
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Interpolate between two stops
|
|
414
|
-
for (let i = 0; i < zooms.length - 1; i++) {
|
|
415
|
-
if (zoom > zooms[i] && zoom < zooms[i + 1]) {
|
|
416
|
-
const z1 = zooms[i];
|
|
417
|
-
const z2 = zooms[i + 1];
|
|
418
|
-
const w1 = this.lineWidthStops[z1];
|
|
419
|
-
const w2 = this.lineWidthStops[z2];
|
|
420
|
-
const t = (zoom - z1) / (z2 - z1);
|
|
421
|
-
return w1 + t * (w2 - w1);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return DEFAULT_LINE_WIDTH; // fallback
|
|
426
|
-
}
|
|
427
|
-
|
|
428
480
|
/**
|
|
429
481
|
* Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
|
|
430
482
|
* @param {string | string[]} templates - Single template URL or array of template URLs
|