@india-boundary-corrector/layer-configs 0.0.1
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/LICENSE +24 -0
- package/README.md +124 -0
- package/dist/index.cjs +407 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +378 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
- package/src/configs.json +85 -0
- package/src/index.d.ts +159 -0
- package/src/index.js +120 -0
- package/src/layerconfig.js +269 -0
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line style definition for drawing boundary lines
|
|
3
|
+
*/
|
|
4
|
+
export interface LineStyle {
|
|
5
|
+
/** Line color (CSS color string) */
|
|
6
|
+
color: string;
|
|
7
|
+
/** Width as fraction of base line width (default: 1.0) */
|
|
8
|
+
widthFraction?: number;
|
|
9
|
+
/** Dash pattern array (omit for solid line) */
|
|
10
|
+
dashArray?: number[];
|
|
11
|
+
/** Opacity/alpha value from 0 (transparent) to 1 (opaque) (default: 1.0) */
|
|
12
|
+
alpha?: number;
|
|
13
|
+
/** Minimum zoom level for this style (default: layerConfig.startZoom) */
|
|
14
|
+
startZoom?: number;
|
|
15
|
+
/** Maximum zoom level for this style (default: Infinity) */
|
|
16
|
+
endZoom?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Configuration options for LayerConfig
|
|
21
|
+
*/
|
|
22
|
+
export interface LayerConfigOptions {
|
|
23
|
+
/** Unique identifier for this config */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Minimum zoom to start rendering (default: 0) */
|
|
26
|
+
startZoom?: number;
|
|
27
|
+
/** Zoom level to switch from NE to OSM data (default: 5) */
|
|
28
|
+
zoomThreshold?: number;
|
|
29
|
+
/** Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png") */
|
|
30
|
+
tileUrlTemplates?: string | string[];
|
|
31
|
+
/** Line width stops: map of zoom level to line width (at least 2 entries) */
|
|
32
|
+
lineWidthStops?: Record<number, number>;
|
|
33
|
+
/** Line styles array - lines are drawn in order */
|
|
34
|
+
lineStyles?: LineStyle[];
|
|
35
|
+
/** Factor to multiply line width for deletion blur (default: 1.5) */
|
|
36
|
+
delWidthFactor?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Layer configuration class for boundary corrections
|
|
41
|
+
*/
|
|
42
|
+
export class LayerConfig {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly startZoom: number;
|
|
45
|
+
readonly zoomThreshold: number;
|
|
46
|
+
readonly tileUrlTemplates: string[];
|
|
47
|
+
readonly lineWidthStops: Record<number, number>;
|
|
48
|
+
readonly lineStyles: LineStyle[];
|
|
49
|
+
readonly delWidthFactor: number;
|
|
50
|
+
|
|
51
|
+
constructor(options: LayerConfigOptions);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get line styles active at a given zoom level
|
|
55
|
+
* @param z - Zoom level
|
|
56
|
+
*/
|
|
57
|
+
getLineStylesForZoom(z: number): LineStyle[];
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
|
|
61
|
+
* @param templates - Single template URL or array of template URLs
|
|
62
|
+
*/
|
|
63
|
+
matchTemplate(templates: string | string[]): boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Check if this config matches the given tile URLs (with actual coordinates)
|
|
67
|
+
* @param tiles - Single tile URL or array of tile URLs
|
|
68
|
+
*/
|
|
69
|
+
matchTileUrl(tiles: string | string[]): boolean;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Extract tile coordinates (z, x, y) from a URL using this config's templates
|
|
73
|
+
* @param url - Tile URL to extract coordinates from
|
|
74
|
+
* @returns Tile coordinates or null if not found
|
|
75
|
+
*/
|
|
76
|
+
extractCoords(url: string): TileCoords | null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Serialize the config to a plain object for postMessage
|
|
80
|
+
*/
|
|
81
|
+
toJSON(): LayerConfigOptions;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create a LayerConfig from a plain object (e.g., from postMessage)
|
|
85
|
+
*/
|
|
86
|
+
static fromJSON(obj: LayerConfigOptions): LayerConfig;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Registry for layer configurations
|
|
91
|
+
*/
|
|
92
|
+
export class LayerConfigRegistry {
|
|
93
|
+
constructor();
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get a layer config by id
|
|
97
|
+
*/
|
|
98
|
+
get(id: string): LayerConfig | undefined;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Register a new layer config
|
|
102
|
+
*/
|
|
103
|
+
register(config: LayerConfig): void;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Remove a layer config by id
|
|
107
|
+
*/
|
|
108
|
+
remove(id: string): boolean;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)
|
|
112
|
+
* @param templates - Single template URL or array of template URLs
|
|
113
|
+
*/
|
|
114
|
+
detectFromTemplates(templates: string | string[]): LayerConfig | undefined;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Detect layer config from actual tile URLs (with numeric coordinates)
|
|
118
|
+
* @param urls - Single tile URL or array of tile URLs
|
|
119
|
+
*/
|
|
120
|
+
detectFromTileUrls(urls: string | string[]): LayerConfig | undefined;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get all available layer config ids
|
|
124
|
+
*/
|
|
125
|
+
getAvailableIds(): string[];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create a new registry with all configs from this registry plus extra configs.
|
|
129
|
+
* @param extraLayerConfigs - Additional configs to add
|
|
130
|
+
*/
|
|
131
|
+
createMergedRegistry(extraLayerConfigs?: LayerConfig[]): LayerConfigRegistry;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Parse a tile URL into its components: layer config and coordinates
|
|
135
|
+
* @param url - Tile URL to parse
|
|
136
|
+
* @returns Parsed tile URL result or null if not matched
|
|
137
|
+
*/
|
|
138
|
+
parseTileUrl(url: string): ParsedTileUrl | null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Default registry with built-in configs */
|
|
142
|
+
export const layerConfigs: LayerConfigRegistry;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Tile coordinates
|
|
146
|
+
*/
|
|
147
|
+
export interface TileCoords {
|
|
148
|
+
z: number;
|
|
149
|
+
x: number;
|
|
150
|
+
y: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Parsed tile URL result
|
|
155
|
+
*/
|
|
156
|
+
export interface ParsedTileUrl {
|
|
157
|
+
layerConfig: LayerConfig;
|
|
158
|
+
coords: TileCoords;
|
|
159
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import configsJson from './configs.json' with { type: 'json' };
|
|
2
|
+
import { LayerConfig } from './layerconfig.js';
|
|
3
|
+
|
|
4
|
+
export { LayerConfig } from './layerconfig.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Layer configuration registry
|
|
8
|
+
*/
|
|
9
|
+
export class LayerConfigRegistry {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.registry = {};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get a layer config by id
|
|
16
|
+
*/
|
|
17
|
+
get(id) {
|
|
18
|
+
return this.registry[id];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register a new layer config
|
|
23
|
+
*/
|
|
24
|
+
register(config) {
|
|
25
|
+
this.registry[config.id] = config;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Remove a layer config by id
|
|
30
|
+
*/
|
|
31
|
+
remove(id) {
|
|
32
|
+
if (!this.registry[id]) return false;
|
|
33
|
+
delete this.registry[id];
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detect layer config from tile URL templates (with {z}/{x}/{y} placeholders)
|
|
39
|
+
* @param {string | string[]} templates - Single template URL or array of template URLs
|
|
40
|
+
*/
|
|
41
|
+
detectFromTemplates(templates) {
|
|
42
|
+
if (!templates || (Array.isArray(templates) && templates.length === 0)) return undefined;
|
|
43
|
+
|
|
44
|
+
for (const config of Object.values(this.registry)) {
|
|
45
|
+
if (config.matchTemplate(templates)) {
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Detect layer config from actual tile URLs (with numeric coordinates)
|
|
55
|
+
* @param {string | string[]} urls - Single tile URL or array of tile URLs
|
|
56
|
+
*/
|
|
57
|
+
detectFromTileUrls(urls) {
|
|
58
|
+
if (!urls || (Array.isArray(urls) && urls.length === 0)) return undefined;
|
|
59
|
+
|
|
60
|
+
for (const config of Object.values(this.registry)) {
|
|
61
|
+
if (config.matchTileUrl(urls)) {
|
|
62
|
+
return config;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all available layer config ids
|
|
71
|
+
*/
|
|
72
|
+
getAvailableIds() {
|
|
73
|
+
return Object.keys(this.registry);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a new registry with all configs from this registry plus extra configs.
|
|
78
|
+
* @param {LayerConfig[]} extraLayerConfigs - Additional configs to add
|
|
79
|
+
* @returns {LayerConfigRegistry} A new registry with merged configs
|
|
80
|
+
*/
|
|
81
|
+
createMergedRegistry(extraLayerConfigs) {
|
|
82
|
+
const registry = new LayerConfigRegistry();
|
|
83
|
+
|
|
84
|
+
for (const id of this.getAvailableIds()) {
|
|
85
|
+
registry.register(this.get(id));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (extraLayerConfigs && extraLayerConfigs.length > 0) {
|
|
89
|
+
for (const config of extraLayerConfigs) {
|
|
90
|
+
registry.register(config);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return registry;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Parse a tile URL into its components: layer config and coordinates
|
|
99
|
+
* @param {string} url - Tile URL to parse
|
|
100
|
+
* @returns {{ layerConfig: LayerConfig, coords: { z: number, x: number, y: number } } | null}
|
|
101
|
+
*/
|
|
102
|
+
parseTileUrl(url) {
|
|
103
|
+
// Check if URL matches any layer config
|
|
104
|
+
const layerConfig = this.detectFromTileUrls([url]);
|
|
105
|
+
if (!layerConfig) return null;
|
|
106
|
+
|
|
107
|
+
// Extract tile coordinates using the matched config
|
|
108
|
+
const coords = layerConfig.extractCoords(url);
|
|
109
|
+
if (!coords) return null;
|
|
110
|
+
|
|
111
|
+
return { layerConfig, coords };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Default registry with built-in configs loaded from JSON
|
|
116
|
+
export const layerConfigs = new LayerConfigRegistry();
|
|
117
|
+
for (const configData of configsJson) {
|
|
118
|
+
layerConfigs.register(new LayerConfig(configData));
|
|
119
|
+
}
|
|
120
|
+
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a tile URL template to a regex pattern and capture group names.
|
|
3
|
+
* Supports {z}, {x}, {y}, {s} (Leaflet subdomain), {a-c}/{1-4} (OpenLayers subdomain), and {r} (retina) placeholders.
|
|
4
|
+
* @param {string} template - URL template like "https://{s}.tile.example.com/{z}/{x}/{y}.png"
|
|
5
|
+
* @returns {{ pattern: RegExp, groups: string[] }}
|
|
6
|
+
*/
|
|
7
|
+
function templateToRegex(template) {
|
|
8
|
+
const groups = [];
|
|
9
|
+
// Escape regex special chars, then replace placeholders
|
|
10
|
+
let pattern = template
|
|
11
|
+
.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
|
|
12
|
+
// Don't escape our placeholders
|
|
13
|
+
if (char === '{' || char === '}') return char;
|
|
14
|
+
return '\\' + char;
|
|
15
|
+
})
|
|
16
|
+
// Make protocol flexible (http/https)
|
|
17
|
+
.replace(/^https:\/\//, 'https?://')
|
|
18
|
+
.replace(/^http:\/\//, 'https?://')
|
|
19
|
+
// Handle {a-c} or {1-4} etc (OpenLayers style subdomain)
|
|
20
|
+
.replace(/\{[a-z0-9]-[a-z0-9]\}/gi, () => {
|
|
21
|
+
groups.push('s');
|
|
22
|
+
return '([a-z0-9]+)';
|
|
23
|
+
})
|
|
24
|
+
.replace(/\{(z|x|y|s|r)\}/gi, (_, name) => {
|
|
25
|
+
const lowerName = name.toLowerCase();
|
|
26
|
+
groups.push(lowerName);
|
|
27
|
+
if (lowerName === 's') {
|
|
28
|
+
// Subdomain: single letter or short string
|
|
29
|
+
return '([a-z0-9]+)';
|
|
30
|
+
}
|
|
31
|
+
if (lowerName === 'r') {
|
|
32
|
+
// Retina suffix: optional @2x or similar
|
|
33
|
+
return '(@\\d+x)?';
|
|
34
|
+
}
|
|
35
|
+
// z, x, y: numeric
|
|
36
|
+
return '(\\d+)';
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Allow optional query string at end
|
|
40
|
+
return { pattern: new RegExp('^' + pattern + '(\\?.*)?$', 'i'), groups };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a tile URL template to a regex that matches the template itself.
|
|
45
|
+
* @param {string} template - URL template like "https://{s}.tile.example.com/{z}/{x}/{y}.png"
|
|
46
|
+
* @returns {RegExp}
|
|
47
|
+
*/
|
|
48
|
+
function templateToTemplateRegex(template) {
|
|
49
|
+
// Escape regex special chars, then replace placeholders with literal match
|
|
50
|
+
let pattern = template
|
|
51
|
+
.replace(/[.*+?^${}()|[\]\\]/g, (char) => {
|
|
52
|
+
if (char === '{' || char === '}') return char;
|
|
53
|
+
return '\\' + char;
|
|
54
|
+
})
|
|
55
|
+
// Make protocol flexible (http/https)
|
|
56
|
+
.replace(/^https:\/\//, 'https?://')
|
|
57
|
+
.replace(/^http:\/\//, 'https?://')
|
|
58
|
+
// Handle {a-c} or {1-4} (OpenLayers style subdomain)
|
|
59
|
+
.replace(/\{([a-z0-9])-([a-z0-9])\}/gi, (_, start, end) => `(\\{${start}-${end}\\}|[a-z0-9]+)`)
|
|
60
|
+
.replace(/\{(z|x|y|s|r)\}/gi, (_, name) => {
|
|
61
|
+
const lowerName = name.toLowerCase();
|
|
62
|
+
if (lowerName === 's') {
|
|
63
|
+
// Match {s} placeholder or actual subdomain
|
|
64
|
+
return '(\\{s\\}|[a-z0-9]+)';
|
|
65
|
+
}
|
|
66
|
+
if (lowerName === 'r') {
|
|
67
|
+
// Match {r} placeholder or actual retina or empty
|
|
68
|
+
return '(\\{r\\}|@\\d+x)?';
|
|
69
|
+
}
|
|
70
|
+
// Match {z}, {x}, {y} placeholders
|
|
71
|
+
return `\\{${lowerName}\\}`;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Allow optional query string at end
|
|
75
|
+
return new RegExp('^' + pattern + '(\\?.*)?$', 'i');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Base class for layer configurations
|
|
80
|
+
*
|
|
81
|
+
* Supports separate styling for NE (Natural Earth) data at low zoom levels
|
|
82
|
+
* and OSM data at higher zoom levels, split by zoomThreshold.
|
|
83
|
+
*/
|
|
84
|
+
export class LayerConfig {
|
|
85
|
+
constructor({
|
|
86
|
+
id,
|
|
87
|
+
startZoom = 0,
|
|
88
|
+
zoomThreshold = 5,
|
|
89
|
+
// Tile URL templates for matching (e.g., "https://{s}.tile.example.com/{z}/{x}/{y}.png")
|
|
90
|
+
tileUrlTemplates = [],
|
|
91
|
+
// Line width stops: map of zoom level to line width (at least 2 entries)
|
|
92
|
+
lineWidthStops = { 1: 0.5, 10: 2.5 },
|
|
93
|
+
// Line styles array - each element describes a line to draw
|
|
94
|
+
// { color: string, widthFraction?: number, dashArray?: number[], startZoom?: number, endZoom?: number }
|
|
95
|
+
// Lines are drawn in array order. startZoom defaults to layerConfig startZoom, endZoom defaults to Infinity
|
|
96
|
+
lineStyles = [{ color: 'green', widthFraction: 1.0 }],
|
|
97
|
+
// Factor to multiply line width for deletion blur (default 1.5)
|
|
98
|
+
// Higher values leave gaps where wiped lines meet existing lines
|
|
99
|
+
// Lower values mean wiped lines show through
|
|
100
|
+
delWidthFactor = 1.5,
|
|
101
|
+
// Factor to extend add lines by (multiplied by deletion line width)
|
|
102
|
+
// Helps cover gaps where deleted lines meet the new boundary
|
|
103
|
+
// Set to 0 to disable extension
|
|
104
|
+
lineExtensionFactor = 0.5,
|
|
105
|
+
}) {
|
|
106
|
+
if (!id || typeof id !== 'string') {
|
|
107
|
+
throw new Error('LayerConfig requires a non-empty string id');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.id = id;
|
|
111
|
+
this.startZoom = startZoom;
|
|
112
|
+
this.zoomThreshold = zoomThreshold;
|
|
113
|
+
|
|
114
|
+
if (startZoom > zoomThreshold) {
|
|
115
|
+
throw new Error(`LayerConfig "${id}": startZoom (${startZoom}) must be <= zoomThreshold (${zoomThreshold})`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Normalize to array
|
|
119
|
+
const templates = Array.isArray(tileUrlTemplates) ? tileUrlTemplates :
|
|
120
|
+
(tileUrlTemplates ? [tileUrlTemplates] : []);
|
|
121
|
+
this.tileUrlTemplates = templates;
|
|
122
|
+
|
|
123
|
+
// Pre-compile regex patterns for matching tile URLs (with actual coords)
|
|
124
|
+
this._compiledPatterns = templates.map(t => templateToRegex(t));
|
|
125
|
+
|
|
126
|
+
// Pre-compile regex patterns for matching template URLs (with {z}/{x}/{y} placeholders)
|
|
127
|
+
this._templatePatterns = templates.map(t => templateToTemplateRegex(t));
|
|
128
|
+
|
|
129
|
+
// Validate lineWidthStops
|
|
130
|
+
if (!lineWidthStops || typeof lineWidthStops !== 'object' || Array.isArray(lineWidthStops)) {
|
|
131
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must be an object`);
|
|
132
|
+
}
|
|
133
|
+
const stopKeys = Object.keys(lineWidthStops);
|
|
134
|
+
if (stopKeys.length < 2) {
|
|
135
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops must have at least 2 entries`);
|
|
136
|
+
}
|
|
137
|
+
for (const key of stopKeys) {
|
|
138
|
+
const zoom = Number(key);
|
|
139
|
+
if (!Number.isInteger(zoom) || zoom < 0) {
|
|
140
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops keys must be non-negative integers, got "${key}"`);
|
|
141
|
+
}
|
|
142
|
+
if (typeof lineWidthStops[key] !== 'number' || lineWidthStops[key] <= 0) {
|
|
143
|
+
throw new Error(`LayerConfig "${id}": lineWidthStops values must be positive numbers`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.lineWidthStops = lineWidthStops;
|
|
147
|
+
|
|
148
|
+
// Validate lineStyles
|
|
149
|
+
if (!Array.isArray(lineStyles) || lineStyles.length === 0) {
|
|
150
|
+
throw new Error(`LayerConfig "${id}": lineStyles must be a non-empty array`);
|
|
151
|
+
}
|
|
152
|
+
for (let i = 0; i < lineStyles.length; i++) {
|
|
153
|
+
const style = lineStyles[i];
|
|
154
|
+
if (!style || typeof style !== 'object') {
|
|
155
|
+
throw new Error(`LayerConfig "${id}": lineStyles[${i}] must be an object`);
|
|
156
|
+
}
|
|
157
|
+
if (!style.color || typeof style.color !== 'string') {
|
|
158
|
+
throw new Error(`LayerConfig "${id}": lineStyles[${i}].color must be a non-empty string`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Line styles - normalize startZoom/endZoom defaults
|
|
163
|
+
this.lineStyles = lineStyles.map(style => ({
|
|
164
|
+
...style,
|
|
165
|
+
startZoom: style.startZoom ?? startZoom,
|
|
166
|
+
endZoom: style.endZoom ?? Infinity,
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
// Deletion width factor
|
|
170
|
+
this.delWidthFactor = delWidthFactor;
|
|
171
|
+
|
|
172
|
+
// Line extension factor
|
|
173
|
+
this.lineExtensionFactor = lineExtensionFactor;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get line styles active at a given zoom level
|
|
178
|
+
* @param {number} z - Zoom level
|
|
179
|
+
* @returns {Array<{color: string, widthFraction?: number, dashArray?: number[]}>}
|
|
180
|
+
*/
|
|
181
|
+
getLineStylesForZoom(z) {
|
|
182
|
+
return this.lineStyles.filter(style => z >= style.startZoom && z <= style.endZoom);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if this config matches the given template URLs (with {z}/{x}/{y} placeholders)
|
|
187
|
+
* @param {string | string[]} templates - Single template URL or array of template URLs
|
|
188
|
+
* @returns {boolean}
|
|
189
|
+
*/
|
|
190
|
+
matchTemplate(templates) {
|
|
191
|
+
if (this._templatePatterns.length === 0) return false;
|
|
192
|
+
|
|
193
|
+
const urls = Array.isArray(templates) ? templates : [templates];
|
|
194
|
+
if (urls.length === 0) return false;
|
|
195
|
+
|
|
196
|
+
return urls.some(url =>
|
|
197
|
+
this._templatePatterns.some(pattern => pattern.test(url))
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Check if this config matches the given tile URLs (with actual coordinates)
|
|
203
|
+
* @param {string | string[]} tiles - Single tile URL or array of tile URLs
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
matchTileUrl(tiles) {
|
|
207
|
+
if (this._compiledPatterns.length === 0) return false;
|
|
208
|
+
|
|
209
|
+
const urls = Array.isArray(tiles) ? tiles : [tiles];
|
|
210
|
+
if (urls.length === 0) return false;
|
|
211
|
+
|
|
212
|
+
return urls.some(url =>
|
|
213
|
+
this._compiledPatterns.some(({ pattern }) => pattern.test(url))
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extract tile coordinates (z, x, y) from a URL using this config's templates
|
|
219
|
+
* @param {string} url - Tile URL to extract coordinates from
|
|
220
|
+
* @returns {{ z: number, x: number, y: number } | null}
|
|
221
|
+
*/
|
|
222
|
+
extractCoords(url) {
|
|
223
|
+
for (const { pattern, groups } of this._compiledPatterns) {
|
|
224
|
+
const match = url.match(pattern);
|
|
225
|
+
if (match) {
|
|
226
|
+
const result = {};
|
|
227
|
+
for (let i = 0; i < groups.length; i++) {
|
|
228
|
+
const name = groups[i];
|
|
229
|
+
const value = match[i + 1];
|
|
230
|
+
if (name === 'z' || name === 'x' || name === 'y') {
|
|
231
|
+
result[name] = parseInt(value, 10);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if ('z' in result && 'x' in result && 'y' in result) {
|
|
235
|
+
return { z: result.z, x: result.x, y: result.y };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Serialize the config to a plain object for postMessage
|
|
244
|
+
* @returns {Object}
|
|
245
|
+
*/
|
|
246
|
+
toJSON() {
|
|
247
|
+
return {
|
|
248
|
+
id: this.id,
|
|
249
|
+
startZoom: this.startZoom,
|
|
250
|
+
zoomThreshold: this.zoomThreshold,
|
|
251
|
+
tileUrlTemplates: this.tileUrlTemplates,
|
|
252
|
+
lineWidthStops: this.lineWidthStops,
|
|
253
|
+
lineStyles: this.lineStyles,
|
|
254
|
+
delWidthFactor: this.delWidthFactor,
|
|
255
|
+
lineExtensionFactor: this.lineExtensionFactor,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create a LayerConfig from a plain object (e.g., from postMessage)
|
|
261
|
+
* @param {Object} obj
|
|
262
|
+
* @returns {LayerConfig}
|
|
263
|
+
*/
|
|
264
|
+
static fromJSON(obj) {
|
|
265
|
+
return new LayerConfig(obj);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export default LayerConfig;
|