@india-boundary-corrector/maplibre-protocol 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/dist/index.js ADDED
@@ -0,0 +1,189 @@
1
+ // src/index.js
2
+ import { getPmtilesUrl } from "@india-boundary-corrector/data";
3
+ import { layerConfigs } from "@india-boundary-corrector/layer-configs";
4
+ import { BoundaryCorrector as TileFixer } from "@india-boundary-corrector/tilefixer";
5
+ import { layerConfigs as layerConfigs2, LayerConfig } from "@india-boundary-corrector/layer-configs";
6
+ import { getPmtilesUrl as getPmtilesUrl2 } from "@india-boundary-corrector/data";
7
+ var PROTOCOL_PREFIX = "ibc";
8
+ function parseCorrectionsUrl(url) {
9
+ const withoutProtocol = url.replace(`${PROTOCOL_PREFIX}://`, "");
10
+ let configId = null;
11
+ let tileUrl = withoutProtocol;
12
+ const atIndex = withoutProtocol.indexOf("@");
13
+ if (atIndex > 0 && atIndex < withoutProtocol.indexOf("/")) {
14
+ configId = withoutProtocol.substring(0, atIndex);
15
+ tileUrl = withoutProtocol.substring(atIndex + 1);
16
+ }
17
+ const urlObj = new URL(tileUrl);
18
+ const pathParts = urlObj.pathname.split("/").filter((p) => p.length > 0);
19
+ let z, x, y;
20
+ for (let i = pathParts.length - 1; i >= 2; i--) {
21
+ const yPart = pathParts[i].replace(/\.[^.]+$/, "");
22
+ const xPart = pathParts[i - 1];
23
+ const zPart = pathParts[i - 2];
24
+ if (/^\d+$/.test(zPart) && /^\d+$/.test(xPart) && /^\d+$/.test(yPart)) {
25
+ z = parseInt(zPart, 10);
26
+ x = parseInt(xPart, 10);
27
+ y = parseInt(yPart, 10);
28
+ break;
29
+ }
30
+ }
31
+ return { configId, tileUrl, z, x, y };
32
+ }
33
+ async function fetchAndFixTile(tileUrl, z, x, y, tileFixer, layerConfig, tileSize, options = {}) {
34
+ const { data, correctionsFailed, correctionsError } = await tileFixer.fetchAndFixTile(
35
+ tileUrl,
36
+ z,
37
+ x,
38
+ y,
39
+ layerConfig,
40
+ { tileSize, signal: options.signal }
41
+ );
42
+ return { data, correctionsFailed, correctionsError };
43
+ }
44
+ var CorrectionProtocol = class {
45
+ /**
46
+ * @param {Object} [options]
47
+ * @param {string} [options.pmtilesUrl] - URL to PMTiles file (defaults to CDN)
48
+ * @param {number} [options.tileSize=256] - Tile size in pixels
49
+ */
50
+ constructor(options = {}) {
51
+ this._pmtilesUrl = options.pmtilesUrl ?? getPmtilesUrl();
52
+ this._tileSize = options.tileSize ?? 256;
53
+ this._tileFixer = new TileFixer(this._pmtilesUrl);
54
+ this._registry = layerConfigs.createMergedRegistry();
55
+ this._listeners = /* @__PURE__ */ new Map();
56
+ this._loadFn = this._createLoadFunction();
57
+ }
58
+ /**
59
+ * Add a listener for an event.
60
+ * @param {'correctionerror'} event - Event name
61
+ * @param {Function} listener - Callback function receiving event data
62
+ * @returns {this}
63
+ */
64
+ on(event, listener) {
65
+ if (!this._listeners.has(event)) {
66
+ this._listeners.set(event, /* @__PURE__ */ new Set());
67
+ }
68
+ this._listeners.get(event).add(listener);
69
+ return this;
70
+ }
71
+ /**
72
+ * Remove an event listener.
73
+ * @param {'correctionerror'} event - Event name
74
+ * @param {Function} listener - Callback to remove
75
+ * @returns {this}
76
+ */
77
+ off(event, listener) {
78
+ this._listeners.get(event)?.delete(listener);
79
+ return this;
80
+ }
81
+ /**
82
+ * Emit an event to all listeners.
83
+ * @param {string} event - Event name
84
+ * @param {Object} data - Event data
85
+ * @private
86
+ */
87
+ _emit(event, data) {
88
+ this._listeners.get(event)?.forEach((fn) => fn(data));
89
+ }
90
+ /**
91
+ * Add a custom layer config to the registry.
92
+ * @param {Object} layerConfig - LayerConfig to add
93
+ * @returns {this}
94
+ */
95
+ addLayerConfig(layerConfig) {
96
+ this._registry.register(layerConfig);
97
+ return this;
98
+ }
99
+ /**
100
+ * Get the registry.
101
+ * @returns {LayerConfigRegistry}
102
+ */
103
+ getRegistry() {
104
+ return this._registry;
105
+ }
106
+ /**
107
+ * Get the TileFixer instance.
108
+ * @returns {TileFixer}
109
+ */
110
+ getTileFixer() {
111
+ return this._tileFixer;
112
+ }
113
+ /**
114
+ * Register the protocol with MapLibre GL.
115
+ * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace
116
+ * @returns {this}
117
+ */
118
+ register(maplibregl) {
119
+ maplibregl.addProtocol(PROTOCOL_PREFIX, this._loadFn);
120
+ return this;
121
+ }
122
+ /**
123
+ * Unregister the protocol from MapLibre GL.
124
+ * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace
125
+ * @returns {this}
126
+ */
127
+ unregister(maplibregl) {
128
+ maplibregl.removeProtocol(PROTOCOL_PREFIX);
129
+ return this;
130
+ }
131
+ /**
132
+ * Create the protocol load function.
133
+ * @returns {Function}
134
+ * @private
135
+ */
136
+ _createLoadFunction() {
137
+ const self = this;
138
+ return async (params, abortController) => {
139
+ const { configId, tileUrl, z, x, y } = parseCorrectionsUrl(params.url);
140
+ if (z === void 0 || x === void 0 || y === void 0) {
141
+ console.warn(`[CorrectionProtocol] Could not parse tile coordinates from URL: ${params.url}, falling back to original`);
142
+ const response = await fetch(tileUrl, { signal: abortController?.signal });
143
+ return { data: await response.arrayBuffer() };
144
+ }
145
+ let layerConfig;
146
+ if (configId) {
147
+ layerConfig = self._registry.get(configId);
148
+ } else {
149
+ layerConfig = self._registry.detectFromTileUrls([tileUrl]);
150
+ }
151
+ try {
152
+ const result = await fetchAndFixTile(
153
+ tileUrl,
154
+ z,
155
+ x,
156
+ y,
157
+ self._tileFixer,
158
+ layerConfig,
159
+ self._tileSize,
160
+ { signal: abortController?.signal }
161
+ );
162
+ if (result.correctionsFailed) {
163
+ console.warn("[CorrectionProtocol] Corrections fetch failed:", result.correctionsError);
164
+ self._emit("correctionerror", { error: result.correctionsError, coords: { z, x, y }, tileUrl });
165
+ }
166
+ return { data: result.data };
167
+ } catch (err) {
168
+ console.warn("[CorrectionProtocol] Error applying corrections, falling back to original:", err);
169
+ self._emit("correctionerror", { error: err, coords: { z, x, y }, tileUrl });
170
+ const response = await fetch(tileUrl, { signal: abortController?.signal });
171
+ return { data: await response.arrayBuffer() };
172
+ }
173
+ };
174
+ }
175
+ };
176
+ function registerCorrectionProtocol(maplibregl, options = {}) {
177
+ const protocol = new CorrectionProtocol(options);
178
+ return protocol.register(maplibregl);
179
+ }
180
+ export {
181
+ CorrectionProtocol,
182
+ LayerConfig,
183
+ fetchAndFixTile,
184
+ getPmtilesUrl2 as getPmtilesUrl,
185
+ layerConfigs2 as layerConfigs,
186
+ parseCorrectionsUrl,
187
+ registerCorrectionProtocol
188
+ };
189
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.js"],"sourcesContent":["import { getPmtilesUrl } from '@india-boundary-corrector/data';\nimport { layerConfigs } from '@india-boundary-corrector/layer-configs';\nimport { BoundaryCorrector as TileFixer } from '@india-boundary-corrector/tilefixer';\n\n// Re-export for convenience\nexport { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';\nexport { getPmtilesUrl } from '@india-boundary-corrector/data';\n\nconst PROTOCOL_PREFIX = 'ibc';\n\n/**\n * Parse an ibc:// URL.\n * Format: ibc://[configId@]originalUrl\n * Examples:\n * ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png\n * ibc://osm-carto@https://tile.openstreetmap.org/{z}/{x}/{y}.png\n * \n * @param {string} url - The full URL with ibc:// prefix\n * @returns {{ configId: string|null, tileUrl: string, z: number, x: number, y: number }}\n */\nfunction parseCorrectionsUrl(url) {\n // Remove protocol prefix\n const withoutProtocol = url.replace(`${PROTOCOL_PREFIX}://`, '');\n \n // Check for configId@url format\n let configId = null;\n let tileUrl = withoutProtocol;\n \n const atIndex = withoutProtocol.indexOf('@');\n if (atIndex > 0 && atIndex < withoutProtocol.indexOf('/')) {\n // Has configId prefix\n configId = withoutProtocol.substring(0, atIndex);\n tileUrl = withoutProtocol.substring(atIndex + 1);\n }\n \n // Extract z, x, y from the URL (assuming standard {z}/{x}/{y} pattern in the path)\n // The URL has already been templated by MapLibre, so we need to parse actual numbers\n const urlObj = new URL(tileUrl);\n const pathParts = urlObj.pathname.split('/').filter(p => p.length > 0);\n \n // Find z/x/y pattern - typically last 3 numeric segments\n let z, x, y;\n for (let i = pathParts.length - 1; i >= 2; i--) {\n const yPart = pathParts[i].replace(/\\.[^.]+$/, ''); // Remove extension\n const xPart = pathParts[i - 1];\n const zPart = pathParts[i - 2];\n \n if (/^\\d+$/.test(zPart) && /^\\d+$/.test(xPart) && /^\\d+$/.test(yPart)) {\n z = parseInt(zPart, 10);\n x = parseInt(xPart, 10);\n y = parseInt(yPart, 10);\n break;\n }\n }\n \n return { configId, tileUrl, z, x, y };\n}\n\n/**\n * Fetch and fix a tile for MapLibre protocol.\n * Extracted for testability.\n * @param {string} tileUrl - URL of the raster tile\n * @param {number} z - Zoom level\n * @param {number} x - Tile X coordinate\n * @param {number} y - Tile Y coordinate\n * @param {TileFixer} tileFixer - TileFixer instance\n * @param {Object} layerConfig - Layer configuration (can be null)\n * @param {number} tileSize - Tile size in pixels\n * @param {Object} [options] - Fetch options\n * @param {AbortSignal} [options.signal] - Abort signal\n * @returns {Promise<{data: ArrayBuffer, correctionsFailed: boolean, correctionsError: Error|null}>}\n */\nasync function fetchAndFixTile(tileUrl, z, x, y, tileFixer, layerConfig, tileSize, options = {}) {\n const { data, correctionsFailed, correctionsError } = await tileFixer.fetchAndFixTile(\n tileUrl, z, x, y, layerConfig, { tileSize, signal: options.signal }\n );\n return { data, correctionsFailed, correctionsError };\n}\n\n/**\n * India boundary corrections protocol for MapLibre GL.\n * \n * Usage:\n * const protocol = new CorrectionProtocol();\n * protocol.register(maplibregl);\n * \n * // In your style:\n * tiles: ['ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png']\n * // Or with explicit config:\n * tiles: ['ibc://osm-carto@https://tile.openstreetmap.org/{z}/{x}/{y}.png']\n */\nexport class CorrectionProtocol {\n /**\n * @param {Object} [options]\n * @param {string} [options.pmtilesUrl] - URL to PMTiles file (defaults to CDN)\n * @param {number} [options.tileSize=256] - Tile size in pixels\n */\n constructor(options = {}) {\n this._pmtilesUrl = options.pmtilesUrl ?? getPmtilesUrl();\n this._tileSize = options.tileSize ?? 256;\n this._tileFixer = new TileFixer(this._pmtilesUrl);\n this._registry = layerConfigs.createMergedRegistry();\n /** @type {Map<string, Set<Function>>} */\n this._listeners = new Map();\n \n this._loadFn = this._createLoadFunction();\n }\n\n /**\n * Add a listener for an event.\n * @param {'correctionerror'} event - Event name\n * @param {Function} listener - Callback function receiving event data\n * @returns {this}\n */\n on(event, listener) {\n if (!this._listeners.has(event)) {\n this._listeners.set(event, new Set());\n }\n this._listeners.get(event).add(listener);\n return this;\n }\n\n /**\n * Remove an event listener.\n * @param {'correctionerror'} event - Event name\n * @param {Function} listener - Callback to remove\n * @returns {this}\n */\n off(event, listener) {\n this._listeners.get(event)?.delete(listener);\n return this;\n }\n\n /**\n * Emit an event to all listeners.\n * @param {string} event - Event name\n * @param {Object} data - Event data\n * @private\n */\n _emit(event, data) {\n this._listeners.get(event)?.forEach(fn => fn(data));\n }\n\n /**\n * Add a custom layer config to the registry.\n * @param {Object} layerConfig - LayerConfig to add\n * @returns {this}\n */\n addLayerConfig(layerConfig) {\n this._registry.register(layerConfig);\n return this;\n }\n\n /**\n * Get the registry.\n * @returns {LayerConfigRegistry}\n */\n getRegistry() {\n return this._registry;\n }\n\n /**\n * Get the TileFixer instance.\n * @returns {TileFixer}\n */\n getTileFixer() {\n return this._tileFixer;\n }\n\n /**\n * Register the protocol with MapLibre GL.\n * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace\n * @returns {this}\n */\n register(maplibregl) {\n maplibregl.addProtocol(PROTOCOL_PREFIX, this._loadFn);\n return this;\n }\n\n /**\n * Unregister the protocol from MapLibre GL.\n * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace\n * @returns {this}\n */\n unregister(maplibregl) {\n maplibregl.removeProtocol(PROTOCOL_PREFIX);\n return this;\n }\n\n /**\n * Create the protocol load function.\n * @returns {Function}\n * @private\n */\n _createLoadFunction() {\n const self = this;\n \n return async (params, abortController) => {\n const { configId, tileUrl, z, x, y } = parseCorrectionsUrl(params.url);\n \n // Validate parsed coordinates\n if (z === undefined || x === undefined || y === undefined) {\n console.warn(`[CorrectionProtocol] Could not parse tile coordinates from URL: ${params.url}, falling back to original`);\n const response = await fetch(tileUrl, { signal: abortController?.signal });\n return { data: await response.arrayBuffer() };\n }\n \n // Resolve layer config\n let layerConfig;\n if (configId) {\n layerConfig = self._registry.get(configId);\n } else {\n layerConfig = self._registry.detectFromTileUrls([tileUrl]);\n }\n \n try {\n const result = await fetchAndFixTile(\n tileUrl,\n z, \n x,\n y,\n self._tileFixer,\n layerConfig,\n self._tileSize,\n { signal: abortController?.signal }\n );\n \n if (result.correctionsFailed) {\n console.warn('[CorrectionProtocol] Corrections fetch failed:', result.correctionsError);\n self._emit('correctionerror', { error: result.correctionsError, coords: { z, x, y }, tileUrl });\n }\n \n return { data: result.data };\n } catch (err) {\n console.warn('[CorrectionProtocol] Error applying corrections, falling back to original:', err);\n self._emit('correctionerror', { error: err, coords: { z, x, y }, tileUrl });\n const response = await fetch(tileUrl, { signal: abortController?.signal });\n return { data: await response.arrayBuffer() };\n }\n };\n }\n}\n\n/**\n * Create and register a correction protocol with MapLibre GL.\n * \n * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace\n * @param {Object} [options] - Protocol options\n * @param {string} [options.pmtilesUrl] - URL to PMTiles file\n * @param {number} [options.tileSize=256] - Tile size in pixels\n * @returns {CorrectionProtocol}\n * \n * @example\n * import maplibregl from 'maplibre-gl';\n * import { registerCorrectionProtocol } from '@india-boundary-corrector/maplibre-protocol';\n * \n * const protocol = registerCorrectionProtocol(maplibregl);\n * \n * // Use in style:\n * const map = new maplibregl.Map({\n * container: 'map',\n * style: {\n * sources: {\n * osm: {\n * type: 'raster',\n * tiles: ['ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png'],\n * tileSize: 256\n * }\n * },\n * layers: [{ id: 'osm', type: 'raster', source: 'osm' }]\n * }\n * });\n */\nexport function registerCorrectionProtocol(maplibregl, options = {}) {\n const protocol = new CorrectionProtocol(options);\n return protocol.register(maplibregl);\n}\n\n// Export for testing\nexport { parseCorrectionsUrl, fetchAndFixTile };\n"],"mappings":"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","names":["layerConfigs","getPmtilesUrl"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@india-boundary-corrector/maplibre-protocol",
3
+ "version": "0.0.1",
4
+ "description": "MapLibre GL custom protocol for India boundary corrections",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "src/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./src/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./src/index.d.ts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": [
22
+ "src",
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup"
27
+ },
28
+ "peerDependencies": {
29
+ "maplibre-gl": ">=3.0.0"
30
+ },
31
+ "dependencies": {
32
+ "@india-boundary-corrector/data": "^0.0.1",
33
+ "@india-boundary-corrector/layer-configs": "^0.0.1",
34
+ "@india-boundary-corrector/tilefixer": "^0.0.1"
35
+ },
36
+ "keywords": [
37
+ "india",
38
+ "boundary",
39
+ "map",
40
+ "maplibre",
41
+ "protocol"
42
+ ],
43
+ "author": "ramSeraph",
44
+ "license": "Unlicense",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/ramSeraph/india_boundary_corrector.git",
48
+ "directory": "packages/maplibre-protocol"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/ramSeraph/india_boundary_corrector/issues"
52
+ },
53
+ "homepage": "https://github.com/ramSeraph/india_boundary_corrector#readme"
54
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { LayerConfig, LayerConfigRegistry } from '@india-boundary-corrector/layer-configs';
2
+ import { BoundaryCorrector as TileFixer } from '@india-boundary-corrector/tilefixer';
3
+
4
+ export { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
5
+ export { getPmtilesUrl } from '@india-boundary-corrector/data';
6
+
7
+ /**
8
+ * Options for CorrectionProtocol
9
+ */
10
+ export interface CorrectionProtocolOptions {
11
+ /** URL to PMTiles file (defaults to CDN) */
12
+ pmtilesUrl?: string;
13
+ /** Tile size in pixels (default: 256) */
14
+ tileSize?: number;
15
+ }
16
+
17
+ /**
18
+ * India boundary corrections protocol for MapLibre GL.
19
+ *
20
+ * Usage:
21
+ * const protocol = new CorrectionProtocol();
22
+ * protocol.register(maplibregl);
23
+ *
24
+ * // In your style:
25
+ * tiles: ['ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png']
26
+ * // Or with explicit config:
27
+ * tiles: ['ibc://osm-carto@https://tile.openstreetmap.org/{z}/{x}/{y}.png']
28
+ */
29
+ export declare class CorrectionProtocol {
30
+ constructor(options?: CorrectionProtocolOptions);
31
+
32
+ /**
33
+ * Add a custom layer config to the registry.
34
+ */
35
+ addLayerConfig(layerConfig: LayerConfig): this;
36
+
37
+ /**
38
+ * Get the registry.
39
+ */
40
+ getRegistry(): LayerConfigRegistry;
41
+
42
+ /**
43
+ * Get the TileFixer instance.
44
+ */
45
+ getTileFixer(): TileFixer;
46
+
47
+ /**
48
+ * Register the protocol with MapLibre GL.
49
+ */
50
+ register(maplibregl: typeof import('maplibre-gl')): this;
51
+
52
+ /**
53
+ * Unregister the protocol from MapLibre GL.
54
+ */
55
+ unregister(maplibregl: typeof import('maplibre-gl')): this;
56
+ }
57
+
58
+ /**
59
+ * Create and register a correction protocol with MapLibre GL.
60
+ */
61
+ export declare function registerCorrectionProtocol(
62
+ maplibregl: typeof import('maplibre-gl'),
63
+ options?: CorrectionProtocolOptions
64
+ ): CorrectionProtocol;
package/src/index.js ADDED
@@ -0,0 +1,280 @@
1
+ import { getPmtilesUrl } from '@india-boundary-corrector/data';
2
+ import { layerConfigs } from '@india-boundary-corrector/layer-configs';
3
+ import { BoundaryCorrector as TileFixer } from '@india-boundary-corrector/tilefixer';
4
+
5
+ // Re-export for convenience
6
+ export { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
7
+ export { getPmtilesUrl } from '@india-boundary-corrector/data';
8
+
9
+ const PROTOCOL_PREFIX = 'ibc';
10
+
11
+ /**
12
+ * Parse an ibc:// URL.
13
+ * Format: ibc://[configId@]originalUrl
14
+ * Examples:
15
+ * ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png
16
+ * ibc://osm-carto@https://tile.openstreetmap.org/{z}/{x}/{y}.png
17
+ *
18
+ * @param {string} url - The full URL with ibc:// prefix
19
+ * @returns {{ configId: string|null, tileUrl: string, z: number, x: number, y: number }}
20
+ */
21
+ function parseCorrectionsUrl(url) {
22
+ // Remove protocol prefix
23
+ const withoutProtocol = url.replace(`${PROTOCOL_PREFIX}://`, '');
24
+
25
+ // Check for configId@url format
26
+ let configId = null;
27
+ let tileUrl = withoutProtocol;
28
+
29
+ const atIndex = withoutProtocol.indexOf('@');
30
+ if (atIndex > 0 && atIndex < withoutProtocol.indexOf('/')) {
31
+ // Has configId prefix
32
+ configId = withoutProtocol.substring(0, atIndex);
33
+ tileUrl = withoutProtocol.substring(atIndex + 1);
34
+ }
35
+
36
+ // Extract z, x, y from the URL (assuming standard {z}/{x}/{y} pattern in the path)
37
+ // The URL has already been templated by MapLibre, so we need to parse actual numbers
38
+ const urlObj = new URL(tileUrl);
39
+ const pathParts = urlObj.pathname.split('/').filter(p => p.length > 0);
40
+
41
+ // Find z/x/y pattern - typically last 3 numeric segments
42
+ let z, x, y;
43
+ for (let i = pathParts.length - 1; i >= 2; i--) {
44
+ const yPart = pathParts[i].replace(/\.[^.]+$/, ''); // Remove extension
45
+ const xPart = pathParts[i - 1];
46
+ const zPart = pathParts[i - 2];
47
+
48
+ if (/^\d+$/.test(zPart) && /^\d+$/.test(xPart) && /^\d+$/.test(yPart)) {
49
+ z = parseInt(zPart, 10);
50
+ x = parseInt(xPart, 10);
51
+ y = parseInt(yPart, 10);
52
+ break;
53
+ }
54
+ }
55
+
56
+ return { configId, tileUrl, z, x, y };
57
+ }
58
+
59
+ /**
60
+ * Fetch and fix a tile for MapLibre protocol.
61
+ * Extracted for testability.
62
+ * @param {string} tileUrl - URL of the raster tile
63
+ * @param {number} z - Zoom level
64
+ * @param {number} x - Tile X coordinate
65
+ * @param {number} y - Tile Y coordinate
66
+ * @param {TileFixer} tileFixer - TileFixer instance
67
+ * @param {Object} layerConfig - Layer configuration (can be null)
68
+ * @param {number} tileSize - Tile size in pixels
69
+ * @param {Object} [options] - Fetch options
70
+ * @param {AbortSignal} [options.signal] - Abort signal
71
+ * @returns {Promise<{data: ArrayBuffer, correctionsFailed: boolean, correctionsError: Error|null}>}
72
+ */
73
+ async function fetchAndFixTile(tileUrl, z, x, y, tileFixer, layerConfig, tileSize, options = {}) {
74
+ const { data, correctionsFailed, correctionsError } = await tileFixer.fetchAndFixTile(
75
+ tileUrl, z, x, y, layerConfig, { tileSize, signal: options.signal }
76
+ );
77
+ return { data, correctionsFailed, correctionsError };
78
+ }
79
+
80
+ /**
81
+ * India boundary corrections protocol for MapLibre GL.
82
+ *
83
+ * Usage:
84
+ * const protocol = new CorrectionProtocol();
85
+ * protocol.register(maplibregl);
86
+ *
87
+ * // In your style:
88
+ * tiles: ['ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png']
89
+ * // Or with explicit config:
90
+ * tiles: ['ibc://osm-carto@https://tile.openstreetmap.org/{z}/{x}/{y}.png']
91
+ */
92
+ export class CorrectionProtocol {
93
+ /**
94
+ * @param {Object} [options]
95
+ * @param {string} [options.pmtilesUrl] - URL to PMTiles file (defaults to CDN)
96
+ * @param {number} [options.tileSize=256] - Tile size in pixels
97
+ */
98
+ constructor(options = {}) {
99
+ this._pmtilesUrl = options.pmtilesUrl ?? getPmtilesUrl();
100
+ this._tileSize = options.tileSize ?? 256;
101
+ this._tileFixer = new TileFixer(this._pmtilesUrl);
102
+ this._registry = layerConfigs.createMergedRegistry();
103
+ /** @type {Map<string, Set<Function>>} */
104
+ this._listeners = new Map();
105
+
106
+ this._loadFn = this._createLoadFunction();
107
+ }
108
+
109
+ /**
110
+ * Add a listener for an event.
111
+ * @param {'correctionerror'} event - Event name
112
+ * @param {Function} listener - Callback function receiving event data
113
+ * @returns {this}
114
+ */
115
+ on(event, listener) {
116
+ if (!this._listeners.has(event)) {
117
+ this._listeners.set(event, new Set());
118
+ }
119
+ this._listeners.get(event).add(listener);
120
+ return this;
121
+ }
122
+
123
+ /**
124
+ * Remove an event listener.
125
+ * @param {'correctionerror'} event - Event name
126
+ * @param {Function} listener - Callback to remove
127
+ * @returns {this}
128
+ */
129
+ off(event, listener) {
130
+ this._listeners.get(event)?.delete(listener);
131
+ return this;
132
+ }
133
+
134
+ /**
135
+ * Emit an event to all listeners.
136
+ * @param {string} event - Event name
137
+ * @param {Object} data - Event data
138
+ * @private
139
+ */
140
+ _emit(event, data) {
141
+ this._listeners.get(event)?.forEach(fn => fn(data));
142
+ }
143
+
144
+ /**
145
+ * Add a custom layer config to the registry.
146
+ * @param {Object} layerConfig - LayerConfig to add
147
+ * @returns {this}
148
+ */
149
+ addLayerConfig(layerConfig) {
150
+ this._registry.register(layerConfig);
151
+ return this;
152
+ }
153
+
154
+ /**
155
+ * Get the registry.
156
+ * @returns {LayerConfigRegistry}
157
+ */
158
+ getRegistry() {
159
+ return this._registry;
160
+ }
161
+
162
+ /**
163
+ * Get the TileFixer instance.
164
+ * @returns {TileFixer}
165
+ */
166
+ getTileFixer() {
167
+ return this._tileFixer;
168
+ }
169
+
170
+ /**
171
+ * Register the protocol with MapLibre GL.
172
+ * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace
173
+ * @returns {this}
174
+ */
175
+ register(maplibregl) {
176
+ maplibregl.addProtocol(PROTOCOL_PREFIX, this._loadFn);
177
+ return this;
178
+ }
179
+
180
+ /**
181
+ * Unregister the protocol from MapLibre GL.
182
+ * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace
183
+ * @returns {this}
184
+ */
185
+ unregister(maplibregl) {
186
+ maplibregl.removeProtocol(PROTOCOL_PREFIX);
187
+ return this;
188
+ }
189
+
190
+ /**
191
+ * Create the protocol load function.
192
+ * @returns {Function}
193
+ * @private
194
+ */
195
+ _createLoadFunction() {
196
+ const self = this;
197
+
198
+ return async (params, abortController) => {
199
+ const { configId, tileUrl, z, x, y } = parseCorrectionsUrl(params.url);
200
+
201
+ // Validate parsed coordinates
202
+ if (z === undefined || x === undefined || y === undefined) {
203
+ console.warn(`[CorrectionProtocol] Could not parse tile coordinates from URL: ${params.url}, falling back to original`);
204
+ const response = await fetch(tileUrl, { signal: abortController?.signal });
205
+ return { data: await response.arrayBuffer() };
206
+ }
207
+
208
+ // Resolve layer config
209
+ let layerConfig;
210
+ if (configId) {
211
+ layerConfig = self._registry.get(configId);
212
+ } else {
213
+ layerConfig = self._registry.detectFromTileUrls([tileUrl]);
214
+ }
215
+
216
+ try {
217
+ const result = await fetchAndFixTile(
218
+ tileUrl,
219
+ z,
220
+ x,
221
+ y,
222
+ self._tileFixer,
223
+ layerConfig,
224
+ self._tileSize,
225
+ { signal: abortController?.signal }
226
+ );
227
+
228
+ if (result.correctionsFailed) {
229
+ console.warn('[CorrectionProtocol] Corrections fetch failed:', result.correctionsError);
230
+ self._emit('correctionerror', { error: result.correctionsError, coords: { z, x, y }, tileUrl });
231
+ }
232
+
233
+ return { data: result.data };
234
+ } catch (err) {
235
+ console.warn('[CorrectionProtocol] Error applying corrections, falling back to original:', err);
236
+ self._emit('correctionerror', { error: err, coords: { z, x, y }, tileUrl });
237
+ const response = await fetch(tileUrl, { signal: abortController?.signal });
238
+ return { data: await response.arrayBuffer() };
239
+ }
240
+ };
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Create and register a correction protocol with MapLibre GL.
246
+ *
247
+ * @param {typeof import('maplibre-gl')} maplibregl - MapLibre GL namespace
248
+ * @param {Object} [options] - Protocol options
249
+ * @param {string} [options.pmtilesUrl] - URL to PMTiles file
250
+ * @param {number} [options.tileSize=256] - Tile size in pixels
251
+ * @returns {CorrectionProtocol}
252
+ *
253
+ * @example
254
+ * import maplibregl from 'maplibre-gl';
255
+ * import { registerCorrectionProtocol } from '@india-boundary-corrector/maplibre-protocol';
256
+ *
257
+ * const protocol = registerCorrectionProtocol(maplibregl);
258
+ *
259
+ * // Use in style:
260
+ * const map = new maplibregl.Map({
261
+ * container: 'map',
262
+ * style: {
263
+ * sources: {
264
+ * osm: {
265
+ * type: 'raster',
266
+ * tiles: ['ibc://https://tile.openstreetmap.org/{z}/{x}/{y}.png'],
267
+ * tileSize: 256
268
+ * }
269
+ * },
270
+ * layers: [{ id: 'osm', type: 'raster', source: 'osm' }]
271
+ * }
272
+ * });
273
+ */
274
+ export function registerCorrectionProtocol(maplibregl, options = {}) {
275
+ const protocol = new CorrectionProtocol(options);
276
+ return protocol.register(maplibregl);
277
+ }
278
+
279
+ // Export for testing
280
+ export { parseCorrectionsUrl, fetchAndFixTile };