@rcpch/imd-map 0.3.0 → 0.3.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/CHANGELOG.md +14 -0
- package/README.md +20 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +31 -20
- package/dist/index.esm.js.map +1 -1
- package/dist/umd/rcpch-imd-map.min.js +14 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,20 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.3.1] — 2026-04-28
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Optional tile API key support via `tilesApiKey` and `tilesApiKeyParam` options in `CreateImdMapOptions`.
|
|
16
|
+
- Tile API key query parameters are appended to all choropleth and boundary overlay tile requests.
|
|
17
|
+
- Customizable query parameter name for tile auth (defaults to `api_key`).
|
|
18
|
+
- Unit test coverage for tile URL builder with optional auth and auth propagation across overlays.
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
- Updated README runtime tile configuration section with tile auth options and example usage.
|
|
23
|
+
- Added note that browser-delivered keys are non-secret and primarily useful for operational traffic control (rate limiting, revocation, origin restrictions).
|
|
24
|
+
|
|
11
25
|
## [0.3.0] — 2026-04-27
|
|
12
26
|
|
|
13
27
|
### Changed
|
package/README.md
CHANGED
|
@@ -201,6 +201,24 @@ Tile URL resolution precedence:
|
|
|
201
201
|
|
|
202
202
|
The library source contains **no hardcoded tile URLs**.
|
|
203
203
|
|
|
204
|
+
Optional tile auth query options:
|
|
205
|
+
|
|
206
|
+
- `tilesApiKey`: appends a query value to all choropleth and overlay tile requests
|
|
207
|
+
- `tilesApiKeyParam`: query parameter name for `tilesApiKey` (default: `api_key`)
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
createImdMap({
|
|
213
|
+
container: 'map',
|
|
214
|
+
tilesBaseUrl: 'https://your-tile-server.example.com',
|
|
215
|
+
tilesApiKey: 'switchable-browser-token',
|
|
216
|
+
tilesApiKeyParam: 'key',
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This is useful for revoking abusive traffic quickly, but browser-delivered keys must still be treated as non-secret.
|
|
221
|
+
|
|
204
222
|
### Overlay boundary tile contract
|
|
205
223
|
|
|
206
224
|
Boundary overlays (local authority, NHSER, ICB, LHB) are requested from schema-qualified table ids and rendered with the same schema-qualified `source-layer` name. Example:
|
|
@@ -407,6 +425,8 @@ map.setStyle({ tooltip: { areaLabel: 'Local area' } });
|
|
|
407
425
|
|---|---|---|---|
|
|
408
426
|
| `container` | `string \| HTMLElement` | — | DOM element ID or element reference |
|
|
409
427
|
| `tilesBaseUrl` | `string` | — | Base URL of the tile server |
|
|
428
|
+
| `tilesApiKey` | `string` | — | Optional API key appended to tile URLs as a query parameter |
|
|
429
|
+
| `tilesApiKeyParam` | `string` | `'api_key'` | Query parameter name used for `tilesApiKey` |
|
|
410
430
|
| `initialNation` | `Nation` | `'all'` | Starting nation filter |
|
|
411
431
|
| `initialEra` | `Era` | `'2021'` | Requested era (may be overridden) |
|
|
412
432
|
| `enableLocalAuthorityOverlay` | `boolean` | `false` | Show local authority boundary overlay at startup |
|
package/dist/index.d.ts
CHANGED
|
@@ -183,6 +183,10 @@ interface CreateImdMapOptions {
|
|
|
183
183
|
container: string | HTMLElement;
|
|
184
184
|
/** Base URL of the tile server. Required for choropleth rendering. */
|
|
185
185
|
tilesBaseUrl?: string;
|
|
186
|
+
/** Optional API key appended to tile URLs as a query string parameter. */
|
|
187
|
+
tilesApiKey?: string;
|
|
188
|
+
/** Query parameter name used for tilesApiKey. Default: api_key. */
|
|
189
|
+
tilesApiKeyParam?: string;
|
|
186
190
|
initialNation?: Nation;
|
|
187
191
|
initialEra?: Era;
|
|
188
192
|
showDefaultControls?: boolean;
|
package/dist/index.esm.js
CHANGED
|
@@ -29,9 +29,13 @@ var ZOOM_TIERS = [
|
|
|
29
29
|
function resolveFullTableName(effectiveEra, tier) {
|
|
30
30
|
return `public.uk_master_${effectiveEra}_${tier}`;
|
|
31
31
|
}
|
|
32
|
-
function buildTileUrl(tilesBaseUrl, fullTableName) {
|
|
32
|
+
function buildTileUrl(tilesBaseUrl, fullTableName, auth) {
|
|
33
33
|
const base = tilesBaseUrl.replace(/\/$/, "");
|
|
34
|
-
|
|
34
|
+
const tileUrl = `${base}/${fullTableName}/{z}/{x}/{y}.pbf`;
|
|
35
|
+
if (!auth?.apiKey) return tileUrl;
|
|
36
|
+
const paramName = auth.apiKeyParam?.trim() || "api_key";
|
|
37
|
+
const separator = tileUrl.includes("?") ? "&" : "?";
|
|
38
|
+
return `${tileUrl}${separator}${encodeURIComponent(paramName)}=${encodeURIComponent(auth.apiKey)}`;
|
|
35
39
|
}
|
|
36
40
|
function resolveNationFilter(nation) {
|
|
37
41
|
if (nation === "all") return null;
|
|
@@ -60,11 +64,11 @@ function choroplethSourceId(tier) {
|
|
|
60
64
|
ZOOM_TIERS.map((t) => choroplethSourceId(t.tier));
|
|
61
65
|
var PATIENTS_SOURCE_ID = "rcpch-imd-patients";
|
|
62
66
|
var LEAD_CENTRE_SOURCE_ID = "rcpch-imd-lead-centre";
|
|
63
|
-
function addOrUpdateChoroplethSources(map, tilesBaseUrl, effectiveEra) {
|
|
67
|
+
function addOrUpdateChoroplethSources(map, tilesBaseUrl, effectiveEra, tileAuth) {
|
|
64
68
|
for (const { tier } of ZOOM_TIERS) {
|
|
65
69
|
const sourceId = choroplethSourceId(tier);
|
|
66
70
|
const fullTableName = resolveFullTableName(effectiveEra, tier);
|
|
67
|
-
const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName);
|
|
71
|
+
const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName, tileAuth);
|
|
68
72
|
const existing = map.getSource(sourceId);
|
|
69
73
|
if (existing instanceof VectorTileSource) {
|
|
70
74
|
existing.setTiles([tileUrl]);
|
|
@@ -899,13 +903,13 @@ function localAuthoritySourceId(tier) {
|
|
|
899
903
|
function localAuthorityLayerId(tier) {
|
|
900
904
|
return `${LOCAL_AUTHORITY_LAYER_ID}-${tier}`;
|
|
901
905
|
}
|
|
902
|
-
function addOrUpdateLocalAuthorityOverlay(map, tilesBaseUrl, style) {
|
|
906
|
+
function addOrUpdateLocalAuthorityOverlay(map, tilesBaseUrl, style, tileAuth) {
|
|
903
907
|
for (const { tier, minzoom, maxzoom } of ZOOM_TIERS) {
|
|
904
908
|
const sourceId = localAuthoritySourceId(tier);
|
|
905
909
|
const layerId = localAuthorityLayerId(tier);
|
|
906
910
|
const fullTableName = buildMvtLayerName(LOCAL_AUTHORITY_TABLE_PREFIX, tier);
|
|
907
911
|
const sourceLayer = fullTableName;
|
|
908
|
-
const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName);
|
|
912
|
+
const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName, tileAuth);
|
|
909
913
|
const existing = map.getSource(sourceId);
|
|
910
914
|
if (existing instanceof VectorTileSource) {
|
|
911
915
|
existing.setTiles([tileUrl]);
|
|
@@ -972,13 +976,13 @@ function overlaySourceId(baseSourceId, tier) {
|
|
|
972
976
|
function overlayLayerId(baseLayerId, tier) {
|
|
973
977
|
return `${baseLayerId}-${tier}`;
|
|
974
978
|
}
|
|
975
|
-
function addOrUpdateBoundaryOverlay(map, tilesBaseUrl, input) {
|
|
979
|
+
function addOrUpdateBoundaryOverlay(map, tilesBaseUrl, tileAuth, input) {
|
|
976
980
|
for (const { tier, minzoom, maxzoom } of ZOOM_TIERS) {
|
|
977
981
|
const sourceId = overlaySourceId(input.sourceId, tier);
|
|
978
982
|
const layerId = overlayLayerId(input.layerId, tier);
|
|
979
983
|
const fullTableName = buildMvtLayerName(input.tablePrefix, tier);
|
|
980
984
|
const sourceLayer = fullTableName;
|
|
981
|
-
const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName);
|
|
985
|
+
const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName, tileAuth);
|
|
982
986
|
const existing = map.getSource(sourceId);
|
|
983
987
|
if (existing instanceof VectorTileSource) {
|
|
984
988
|
existing.setTiles([tileUrl]);
|
|
@@ -1014,8 +1018,8 @@ function addOrUpdateBoundaryOverlay(map, tilesBaseUrl, input) {
|
|
|
1014
1018
|
});
|
|
1015
1019
|
}
|
|
1016
1020
|
}
|
|
1017
|
-
function addOrUpdateNhserOverlay(map, tilesBaseUrl, style) {
|
|
1018
|
-
addOrUpdateBoundaryOverlay(map, tilesBaseUrl, {
|
|
1021
|
+
function addOrUpdateNhserOverlay(map, tilesBaseUrl, style, tileAuth) {
|
|
1022
|
+
addOrUpdateBoundaryOverlay(map, tilesBaseUrl, tileAuth, {
|
|
1019
1023
|
sourceId: NHSER_SOURCE_ID,
|
|
1020
1024
|
layerId: NHSER_LAYER_ID,
|
|
1021
1025
|
tablePrefix: NHSER_TABLE_PREFIX,
|
|
@@ -1023,8 +1027,8 @@ function addOrUpdateNhserOverlay(map, tilesBaseUrl, style) {
|
|
|
1023
1027
|
lineWidth: style.boundaries.nhserWidth ?? 1.5
|
|
1024
1028
|
});
|
|
1025
1029
|
}
|
|
1026
|
-
function addOrUpdateIcbOverlay(map, tilesBaseUrl, style) {
|
|
1027
|
-
addOrUpdateBoundaryOverlay(map, tilesBaseUrl, {
|
|
1030
|
+
function addOrUpdateIcbOverlay(map, tilesBaseUrl, style, tileAuth) {
|
|
1031
|
+
addOrUpdateBoundaryOverlay(map, tilesBaseUrl, tileAuth, {
|
|
1028
1032
|
sourceId: ICB_SOURCE_ID,
|
|
1029
1033
|
layerId: ICB_LAYER_ID,
|
|
1030
1034
|
tablePrefix: ICB_TABLE_PREFIX,
|
|
@@ -1032,8 +1036,8 @@ function addOrUpdateIcbOverlay(map, tilesBaseUrl, style) {
|
|
|
1032
1036
|
lineWidth: style.boundaries.icbWidth ?? 1
|
|
1033
1037
|
});
|
|
1034
1038
|
}
|
|
1035
|
-
function addOrUpdateLhbOverlay(map, tilesBaseUrl, style) {
|
|
1036
|
-
addOrUpdateBoundaryOverlay(map, tilesBaseUrl, {
|
|
1039
|
+
function addOrUpdateLhbOverlay(map, tilesBaseUrl, style, tileAuth) {
|
|
1040
|
+
addOrUpdateBoundaryOverlay(map, tilesBaseUrl, tileAuth, {
|
|
1037
1041
|
sourceId: LHB_SOURCE_ID,
|
|
1038
1042
|
layerId: LHB_LAYER_ID,
|
|
1039
1043
|
tablePrefix: LHB_TABLE_PREFIX,
|
|
@@ -1377,6 +1381,10 @@ function createImdMap(options) {
|
|
|
1377
1381
|
if (!tilesBaseUrl) {
|
|
1378
1382
|
logger.warn("No tilesBaseUrl provided. Choropleth tiles will not load.");
|
|
1379
1383
|
}
|
|
1384
|
+
const tileAuth = {
|
|
1385
|
+
apiKey: options.tilesApiKey,
|
|
1386
|
+
apiKeyParam: options.tilesApiKeyParam
|
|
1387
|
+
};
|
|
1380
1388
|
let resolvedStyle = mergeStyle(DEFAULT_STYLE, options.style);
|
|
1381
1389
|
let state = createInitialState(options.initialNation ?? "all", options.initialEra ?? "2021");
|
|
1382
1390
|
if (options.enableLocalAuthorityOverlay) {
|
|
@@ -1473,22 +1481,22 @@ function createImdMap(options) {
|
|
|
1473
1481
|
const canShowIcb = nation === "all" || nation === "england";
|
|
1474
1482
|
const canShowLhb = nation === "all" || nation === "wales";
|
|
1475
1483
|
if (state.overlays.localAuthority) {
|
|
1476
|
-
addOrUpdateLocalAuthorityOverlay(map, tilesBaseUrl, resolvedStyle);
|
|
1484
|
+
addOrUpdateLocalAuthorityOverlay(map, tilesBaseUrl, resolvedStyle, tileAuth);
|
|
1477
1485
|
} else {
|
|
1478
1486
|
hideLocalAuthorityOverlay(map);
|
|
1479
1487
|
}
|
|
1480
1488
|
if (state.overlays.nhser && canShowNhser) {
|
|
1481
|
-
addOrUpdateNhserOverlay(map, tilesBaseUrl, resolvedStyle);
|
|
1489
|
+
addOrUpdateNhserOverlay(map, tilesBaseUrl, resolvedStyle, tileAuth);
|
|
1482
1490
|
} else {
|
|
1483
1491
|
hideNhserOverlay(map);
|
|
1484
1492
|
}
|
|
1485
1493
|
if (state.overlays.icb && canShowIcb) {
|
|
1486
|
-
addOrUpdateIcbOverlay(map, tilesBaseUrl, resolvedStyle);
|
|
1494
|
+
addOrUpdateIcbOverlay(map, tilesBaseUrl, resolvedStyle, tileAuth);
|
|
1487
1495
|
} else {
|
|
1488
1496
|
hideIcbOverlay(map);
|
|
1489
1497
|
}
|
|
1490
1498
|
if (state.overlays.lhb && canShowLhb) {
|
|
1491
|
-
addOrUpdateLhbOverlay(map, tilesBaseUrl, resolvedStyle);
|
|
1499
|
+
addOrUpdateLhbOverlay(map, tilesBaseUrl, resolvedStyle, tileAuth);
|
|
1492
1500
|
} else {
|
|
1493
1501
|
hideLhbOverlay(map);
|
|
1494
1502
|
}
|
|
@@ -1522,10 +1530,13 @@ function createImdMap(options) {
|
|
|
1522
1530
|
});
|
|
1523
1531
|
}
|
|
1524
1532
|
logger.debug(`tilesBaseUrl resolved to: "${tilesBaseUrl}"`);
|
|
1533
|
+
if (tileAuth.apiKey) {
|
|
1534
|
+
logger.debug("Tile API key is configured for tile URL requests.");
|
|
1535
|
+
}
|
|
1525
1536
|
map.on("load", () => {
|
|
1526
1537
|
mapLoaded = true;
|
|
1527
1538
|
if (tilesBaseUrl) {
|
|
1528
|
-
addOrUpdateChoroplethSources(map, tilesBaseUrl, state.effectiveEra);
|
|
1539
|
+
addOrUpdateChoroplethSources(map, tilesBaseUrl, state.effectiveEra, tileAuth);
|
|
1529
1540
|
addChoroplethLayers(map, state.nation, state.effectiveEra, resolvedStyle);
|
|
1530
1541
|
}
|
|
1531
1542
|
applyOverlayVisibility();
|
|
@@ -1576,7 +1587,7 @@ function createImdMap(options) {
|
|
|
1576
1587
|
if (mapLoaded && tilesBaseUrl) {
|
|
1577
1588
|
if (eraChanged) {
|
|
1578
1589
|
removeChoroplethLayers(map);
|
|
1579
|
-
addOrUpdateChoroplethSources(map, tilesBaseUrl, newEffectiveEra);
|
|
1590
|
+
addOrUpdateChoroplethSources(map, tilesBaseUrl, newEffectiveEra, tileAuth);
|
|
1580
1591
|
addChoroplethLayers(map, newNation, newEffectiveEra, resolvedStyle);
|
|
1581
1592
|
} else if (nationChanged) {
|
|
1582
1593
|
updateChoroplethNationFilter(map, newNation);
|