@india-boundary-corrector/tilefixer 0.0.2 → 0.0.4
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 +14 -9
- package/dist/index.cjs +121 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +123 -30
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/corrections.js +81 -20
- package/src/index.d.ts +48 -12
- package/src/index.js +86 -13
package/README.md
CHANGED
|
@@ -13,12 +13,12 @@ npm install @india-boundary-corrector/tilefixer
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
|
15
15
|
```javascript
|
|
16
|
-
import {
|
|
16
|
+
import { TileFixer } from '@india-boundary-corrector/tilefixer';
|
|
17
17
|
import { getPmtilesUrl } from '@india-boundary-corrector/data';
|
|
18
18
|
import { layerConfigs } from '@india-boundary-corrector/layer-configs';
|
|
19
19
|
|
|
20
20
|
// Create a boundary corrector
|
|
21
|
-
const corrector = new
|
|
21
|
+
const corrector = new TileFixer(getPmtilesUrl());
|
|
22
22
|
|
|
23
23
|
// Get corrections for a tile
|
|
24
24
|
const corrections = await corrector.getCorrections(z, x, y);
|
|
@@ -30,32 +30,38 @@ const fixedTileData = await corrector.fixTile(
|
|
|
30
30
|
corrections,
|
|
31
31
|
originalTileArrayBuffer,
|
|
32
32
|
layerConfig,
|
|
33
|
-
z
|
|
34
|
-
256 // tile size (optional, defaults to 256)
|
|
33
|
+
z // zoom level (tile size is derived from image)
|
|
35
34
|
);
|
|
36
35
|
```
|
|
37
36
|
|
|
38
37
|
## API
|
|
39
38
|
|
|
40
|
-
### `
|
|
39
|
+
### `TileFixer`
|
|
40
|
+
|
|
41
|
+
#### Static Methods
|
|
42
|
+
|
|
43
|
+
| Method | Returns | Description |
|
|
44
|
+
|--------|---------|-------------|
|
|
45
|
+
| `TileFixer.getOrCreate(pmtilesUrl)` | `TileFixer` | Get or create a TileFixer for a URL (reuses existing instances) |
|
|
46
|
+
| `TileFixer.setDefaultCacheMaxFeatures(count)` | `void` | Set default max features to cache for new instances (default: 25000) |
|
|
41
47
|
|
|
42
48
|
#### Constructor
|
|
43
49
|
|
|
44
50
|
```javascript
|
|
45
|
-
new
|
|
51
|
+
new TileFixer(pmtilesUrl, options?)
|
|
46
52
|
```
|
|
47
53
|
|
|
48
54
|
| Parameter | Type | Description |
|
|
49
55
|
|-----------|------|-------------|
|
|
50
56
|
| `pmtilesUrl` | string | URL to the PMTiles file |
|
|
51
|
-
| `options.
|
|
57
|
+
| `options.cacheMaxFeatures` | number | Maximum features to cache |
|
|
52
58
|
|
|
53
59
|
#### Methods
|
|
54
60
|
|
|
55
61
|
| Method | Returns | Description |
|
|
56
62
|
|--------|---------|-------------|
|
|
57
63
|
| `getCorrections(z, x, y)` | `Promise<Object>` | Get correction features for a tile. Supports overzoom beyond zoom 14. |
|
|
58
|
-
| `fixTile(corrections, rasterTile, layerConfig, zoom
|
|
64
|
+
| `fixTile(corrections, rasterTile, layerConfig, zoom)` | `Promise<ArrayBuffer>` | Apply corrections to a raster tile and return corrected PNG. Tile size is derived from the image. |
|
|
59
65
|
| `fetchAndFixTile(tileUrl, z, x, y, layerConfig, options?)` | `Promise<Object>` | Fetch a tile, apply corrections, and return result. See below. |
|
|
60
66
|
| `getSource()` | `PMTiles` | Get the underlying PMTiles source object |
|
|
61
67
|
| `clearCache()` | `void` | Clear the tile cache |
|
|
@@ -64,7 +70,6 @@ new BoundaryCorrector(pmtilesUrl, options?)
|
|
|
64
70
|
|
|
65
71
|
```javascript
|
|
66
72
|
const result = await corrector.fetchAndFixTile(tileUrl, z, x, y, layerConfig, {
|
|
67
|
-
tileSize: 256, // Tile size in pixels (default: 256)
|
|
68
73
|
signal: abortSignal, // AbortSignal for cancellation
|
|
69
74
|
mode: 'cors', // Fetch mode
|
|
70
75
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
7
|
var __export = (target, all) => {
|
|
7
8
|
for (var name in all)
|
|
8
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -16,12 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
17
|
return to;
|
|
17
18
|
};
|
|
18
19
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
19
21
|
|
|
20
22
|
// src/index.js
|
|
21
23
|
var index_exports = {};
|
|
22
24
|
__export(index_exports, {
|
|
23
|
-
BoundaryCorrector: () => BoundaryCorrector,
|
|
24
25
|
MIN_LINE_WIDTH: () => MIN_LINE_WIDTH,
|
|
26
|
+
TileFetchError: () => TileFetchError,
|
|
27
|
+
TileFixer: () => TileFixer,
|
|
25
28
|
getLineWidth: () => getLineWidth
|
|
26
29
|
});
|
|
27
30
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -2052,10 +2055,16 @@ function writeUtf8(buf, str, pos) {
|
|
|
2052
2055
|
}
|
|
2053
2056
|
|
|
2054
2057
|
// src/corrections.js
|
|
2055
|
-
var DEFAULT_CACHE_SIZE = 64;
|
|
2056
2058
|
function toIndex(z2, x2, y) {
|
|
2057
2059
|
return `${x2}:${y}:${z2}`;
|
|
2058
2060
|
}
|
|
2061
|
+
function countFeatures(corrections) {
|
|
2062
|
+
let count = 0;
|
|
2063
|
+
for (const features of Object.values(corrections)) {
|
|
2064
|
+
count += features.length;
|
|
2065
|
+
}
|
|
2066
|
+
return count;
|
|
2067
|
+
}
|
|
2059
2068
|
function parseTile(buffer) {
|
|
2060
2069
|
const tile = new VectorTile(new Pbf(buffer));
|
|
2061
2070
|
const result = {};
|
|
@@ -2104,16 +2113,19 @@ var CorrectionsSource = class {
|
|
|
2104
2113
|
/**
|
|
2105
2114
|
* @param {string} pmtilesUrl - URL to the PMTiles file
|
|
2106
2115
|
* @param {Object} [options] - Options
|
|
2107
|
-
* @param {number} [options.
|
|
2116
|
+
* @param {number} [options.cacheMaxFeatures=10000] - Maximum number of features to cache
|
|
2108
2117
|
* @param {number} [options.maxDataZoom] - Maximum zoom level in PMTiles (auto-detected if not provided)
|
|
2109
2118
|
*/
|
|
2110
2119
|
constructor(pmtilesUrl, options = {}) {
|
|
2111
2120
|
this.pmtilesUrl = pmtilesUrl;
|
|
2112
2121
|
this.pmtiles = new x(pmtilesUrl);
|
|
2113
|
-
this.
|
|
2122
|
+
this.cacheMaxFeatures = options.cacheMaxFeatures;
|
|
2114
2123
|
this.maxDataZoom = options.maxDataZoom;
|
|
2115
2124
|
this.cache = /* @__PURE__ */ new Map();
|
|
2116
2125
|
this.inflight = /* @__PURE__ */ new Map();
|
|
2126
|
+
this.cachedFeatureCount = 0;
|
|
2127
|
+
this.emptyKeys = /* @__PURE__ */ new Set();
|
|
2128
|
+
this.nonEmptyKeys = /* @__PURE__ */ new Set();
|
|
2117
2129
|
}
|
|
2118
2130
|
/**
|
|
2119
2131
|
* Get the PMTiles source object.
|
|
@@ -2128,6 +2140,35 @@ var CorrectionsSource = class {
|
|
|
2128
2140
|
clearCache() {
|
|
2129
2141
|
this.cache.clear();
|
|
2130
2142
|
this.inflight.clear();
|
|
2143
|
+
this.cachedFeatureCount = 0;
|
|
2144
|
+
this.emptyKeys.clear();
|
|
2145
|
+
this.nonEmptyKeys.clear();
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Evict cache entries to stay under the feature limit.
|
|
2149
|
+
* Empty entries are evicted first (cheap to re-fetch from PMTiles directory cache).
|
|
2150
|
+
* Within each category, evicts LRU (least recently used) entries.
|
|
2151
|
+
* @private
|
|
2152
|
+
*/
|
|
2153
|
+
_evictIfNeeded() {
|
|
2154
|
+
while (this.cachedFeatureCount > this.cacheMaxFeatures && this.cache.size > 1) {
|
|
2155
|
+
const targetSet = this.emptyKeys.size > 0 ? this.emptyKeys : this.nonEmptyKeys;
|
|
2156
|
+
if (targetSet.size === 0) break;
|
|
2157
|
+
let evictKey = void 0;
|
|
2158
|
+
let minUsed = Infinity;
|
|
2159
|
+
for (const key of targetSet) {
|
|
2160
|
+
const entry = this.cache.get(key);
|
|
2161
|
+
if (entry && entry.used < minUsed) {
|
|
2162
|
+
minUsed = entry.used;
|
|
2163
|
+
evictKey = key;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
if (!evictKey) break;
|
|
2167
|
+
const evicted = this.cache.get(evictKey);
|
|
2168
|
+
this.cachedFeatureCount -= evicted.featureCount;
|
|
2169
|
+
targetSet.delete(evictKey);
|
|
2170
|
+
this.cache.delete(evictKey);
|
|
2171
|
+
}
|
|
2131
2172
|
}
|
|
2132
2173
|
/**
|
|
2133
2174
|
* Auto-detect max zoom from PMTiles metadata.
|
|
@@ -2173,7 +2214,11 @@ var CorrectionsSource = class {
|
|
|
2173
2214
|
} else {
|
|
2174
2215
|
data = {};
|
|
2175
2216
|
}
|
|
2176
|
-
|
|
2217
|
+
const featureCount = countFeatures(data);
|
|
2218
|
+
const empty = featureCount === 0;
|
|
2219
|
+
this.cache.set(idx, { used: performance.now(), data, featureCount, empty });
|
|
2220
|
+
this.cachedFeatureCount += featureCount;
|
|
2221
|
+
(empty ? this.emptyKeys : this.nonEmptyKeys).add(idx);
|
|
2177
2222
|
const ifentry2 = this.inflight.get(idx);
|
|
2178
2223
|
if (ifentry2) {
|
|
2179
2224
|
for (const waiter of ifentry2) {
|
|
@@ -2182,19 +2227,7 @@ var CorrectionsSource = class {
|
|
|
2182
2227
|
}
|
|
2183
2228
|
this.inflight.delete(idx);
|
|
2184
2229
|
resolve(data);
|
|
2185
|
-
|
|
2186
|
-
let minUsed = Infinity;
|
|
2187
|
-
let minKey = void 0;
|
|
2188
|
-
this.cache.forEach((value, key) => {
|
|
2189
|
-
if (value.used < minUsed) {
|
|
2190
|
-
minUsed = value.used;
|
|
2191
|
-
minKey = key;
|
|
2192
|
-
}
|
|
2193
|
-
});
|
|
2194
|
-
if (minKey) {
|
|
2195
|
-
this.cache.delete(minKey);
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2230
|
+
this._evictIfNeeded();
|
|
2198
2231
|
}).catch((e) => {
|
|
2199
2232
|
const ifentry2 = this.inflight.get(idx);
|
|
2200
2233
|
if (ifentry2) {
|
|
@@ -2235,6 +2268,33 @@ var CorrectionsSource = class {
|
|
|
2235
2268
|
};
|
|
2236
2269
|
|
|
2237
2270
|
// src/index.js
|
|
2271
|
+
var TileFetchError = class _TileFetchError extends Error {
|
|
2272
|
+
/**
|
|
2273
|
+
* @param {number} status - HTTP status code
|
|
2274
|
+
* @param {string} [url] - The URL that failed
|
|
2275
|
+
* @param {string} [body] - Response body text
|
|
2276
|
+
*/
|
|
2277
|
+
constructor(status, url, body) {
|
|
2278
|
+
super(`Tile fetch failed: ${status}`);
|
|
2279
|
+
this.name = "TileFetchError";
|
|
2280
|
+
this.status = status;
|
|
2281
|
+
this.url = url;
|
|
2282
|
+
this.body = body;
|
|
2283
|
+
}
|
|
2284
|
+
/**
|
|
2285
|
+
* Create a TileFetchError from a failed Response.
|
|
2286
|
+
* @param {Response} response - The failed fetch response
|
|
2287
|
+
* @returns {Promise<TileFetchError>}
|
|
2288
|
+
*/
|
|
2289
|
+
static async fromResponse(response) {
|
|
2290
|
+
let body;
|
|
2291
|
+
try {
|
|
2292
|
+
body = await response.text();
|
|
2293
|
+
} catch {
|
|
2294
|
+
}
|
|
2295
|
+
return new _TileFetchError(response.status, response.url, body);
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2238
2298
|
var MIN_LINE_WIDTH = 0.5;
|
|
2239
2299
|
function getLineWidth(zoom, lineWidthStops) {
|
|
2240
2300
|
const zooms = Object.keys(lineWidthStops).map(Number).sort((a, b2) => a - b2);
|
|
@@ -2452,11 +2512,34 @@ function drawFeatures(ctx, features, color, lineWidth, tileSize, dashArray, alph
|
|
|
2452
2512
|
ctx.globalAlpha = prevAlpha;
|
|
2453
2513
|
}
|
|
2454
2514
|
}
|
|
2455
|
-
var
|
|
2515
|
+
var _TileFixer = class _TileFixer {
|
|
2516
|
+
/**
|
|
2517
|
+
* Set the default maximum features to cache for new TileFixer instances.
|
|
2518
|
+
* @param {number} maxFeatures - Maximum features to cache
|
|
2519
|
+
*/
|
|
2520
|
+
static setDefaultCacheMaxFeatures(maxFeatures) {
|
|
2521
|
+
_TileFixer._defaultCacheMaxFeatures = maxFeatures;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Get or create a TileFixer instance for a given PMTiles URL.
|
|
2525
|
+
* Reuses existing instances for the same URL.
|
|
2526
|
+
* @param {string} pmtilesUrl - URL to the PMTiles file
|
|
2527
|
+
* @returns {TileFixer}
|
|
2528
|
+
*/
|
|
2529
|
+
static getOrCreate(pmtilesUrl) {
|
|
2530
|
+
let instance = _TileFixer._instances.get(pmtilesUrl);
|
|
2531
|
+
if (!instance) {
|
|
2532
|
+
instance = new _TileFixer(pmtilesUrl, {
|
|
2533
|
+
cacheMaxFeatures: _TileFixer._defaultCacheMaxFeatures
|
|
2534
|
+
});
|
|
2535
|
+
_TileFixer._instances.set(pmtilesUrl, instance);
|
|
2536
|
+
}
|
|
2537
|
+
return instance;
|
|
2538
|
+
}
|
|
2456
2539
|
/**
|
|
2457
2540
|
* @param {string} pmtilesUrl - URL to the PMTiles file
|
|
2458
2541
|
* @param {Object} [options] - Options
|
|
2459
|
-
* @param {number} [options.
|
|
2542
|
+
* @param {number} [options.cacheMaxFeatures] - Maximum number of features to cache
|
|
2460
2543
|
* @param {number} [options.maxDataZoom] - Maximum zoom level in PMTiles (auto-detected if not provided)
|
|
2461
2544
|
*/
|
|
2462
2545
|
constructor(pmtilesUrl, options = {}) {
|
|
@@ -2493,10 +2576,9 @@ var BoundaryCorrector = class {
|
|
|
2493
2576
|
* @param {ArrayBuffer} rasterTile - The original raster tile as ArrayBuffer
|
|
2494
2577
|
* @param {Object} layerConfig - Layer configuration with colors and styles
|
|
2495
2578
|
* @param {number} zoom - Current zoom level
|
|
2496
|
-
* @param {number} [tileSize=256] - Size of the tile in pixels
|
|
2497
2579
|
* @returns {Promise<ArrayBuffer>} The corrected tile as ArrayBuffer (PNG)
|
|
2498
2580
|
*/
|
|
2499
|
-
async fixTile(corrections, rasterTile, layerConfig, zoom
|
|
2581
|
+
async fixTile(corrections, rasterTile, layerConfig, zoom) {
|
|
2500
2582
|
const {
|
|
2501
2583
|
startZoom = 0,
|
|
2502
2584
|
zoomThreshold,
|
|
@@ -2520,13 +2602,14 @@ var BoundaryCorrector = class {
|
|
|
2520
2602
|
const useOsm = zoom >= zoomThreshold;
|
|
2521
2603
|
const addLayerName = useOsm ? "to-add-osm" : "to-add-ne";
|
|
2522
2604
|
const delLayerName = useOsm ? "to-del-osm" : "to-del-ne";
|
|
2605
|
+
const blob = new Blob([rasterTile]);
|
|
2606
|
+
const imageBitmap = await createImageBitmap(blob);
|
|
2607
|
+
const tileSize = imageBitmap.width;
|
|
2523
2608
|
if (!this._canvas || this._canvas.width !== tileSize) {
|
|
2524
2609
|
this._canvas = new OffscreenCanvas(tileSize, tileSize);
|
|
2525
2610
|
}
|
|
2526
2611
|
const canvas = this._canvas;
|
|
2527
2612
|
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
2528
|
-
const blob = new Blob([rasterTile]);
|
|
2529
|
-
const imageBitmap = await createImageBitmap(blob);
|
|
2530
2613
|
ctx.drawImage(imageBitmap, 0, 0, tileSize, tileSize);
|
|
2531
2614
|
const baseLineWidth = getLineWidth(zoom, lineWidthStops);
|
|
2532
2615
|
const maxWidthFraction = activeLineStyles.length > 0 ? Math.max(...activeLineStyles.map((s) => s.widthFraction ?? 1)) : 1;
|
|
@@ -2563,24 +2646,24 @@ var BoundaryCorrector = class {
|
|
|
2563
2646
|
* @param {number} y - Tile Y coordinate
|
|
2564
2647
|
* @param {Object} layerConfig - Layer configuration with colors and styles
|
|
2565
2648
|
* @param {Object} [options] - Fetch options
|
|
2566
|
-
* @param {number} [options.tileSize=256] - Tile size in pixels
|
|
2567
2649
|
* @param {AbortSignal} [options.signal] - Abort signal for fetch
|
|
2568
2650
|
* @param {RequestMode} [options.mode] - Fetch mode (e.g., 'cors')
|
|
2651
|
+
* @param {boolean} [options.fallbackOnCorrectionFailure=true] - Return original tile if corrections fail
|
|
2569
2652
|
* @returns {Promise<{data: ArrayBuffer, wasFixed: boolean}>}
|
|
2570
2653
|
*/
|
|
2571
2654
|
async fetchAndFixTile(tileUrl, z2, x2, y, layerConfig, options = {}) {
|
|
2572
|
-
const {
|
|
2655
|
+
const { signal, mode, fallbackOnCorrectionFailure = true } = options;
|
|
2573
2656
|
const fetchOptions = {};
|
|
2574
2657
|
if (signal) fetchOptions.signal = signal;
|
|
2575
2658
|
if (mode) fetchOptions.mode = mode;
|
|
2576
2659
|
if (!layerConfig) {
|
|
2577
2660
|
const response = await fetch(tileUrl, fetchOptions);
|
|
2578
|
-
if (!response.ok) throw
|
|
2661
|
+
if (!response.ok) throw await TileFetchError.fromResponse(response);
|
|
2579
2662
|
return { data: await response.arrayBuffer(), wasFixed: false };
|
|
2580
2663
|
}
|
|
2581
2664
|
const [tileResult, correctionsResult] = await Promise.allSettled([
|
|
2582
|
-
fetch(tileUrl, fetchOptions).then((r) => {
|
|
2583
|
-
if (!r.ok) throw
|
|
2665
|
+
fetch(tileUrl, fetchOptions).then(async (r) => {
|
|
2666
|
+
if (!r.ok) throw await TileFetchError.fromResponse(r);
|
|
2584
2667
|
return r.arrayBuffer();
|
|
2585
2668
|
}),
|
|
2586
2669
|
this.getCorrections(z2, x2, y)
|
|
@@ -2594,13 +2677,21 @@ var BoundaryCorrector = class {
|
|
|
2594
2677
|
const tileData = tileResult.value;
|
|
2595
2678
|
const correctionsFailed = correctionsResult.status === "rejected";
|
|
2596
2679
|
const correctionsError = correctionsFailed ? correctionsResult.reason : null;
|
|
2680
|
+
if (correctionsFailed && !fallbackOnCorrectionFailure) {
|
|
2681
|
+
throw correctionsError;
|
|
2682
|
+
}
|
|
2597
2683
|
const corrections = correctionsResult.status === "fulfilled" ? correctionsResult.value : {};
|
|
2598
2684
|
const hasCorrections = Object.values(corrections).some((arr) => arr && arr.length > 0);
|
|
2599
2685
|
if (!hasCorrections) {
|
|
2600
2686
|
return { data: tileData, wasFixed: false, correctionsFailed, correctionsError };
|
|
2601
2687
|
}
|
|
2602
|
-
const fixedData = await this.fixTile(corrections, tileData, layerConfig, z2
|
|
2688
|
+
const fixedData = await this.fixTile(corrections, tileData, layerConfig, z2);
|
|
2603
2689
|
return { data: fixedData, wasFixed: true, correctionsFailed: false, correctionsError: null };
|
|
2604
2690
|
}
|
|
2605
2691
|
};
|
|
2692
|
+
/** @type {Map<string, TileFixer>} */
|
|
2693
|
+
__publicField(_TileFixer, "_instances", /* @__PURE__ */ new Map());
|
|
2694
|
+
/** @type {number} */
|
|
2695
|
+
__publicField(_TileFixer, "_defaultCacheMaxFeatures", 25e3);
|
|
2696
|
+
var TileFixer = _TileFixer;
|
|
2606
2697
|
//# sourceMappingURL=index.cjs.map
|