@rcpch/imd-map 0.2.1 → 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 +22 -0
- package/README.md +54 -2
- package/dist/index.d.ts +4 -0
- package/dist/index.esm.js +32 -21
- 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,28 @@ 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
|
+
|
|
25
|
+
## [0.3.0] — 2026-04-27
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Updated all-UK era resolution so `initialNation: 'all'` honours the requested era instead of always forcing `2011`.
|
|
30
|
+
- Documented the mixed-vintage `uk_master_2021_*` UK tiles, where England uses 2021 LSOA boundaries with 2025 IMD data while Wales, Scotland, and Northern Ireland remain on their current older datasets.
|
|
31
|
+
- Clarified in the public docs that `era` refers to the LSOA boundary year, not the IMD publication year, and documented the England pairings of `2011` → 2011 LSOAs + 2019 IMD and `2021` → 2021 LSOAs + 2025 IMD.
|
|
32
|
+
|
|
11
33
|
## [0.2.0] — 2026-04-26
|
|
12
34
|
|
|
13
35
|
### Changed
|
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ The UMD bundle includes MapLibre GL. No separate script tag required.
|
|
|
69
69
|
```html
|
|
70
70
|
<div id="map" style="height: 600px"></div>
|
|
71
71
|
|
|
72
|
-
<script src="https://cdn.jsdelivr.net/npm/@rcpch/imd-map@0.
|
|
72
|
+
<script src="https://cdn.jsdelivr.net/npm/@rcpch/imd-map@0.3.0/dist/umd/rcpch-imd-map.min.js"></script>
|
|
73
73
|
<script>
|
|
74
74
|
const map = RcpchImdMap.createImdMap({
|
|
75
75
|
container: 'map',
|
|
@@ -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:
|
|
@@ -216,9 +234,16 @@ Bring your own overlay configuration (custom overlay table/layer names via libra
|
|
|
216
234
|
|
|
217
235
|
## Nation and era rules
|
|
218
236
|
|
|
237
|
+
The `era` option refers to the boundary year used for the LSOA geography, not the IMD publication year.
|
|
238
|
+
|
|
239
|
+
For England, the supported pairings are:
|
|
240
|
+
|
|
241
|
+
- `2011` era = 2011 LSOA boundaries + 2019 IMD data
|
|
242
|
+
- `2021` era = 2021 LSOA boundaries + 2025 IMD data
|
|
243
|
+
|
|
219
244
|
| Nation | Requested era | Effective era |
|
|
220
245
|
|---|---|---|
|
|
221
|
-
| `all` |
|
|
246
|
+
| `all` | `2011` or `2021` | as requested |
|
|
222
247
|
| `england` | `2011` or `2021` | as requested |
|
|
223
248
|
| `wales` | any | always `2011` |
|
|
224
249
|
| `scotland` | any | always `2011` |
|
|
@@ -226,6 +251,31 @@ Bring your own overlay configuration (custom overlay table/layer names via libra
|
|
|
226
251
|
|
|
227
252
|
When the effective era differs from the requested era, `onWarning` is called with code `ERA_OVERRIDE`.
|
|
228
253
|
|
|
254
|
+
For all-UK maps, `initialEra: '2021'` now uses the mixed-vintage `uk_master_2021_*` tables: England renders with 2021 LSOA boundaries and 2025 IMD data, while Wales, Scotland, and Northern Ireland continue to render from their existing older datasets within the same UK tile family. Use `initialEra: '2011'` when you want the older England 2011 LSOA + 2019 IMD view alongside the existing Welsh and other nation data.
|
|
255
|
+
|
|
256
|
+
This means you can instantiate two separate UK maps in the same application, choosing the England boundary/IMD pairing by era:
|
|
257
|
+
|
|
258
|
+
```js
|
|
259
|
+
const historicalMap = createImdMap({
|
|
260
|
+
container: 'map-2011',
|
|
261
|
+
tilesBaseUrl: 'https://your-tile-server.example.com',
|
|
262
|
+
initialNation: 'all',
|
|
263
|
+
initialEra: '2011',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const currentMap = createImdMap({
|
|
267
|
+
container: 'map-2021',
|
|
268
|
+
tilesBaseUrl: 'https://your-tile-server.example.com',
|
|
269
|
+
initialNation: 'all',
|
|
270
|
+
initialEra: '2021',
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
In a patient-facing application, a common pattern would be:
|
|
275
|
+
|
|
276
|
+
- patients before 2025 or 2026 cutoff: `initialEra: '2011'`
|
|
277
|
+
- patients in the newer cohort: `initialEra: '2021'`
|
|
278
|
+
|
|
229
279
|
---
|
|
230
280
|
|
|
231
281
|
## Styling
|
|
@@ -375,6 +425,8 @@ map.setStyle({ tooltip: { areaLabel: 'Local area' } });
|
|
|
375
425
|
|---|---|---|---|
|
|
376
426
|
| `container` | `string \| HTMLElement` | — | DOM element ID or element reference |
|
|
377
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` |
|
|
378
430
|
| `initialNation` | `Nation` | `'all'` | Starting nation filter |
|
|
379
431
|
| `initialEra` | `Era` | `'2021'` | Requested era (may be overridden) |
|
|
380
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
|
@@ -6,7 +6,7 @@ import { Map, AttributionControl, Popup, VectorTileSource, GeoJSONSource } from
|
|
|
6
6
|
function resolveEffectiveEra(nation, requestedEra) {
|
|
7
7
|
switch (nation) {
|
|
8
8
|
case "all":
|
|
9
|
-
return
|
|
9
|
+
return requestedEra;
|
|
10
10
|
case "england":
|
|
11
11
|
return requestedEra;
|
|
12
12
|
case "wales":
|
|
@@ -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);
|