@india-boundary-corrector/openlayers-layer 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,161 @@
1
+ // src/index.js
2
+ import TileLayer from "ol/layer/Tile.js";
3
+ import XYZ from "ol/source/XYZ.js";
4
+ import { getPmtilesUrl } from "@india-boundary-corrector/data";
5
+ import { layerConfigs } from "@india-boundary-corrector/layer-configs";
6
+ import { BoundaryCorrector as TileFixer } from "@india-boundary-corrector/tilefixer";
7
+ import { layerConfigs as layerConfigs2, LayerConfig } from "@india-boundary-corrector/layer-configs";
8
+ import { getPmtilesUrl as getPmtilesUrl2 } from "@india-boundary-corrector/data";
9
+ async function fetchAndFixTile(src, z, x, y, tileFixer, layerConfig, tileSize) {
10
+ const { data, wasFixed, correctionsFailed, correctionsError } = await tileFixer.fetchAndFixTile(
11
+ src,
12
+ z,
13
+ x,
14
+ y,
15
+ layerConfig,
16
+ { tileSize, mode: "cors" }
17
+ );
18
+ const blob = new Blob([data], { type: wasFixed ? "image/png" : void 0 });
19
+ return { blob, wasFixed, correctionsFailed, correctionsError };
20
+ }
21
+ function createCorrectedTileLoadFunction(tileFixer, layerConfig, tileSize, layer) {
22
+ return async function(imageTile, src) {
23
+ const tileCoord = imageTile.getTileCoord();
24
+ const z = tileCoord[0];
25
+ const x = tileCoord[1];
26
+ const y = tileCoord[2];
27
+ try {
28
+ const { blob, correctionsFailed, correctionsError } = await fetchAndFixTile(src, z, x, y, tileFixer, layerConfig, tileSize);
29
+ if (correctionsFailed) {
30
+ console.warn("[IndiaBoundaryCorrectedTileLayer] Corrections fetch failed:", correctionsError);
31
+ layer.dispatchEvent({ type: "correctionerror", error: correctionsError, coords: { z, x, y }, tileUrl: src });
32
+ }
33
+ const image = imageTile.getImage();
34
+ if (typeof image.getContext === "function") {
35
+ const imageBitmap = await createImageBitmap(blob);
36
+ image.width = imageBitmap.width;
37
+ image.height = imageBitmap.height;
38
+ const ctx = image.getContext("2d");
39
+ ctx.drawImage(imageBitmap, 0, 0);
40
+ imageBitmap.close?.();
41
+ image.dispatchEvent(new Event("load"));
42
+ } else {
43
+ const blobUrl = URL.createObjectURL(blob);
44
+ image.onload = () => {
45
+ URL.revokeObjectURL(blobUrl);
46
+ };
47
+ image.onerror = () => {
48
+ URL.revokeObjectURL(blobUrl);
49
+ };
50
+ image.src = blobUrl;
51
+ }
52
+ } catch (err) {
53
+ console.warn("[IndiaBoundaryCorrectedTileLayer] Error applying corrections, falling back to original:", err);
54
+ layer.dispatchEvent({ type: "correctionerror", error: err, coords: { z, x, y }, tileUrl: src });
55
+ const image = imageTile.getImage();
56
+ if (typeof image.src !== "undefined") {
57
+ image.src = src;
58
+ } else if (typeof image.dispatchEvent === "function") {
59
+ image.dispatchEvent(new Event("error"));
60
+ }
61
+ }
62
+ };
63
+ }
64
+ var IndiaBoundaryCorrectedTileLayer = class extends TileLayer {
65
+ /**
66
+ * @param {Object} options - Layer options
67
+ * @param {string} options.url - Tile URL template with {x}, {y}, {z} placeholders
68
+ * @param {string} [options.pmtilesUrl] - URL to PMTiles file (defaults to CDN)
69
+ * @param {Object|string} [options.layerConfig] - LayerConfig or config ID (auto-detected if not provided)
70
+ * @param {Object[]} [options.extraLayerConfigs] - Additional LayerConfigs for matching
71
+ * @param {number} [options.tileSize=256] - Tile size in pixels
72
+ * @param {Object} [options.sourceOptions] - Additional options for XYZ source
73
+ * @param {Object} [options.layerOptions] - Additional options for TileLayer
74
+ */
75
+ constructor(options) {
76
+ const {
77
+ url,
78
+ pmtilesUrl,
79
+ layerConfig,
80
+ extraLayerConfigs,
81
+ tileSize = 256,
82
+ sourceOptions = {},
83
+ ...layerOptions
84
+ } = options;
85
+ const registry = layerConfigs.createMergedRegistry(extraLayerConfigs);
86
+ let resolvedConfig;
87
+ if (typeof layerConfig === "string") {
88
+ resolvedConfig = registry.get(layerConfig);
89
+ } else if (layerConfig) {
90
+ resolvedConfig = layerConfig;
91
+ } else {
92
+ resolvedConfig = registry.detectFromTemplates([url]);
93
+ }
94
+ const tileFixer = new TileFixer(pmtilesUrl ?? getPmtilesUrl());
95
+ const source = new XYZ({
96
+ url,
97
+ tileSize,
98
+ crossOrigin: "anonymous",
99
+ ...sourceOptions
100
+ });
101
+ super({
102
+ source,
103
+ ...layerOptions
104
+ });
105
+ this._tileFixer = tileFixer;
106
+ this._layerConfig = resolvedConfig;
107
+ this._registry = registry;
108
+ if (resolvedConfig) {
109
+ source.setTileLoadFunction(createCorrectedTileLoadFunction(tileFixer, resolvedConfig, tileSize, this));
110
+ }
111
+ if (!resolvedConfig) {
112
+ console.warn("[IndiaBoundaryCorrectedTileLayer] Could not detect layer config from URL. Corrections will not be applied.");
113
+ }
114
+ }
115
+ /**
116
+ * Get the TileFixer instance.
117
+ * @returns {TileFixer}
118
+ */
119
+ getTileFixer() {
120
+ return this._tileFixer;
121
+ }
122
+ /**
123
+ * Get the resolved LayerConfig.
124
+ * @returns {Object|null}
125
+ */
126
+ getLayerConfig() {
127
+ return this._layerConfig;
128
+ }
129
+ /**
130
+ * Get the registry.
131
+ * @returns {LayerConfigRegistry}
132
+ */
133
+ getRegistry() {
134
+ return this._registry;
135
+ }
136
+ /**
137
+ * Fetch and fix a tile (exposed for testing).
138
+ * @param {string} src - Tile URL
139
+ * @param {number} z - Zoom level
140
+ * @param {number} x - Tile X coordinate
141
+ * @param {number} y - Tile Y coordinate
142
+ * @returns {Promise<{blob: Blob, wasFixed: boolean}>}
143
+ * @private
144
+ */
145
+ async _fetchAndFixTile(src, z, x, y) {
146
+ const tileSize = this.getSource().getTileGrid()?.getTileSize(z) || 256;
147
+ return fetchAndFixTile(src, z, x, y, this._tileFixer, this._layerConfig, tileSize);
148
+ }
149
+ };
150
+ function indiaBoundaryCorrectedTileLayer(options) {
151
+ return new IndiaBoundaryCorrectedTileLayer(options);
152
+ }
153
+ export {
154
+ IndiaBoundaryCorrectedTileLayer,
155
+ LayerConfig,
156
+ fetchAndFixTile,
157
+ getPmtilesUrl2 as getPmtilesUrl,
158
+ indiaBoundaryCorrectedTileLayer,
159
+ layerConfigs2 as layerConfigs
160
+ };
161
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.js"],"sourcesContent":["import TileLayer from 'ol/layer/Tile.js';\nimport XYZ from 'ol/source/XYZ.js';\nimport { 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\n/**\n * Handle tile fetching and correction application logic.\n * This method is extracted for testability.\n * @param {string} src - 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\n * @param {number} tileSize - Tile size in pixels\n * @returns {Promise<{blob: Blob, wasFixed: boolean, correctionsFailed: boolean, correctionsError: Error|null}>}\n */\nasync function fetchAndFixTile(src, z, x, y, tileFixer, layerConfig, tileSize) {\n const { data, wasFixed, correctionsFailed, correctionsError } = await tileFixer.fetchAndFixTile(\n src, z, x, y, layerConfig, { tileSize, mode: 'cors' }\n );\n const blob = new Blob([data], { type: wasFixed ? 'image/png' : undefined });\n return { blob, wasFixed, correctionsFailed, correctionsError };\n}\n\n/**\n * Create a custom tileLoadFunction that applies boundary corrections.\n * @param {TileFixer} tileFixer - The TileFixer instance\n * @param {Object} layerConfig - The layer configuration\n * @param {number} tileSize - Tile size in pixels\n * @param {IndiaBoundaryCorrectedTileLayer} layer - The layer instance for event dispatching\n * @returns {Function} Custom tile load function\n */\nfunction createCorrectedTileLoadFunction(tileFixer, layerConfig, tileSize, layer) {\n return async function(imageTile, src) {\n const tileCoord = imageTile.getTileCoord();\n const z = tileCoord[0];\n const x = tileCoord[1];\n const y = tileCoord[2];\n\n // TODO: Pass AbortSignal to fetchAndFixTile to cancel in-flight requests when tiles\n // go off-screen. OpenLayers' tileLoadFunction doesn't provide an AbortController,\n // so this would require custom tracking. Deferred due to complexity - will revisit\n // if performance becomes an issue during rapid panning.\n try {\n const { blob, correctionsFailed, correctionsError } = await fetchAndFixTile(src, z, x, y, tileFixer, layerConfig, tileSize);\n\n if (correctionsFailed) {\n console.warn('[IndiaBoundaryCorrectedTileLayer] Corrections fetch failed:', correctionsError);\n layer.dispatchEvent({ type: 'correctionerror', error: correctionsError, coords: { z, x, y }, tileUrl: src });\n }\n\n const image = imageTile.getImage();\n \n // Check if image is a canvas (OffscreenCanvas) or HTMLImageElement\n if (typeof image.getContext === 'function') {\n // OffscreenCanvas path\n const imageBitmap = await createImageBitmap(blob);\n image.width = imageBitmap.width;\n image.height = imageBitmap.height;\n const ctx = image.getContext('2d');\n ctx.drawImage(imageBitmap, 0, 0);\n imageBitmap.close?.();\n image.dispatchEvent(new Event('load'));\n } else {\n // HTMLImageElement path - use blob URL\n const blobUrl = URL.createObjectURL(blob);\n image.onload = () => {\n URL.revokeObjectURL(blobUrl);\n };\n image.onerror = () => {\n URL.revokeObjectURL(blobUrl);\n };\n image.src = blobUrl;\n }\n } catch (err) {\n console.warn('[IndiaBoundaryCorrectedTileLayer] Error applying corrections, falling back to original:', err);\n layer.dispatchEvent({ type: 'correctionerror', error: err, coords: { z, x, y }, tileUrl: src });\n // Fall back to original tile\n const image = imageTile.getImage();\n if (typeof image.src !== 'undefined') {\n // HTMLImageElement - load original tile\n image.src = src;\n } else if (typeof image.dispatchEvent === 'function') {\n // OffscreenCanvas - can't fall back, signal error\n image.dispatchEvent(new Event('error'));\n }\n }\n };\n}\n\n/**\n * Extended OpenLayers TileLayer with India boundary corrections.\n * Extends ol/layer/Tile with a custom XYZ source that applies corrections.\n */\nexport class IndiaBoundaryCorrectedTileLayer extends TileLayer {\n /**\n * @param {Object} options - Layer options\n * @param {string} options.url - Tile URL template with {x}, {y}, {z} placeholders\n * @param {string} [options.pmtilesUrl] - URL to PMTiles file (defaults to CDN)\n * @param {Object|string} [options.layerConfig] - LayerConfig or config ID (auto-detected if not provided)\n * @param {Object[]} [options.extraLayerConfigs] - Additional LayerConfigs for matching\n * @param {number} [options.tileSize=256] - Tile size in pixels\n * @param {Object} [options.sourceOptions] - Additional options for XYZ source\n * @param {Object} [options.layerOptions] - Additional options for TileLayer\n */\n constructor(options) {\n const {\n url,\n pmtilesUrl,\n layerConfig,\n extraLayerConfigs,\n tileSize = 256,\n sourceOptions = {},\n ...layerOptions\n } = options;\n\n // Initialize registry and resolve layer config\n const registry = layerConfigs.createMergedRegistry(extraLayerConfigs);\n let resolvedConfig;\n \n if (typeof layerConfig === 'string') {\n resolvedConfig = registry.get(layerConfig);\n } else if (layerConfig) {\n resolvedConfig = layerConfig;\n } else {\n // Auto-detect from URL\n resolvedConfig = registry.detectFromTemplates([url]);\n }\n\n // Create TileFixer\n const tileFixer = new TileFixer(pmtilesUrl ?? getPmtilesUrl());\n\n // Create XYZ source (tileLoadFunction set after super() to access 'this')\n const source = new XYZ({\n url,\n tileSize,\n crossOrigin: 'anonymous',\n ...sourceOptions,\n });\n\n super({\n source,\n ...layerOptions\n });\n\n this._tileFixer = tileFixer;\n this._layerConfig = resolvedConfig;\n this._registry = registry;\n\n // Set tileLoadFunction after super() so we can pass 'this' for event dispatching\n if (resolvedConfig) {\n source.setTileLoadFunction(createCorrectedTileLoadFunction(tileFixer, resolvedConfig, tileSize, this));\n }\n\n if (!resolvedConfig) {\n console.warn('[IndiaBoundaryCorrectedTileLayer] Could not detect layer config from URL. Corrections will not be applied.');\n }\n }\n\n /**\n * Get the TileFixer instance.\n * @returns {TileFixer}\n */\n getTileFixer() {\n return this._tileFixer;\n }\n\n /**\n * Get the resolved LayerConfig.\n * @returns {Object|null}\n */\n getLayerConfig() {\n return this._layerConfig;\n }\n\n /**\n * Get the registry.\n * @returns {LayerConfigRegistry}\n */\n getRegistry() {\n return this._registry;\n }\n\n /**\n * Fetch and fix a tile (exposed for testing).\n * @param {string} src - Tile URL\n * @param {number} z - Zoom level\n * @param {number} x - Tile X coordinate\n * @param {number} y - Tile Y coordinate\n * @returns {Promise<{blob: Blob, wasFixed: boolean}>}\n * @private\n */\n async _fetchAndFixTile(src, z, x, y) {\n const tileSize = this.getSource().getTileGrid()?.getTileSize(z) || 256;\n return fetchAndFixTile(src, z, x, y, this._tileFixer, this._layerConfig, tileSize);\n }\n}\n\n// Export for testing\nexport { fetchAndFixTile };\n\n/**\n * Factory function to create an IndiaBoundaryCorrectedTileLayer.\n * @param {Object} options - Layer options (see IndiaBoundaryCorrectedTileLayer constructor)\n * @returns {IndiaBoundaryCorrectedTileLayer}\n */\nexport function indiaBoundaryCorrectedTileLayer(options) {\n return new IndiaBoundaryCorrectedTileLayer(options);\n}\n"],"mappings":"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","names":["layerConfigs","getPmtilesUrl"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@india-boundary-corrector/openlayers-layer",
3
+ "version": "0.0.1",
4
+ "description": "OpenLayers TileLayer with 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
+ "ol": ">=7.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
+ "openlayers",
41
+ "tilelayer"
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/openlayers-layer"
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,48 @@
1
+ import TileLayer from 'ol/layer/Tile';
2
+ import XYZ from 'ol/source/XYZ';
3
+ import { LayerConfig, LayerConfigRegistry } from '@india-boundary-corrector/layer-configs';
4
+ import { BoundaryCorrector as TileFixer } from '@india-boundary-corrector/tilefixer';
5
+
6
+ export { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
7
+ export { getPmtilesUrl } from '@india-boundary-corrector/data';
8
+
9
+ /**
10
+ * Options for IndiaBoundaryCorrectedTileLayer
11
+ */
12
+ export interface IndiaBoundaryCorrectedTileLayerOptions {
13
+ /** Tile URL template with {x}, {y}, {z} placeholders */
14
+ url: string;
15
+ /** URL to PMTiles file (defaults to CDN) */
16
+ pmtilesUrl?: string;
17
+ /** LayerConfig object or config ID string (auto-detected from URL if not provided) */
18
+ layerConfig?: LayerConfig | string;
19
+ /** Additional layer configs for matching */
20
+ extraLayerConfigs?: LayerConfig[];
21
+ /** Tile size in pixels (default: 256) */
22
+ tileSize?: number;
23
+ /** Additional options for XYZ source */
24
+ sourceOptions?: Partial<ConstructorParameters<typeof XYZ>[0]>;
25
+ /** Additional options for TileLayer (opacity, visible, etc.) */
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ /**
30
+ * Extended OpenLayers TileLayer with India boundary corrections.
31
+ */
32
+ export declare class IndiaBoundaryCorrectedTileLayer extends TileLayer<XYZ> {
33
+ constructor(options: IndiaBoundaryCorrectedTileLayerOptions);
34
+
35
+ /** Get the TileFixer instance */
36
+ getTileFixer(): TileFixer;
37
+
38
+ /** Get the resolved LayerConfig */
39
+ getLayerConfig(): LayerConfig | null;
40
+
41
+ /** Get the registry */
42
+ getRegistry(): LayerConfigRegistry;
43
+ }
44
+
45
+ /**
46
+ * Factory function to create an IndiaBoundaryCorrectedTileLayer.
47
+ */
48
+ export declare function indiaBoundaryCorrectedTileLayer(options: IndiaBoundaryCorrectedTileLayerOptions): IndiaBoundaryCorrectedTileLayer;
package/src/index.js ADDED
@@ -0,0 +1,215 @@
1
+ import TileLayer from 'ol/layer/Tile.js';
2
+ import XYZ from 'ol/source/XYZ.js';
3
+ import { getPmtilesUrl } from '@india-boundary-corrector/data';
4
+ import { layerConfigs } from '@india-boundary-corrector/layer-configs';
5
+ import { BoundaryCorrector as TileFixer } from '@india-boundary-corrector/tilefixer';
6
+
7
+ // Re-export for convenience
8
+ export { layerConfigs, LayerConfig } from '@india-boundary-corrector/layer-configs';
9
+ export { getPmtilesUrl } from '@india-boundary-corrector/data';
10
+
11
+ /**
12
+ * Handle tile fetching and correction application logic.
13
+ * This method is extracted for testability.
14
+ * @param {string} src - URL of the raster tile
15
+ * @param {number} z - Zoom level
16
+ * @param {number} x - Tile X coordinate
17
+ * @param {number} y - Tile Y coordinate
18
+ * @param {TileFixer} tileFixer - TileFixer instance
19
+ * @param {Object} layerConfig - Layer configuration
20
+ * @param {number} tileSize - Tile size in pixels
21
+ * @returns {Promise<{blob: Blob, wasFixed: boolean, correctionsFailed: boolean, correctionsError: Error|null}>}
22
+ */
23
+ async function fetchAndFixTile(src, z, x, y, tileFixer, layerConfig, tileSize) {
24
+ const { data, wasFixed, correctionsFailed, correctionsError } = await tileFixer.fetchAndFixTile(
25
+ src, z, x, y, layerConfig, { tileSize, mode: 'cors' }
26
+ );
27
+ const blob = new Blob([data], { type: wasFixed ? 'image/png' : undefined });
28
+ return { blob, wasFixed, correctionsFailed, correctionsError };
29
+ }
30
+
31
+ /**
32
+ * Create a custom tileLoadFunction that applies boundary corrections.
33
+ * @param {TileFixer} tileFixer - The TileFixer instance
34
+ * @param {Object} layerConfig - The layer configuration
35
+ * @param {number} tileSize - Tile size in pixels
36
+ * @param {IndiaBoundaryCorrectedTileLayer} layer - The layer instance for event dispatching
37
+ * @returns {Function} Custom tile load function
38
+ */
39
+ function createCorrectedTileLoadFunction(tileFixer, layerConfig, tileSize, layer) {
40
+ return async function(imageTile, src) {
41
+ const tileCoord = imageTile.getTileCoord();
42
+ const z = tileCoord[0];
43
+ const x = tileCoord[1];
44
+ const y = tileCoord[2];
45
+
46
+ // TODO: Pass AbortSignal to fetchAndFixTile to cancel in-flight requests when tiles
47
+ // go off-screen. OpenLayers' tileLoadFunction doesn't provide an AbortController,
48
+ // so this would require custom tracking. Deferred due to complexity - will revisit
49
+ // if performance becomes an issue during rapid panning.
50
+ try {
51
+ const { blob, correctionsFailed, correctionsError } = await fetchAndFixTile(src, z, x, y, tileFixer, layerConfig, tileSize);
52
+
53
+ if (correctionsFailed) {
54
+ console.warn('[IndiaBoundaryCorrectedTileLayer] Corrections fetch failed:', correctionsError);
55
+ layer.dispatchEvent({ type: 'correctionerror', error: correctionsError, coords: { z, x, y }, tileUrl: src });
56
+ }
57
+
58
+ const image = imageTile.getImage();
59
+
60
+ // Check if image is a canvas (OffscreenCanvas) or HTMLImageElement
61
+ if (typeof image.getContext === 'function') {
62
+ // OffscreenCanvas path
63
+ const imageBitmap = await createImageBitmap(blob);
64
+ image.width = imageBitmap.width;
65
+ image.height = imageBitmap.height;
66
+ const ctx = image.getContext('2d');
67
+ ctx.drawImage(imageBitmap, 0, 0);
68
+ imageBitmap.close?.();
69
+ image.dispatchEvent(new Event('load'));
70
+ } else {
71
+ // HTMLImageElement path - use blob URL
72
+ const blobUrl = URL.createObjectURL(blob);
73
+ image.onload = () => {
74
+ URL.revokeObjectURL(blobUrl);
75
+ };
76
+ image.onerror = () => {
77
+ URL.revokeObjectURL(blobUrl);
78
+ };
79
+ image.src = blobUrl;
80
+ }
81
+ } catch (err) {
82
+ console.warn('[IndiaBoundaryCorrectedTileLayer] Error applying corrections, falling back to original:', err);
83
+ layer.dispatchEvent({ type: 'correctionerror', error: err, coords: { z, x, y }, tileUrl: src });
84
+ // Fall back to original tile
85
+ const image = imageTile.getImage();
86
+ if (typeof image.src !== 'undefined') {
87
+ // HTMLImageElement - load original tile
88
+ image.src = src;
89
+ } else if (typeof image.dispatchEvent === 'function') {
90
+ // OffscreenCanvas - can't fall back, signal error
91
+ image.dispatchEvent(new Event('error'));
92
+ }
93
+ }
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Extended OpenLayers TileLayer with India boundary corrections.
99
+ * Extends ol/layer/Tile with a custom XYZ source that applies corrections.
100
+ */
101
+ export class IndiaBoundaryCorrectedTileLayer extends TileLayer {
102
+ /**
103
+ * @param {Object} options - Layer options
104
+ * @param {string} options.url - Tile URL template with {x}, {y}, {z} placeholders
105
+ * @param {string} [options.pmtilesUrl] - URL to PMTiles file (defaults to CDN)
106
+ * @param {Object|string} [options.layerConfig] - LayerConfig or config ID (auto-detected if not provided)
107
+ * @param {Object[]} [options.extraLayerConfigs] - Additional LayerConfigs for matching
108
+ * @param {number} [options.tileSize=256] - Tile size in pixels
109
+ * @param {Object} [options.sourceOptions] - Additional options for XYZ source
110
+ * @param {Object} [options.layerOptions] - Additional options for TileLayer
111
+ */
112
+ constructor(options) {
113
+ const {
114
+ url,
115
+ pmtilesUrl,
116
+ layerConfig,
117
+ extraLayerConfigs,
118
+ tileSize = 256,
119
+ sourceOptions = {},
120
+ ...layerOptions
121
+ } = options;
122
+
123
+ // Initialize registry and resolve layer config
124
+ const registry = layerConfigs.createMergedRegistry(extraLayerConfigs);
125
+ let resolvedConfig;
126
+
127
+ if (typeof layerConfig === 'string') {
128
+ resolvedConfig = registry.get(layerConfig);
129
+ } else if (layerConfig) {
130
+ resolvedConfig = layerConfig;
131
+ } else {
132
+ // Auto-detect from URL
133
+ resolvedConfig = registry.detectFromTemplates([url]);
134
+ }
135
+
136
+ // Create TileFixer
137
+ const tileFixer = new TileFixer(pmtilesUrl ?? getPmtilesUrl());
138
+
139
+ // Create XYZ source (tileLoadFunction set after super() to access 'this')
140
+ const source = new XYZ({
141
+ url,
142
+ tileSize,
143
+ crossOrigin: 'anonymous',
144
+ ...sourceOptions,
145
+ });
146
+
147
+ super({
148
+ source,
149
+ ...layerOptions
150
+ });
151
+
152
+ this._tileFixer = tileFixer;
153
+ this._layerConfig = resolvedConfig;
154
+ this._registry = registry;
155
+
156
+ // Set tileLoadFunction after super() so we can pass 'this' for event dispatching
157
+ if (resolvedConfig) {
158
+ source.setTileLoadFunction(createCorrectedTileLoadFunction(tileFixer, resolvedConfig, tileSize, this));
159
+ }
160
+
161
+ if (!resolvedConfig) {
162
+ console.warn('[IndiaBoundaryCorrectedTileLayer] Could not detect layer config from URL. Corrections will not be applied.');
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get the TileFixer instance.
168
+ * @returns {TileFixer}
169
+ */
170
+ getTileFixer() {
171
+ return this._tileFixer;
172
+ }
173
+
174
+ /**
175
+ * Get the resolved LayerConfig.
176
+ * @returns {Object|null}
177
+ */
178
+ getLayerConfig() {
179
+ return this._layerConfig;
180
+ }
181
+
182
+ /**
183
+ * Get the registry.
184
+ * @returns {LayerConfigRegistry}
185
+ */
186
+ getRegistry() {
187
+ return this._registry;
188
+ }
189
+
190
+ /**
191
+ * Fetch and fix a tile (exposed for testing).
192
+ * @param {string} src - Tile URL
193
+ * @param {number} z - Zoom level
194
+ * @param {number} x - Tile X coordinate
195
+ * @param {number} y - Tile Y coordinate
196
+ * @returns {Promise<{blob: Blob, wasFixed: boolean}>}
197
+ * @private
198
+ */
199
+ async _fetchAndFixTile(src, z, x, y) {
200
+ const tileSize = this.getSource().getTileGrid()?.getTileSize(z) || 256;
201
+ return fetchAndFixTile(src, z, x, y, this._tileFixer, this._layerConfig, tileSize);
202
+ }
203
+ }
204
+
205
+ // Export for testing
206
+ export { fetchAndFixTile };
207
+
208
+ /**
209
+ * Factory function to create an IndiaBoundaryCorrectedTileLayer.
210
+ * @param {Object} options - Layer options (see IndiaBoundaryCorrectedTileLayer constructor)
211
+ * @returns {IndiaBoundaryCorrectedTileLayer}
212
+ */
213
+ export function indiaBoundaryCorrectedTileLayer(options) {
214
+ return new IndiaBoundaryCorrectedTileLayer(options);
215
+ }