@rcpch/imd-map 0.1.2 → 0.2.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 CHANGED
@@ -8,6 +8,19 @@ This project adheres to [Semantic Versioning](https://semver.org/).
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.2.0] — 2026-04-26
12
+
13
+ ### Changed
14
+
15
+ - Switched local authority boundary overlay to zoom-tiered pg_tileserv tables (`public.la_tiles_z0_4`, `public.la_tiles_z5_7`, `public.la_tiles_z8_10`, `public.la_tiles_z11_14`) with tier-matched layer zoom windows.
16
+ - Switched health boundary overlays to zoom-tiered pg_tileserv tables for NHS England regions (`public.nhser_tiles_2021_*`), ICBs (`public.icb_tiles_2023_*`), and Welsh LHBs (`public.lhb_tiles_2022_*`).
17
+ - Corrected overlay `source-layer` usage to match deployed overlay vector tiles by using schema-qualified layer names (for example `public.la_tiles_z5_7`) and keeping `source-layer` equal to the URL table id.
18
+ - Updated overlay visibility handling so hide/show applies across all zoom-tier boundary layers.
19
+
20
+ ### Added
21
+
22
+ - Unit tests covering tiered overlay source/layer creation, source-layer naming, and overlay hide behavior across tiers.
23
+
11
24
  ## [0.1.0] — 2026-04-20
12
25
 
13
26
  ### Added
package/README.md CHANGED
@@ -201,6 +201,17 @@ Tile URL resolution precedence:
201
201
 
202
202
  The library source contains **no hardcoded tile URLs**.
203
203
 
204
+ ### Overlay boundary tile contract
205
+
206
+ 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:
207
+
208
+ - URL table id: `public.la_tiles_z5_7`
209
+ - `source-layer`: `public.la_tiles_z5_7`
210
+
211
+ If you self-host boundary tiles, ensure each overlay PBF exposes the exact same layer name string as the table id used in the URL path.
212
+
213
+ Bring your own overlay configuration (custom overlay table/layer names via library options) could be enabled in a future release. If you need this, please open a GitHub issue so contributors can prioritise and scope it.
214
+
204
215
  ---
205
216
 
206
217
  ## Nation and era rules
package/dist/index.esm.js CHANGED
@@ -882,55 +882,79 @@ function normalizeLeadCentreInput(data) {
882
882
  properties: { label }
883
883
  };
884
884
  }
885
+
886
+ // src/overlays/mvtLayerName.ts
887
+ var DEFAULT_TILES_SCHEMA = "public";
888
+ function buildMvtLayerName(baseName, tier, schema = DEFAULT_TILES_SCHEMA) {
889
+ return `${schema}.${baseName}_${tier}`;
890
+ }
891
+
892
+ // src/overlays/localAuthority.ts
885
893
  var LOCAL_AUTHORITY_SOURCE_ID = "rcpch-imd-la-overlay";
886
894
  var LOCAL_AUTHORITY_LAYER_ID = "rcpch-imd-la-overlay-line";
887
- var LOCAL_AUTHORITY_FULL_TABLE_NAME = "public.la_tiles";
888
- var LOCAL_AUTHORITY_SOURCE_LAYER = "public.la_tiles";
895
+ var LOCAL_AUTHORITY_TABLE_PREFIX = "la_tiles";
896
+ function localAuthoritySourceId(tier) {
897
+ return `${LOCAL_AUTHORITY_SOURCE_ID}-${tier}`;
898
+ }
899
+ function localAuthorityLayerId(tier) {
900
+ return `${LOCAL_AUTHORITY_LAYER_ID}-${tier}`;
901
+ }
889
902
  function addOrUpdateLocalAuthorityOverlay(map, tilesBaseUrl, style) {
890
- const tileUrl = buildTileUrl(tilesBaseUrl, LOCAL_AUTHORITY_FULL_TABLE_NAME);
891
- const existing = map.getSource(LOCAL_AUTHORITY_SOURCE_ID);
892
- if (existing instanceof VectorTileSource) {
893
- existing.setTiles([tileUrl]);
894
- } else {
895
- if (existing) map.removeSource(LOCAL_AUTHORITY_SOURCE_ID);
896
- map.addSource(LOCAL_AUTHORITY_SOURCE_ID, {
897
- type: "vector",
898
- tiles: [tileUrl],
899
- minzoom: 0,
900
- maxzoom: 14
903
+ for (const { tier, minzoom, maxzoom } of ZOOM_TIERS) {
904
+ const sourceId = localAuthoritySourceId(tier);
905
+ const layerId = localAuthorityLayerId(tier);
906
+ const fullTableName = buildMvtLayerName(LOCAL_AUTHORITY_TABLE_PREFIX, tier);
907
+ const sourceLayer = fullTableName;
908
+ const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName);
909
+ const existing = map.getSource(sourceId);
910
+ if (existing instanceof VectorTileSource) {
911
+ existing.setTiles([tileUrl]);
912
+ } else {
913
+ if (existing) map.removeSource(sourceId);
914
+ map.addSource(sourceId, {
915
+ type: "vector",
916
+ tiles: [tileUrl],
917
+ minzoom: 0,
918
+ maxzoom: 14
919
+ });
920
+ }
921
+ if (map.getLayer(layerId)) {
922
+ map.setPaintProperty(
923
+ layerId,
924
+ "line-color",
925
+ style.boundaries.localAuthorityColor ?? "#0d0d58"
926
+ );
927
+ map.setPaintProperty(
928
+ layerId,
929
+ "line-width",
930
+ style.boundaries.localAuthorityWidth ?? 1
931
+ );
932
+ map.setLayoutProperty(layerId, "visibility", "visible");
933
+ continue;
934
+ }
935
+ map.addLayer({
936
+ id: layerId,
937
+ type: "line",
938
+ source: sourceId,
939
+ "source-layer": sourceLayer,
940
+ minzoom,
941
+ maxzoom,
942
+ paint: {
943
+ "line-color": style.boundaries.localAuthorityColor ?? "#0d0d58",
944
+ "line-width": style.boundaries.localAuthorityWidth ?? 1
945
+ },
946
+ layout: {
947
+ visibility: "visible"
948
+ }
901
949
  });
902
950
  }
903
- if (map.getLayer(LOCAL_AUTHORITY_LAYER_ID)) {
904
- map.setPaintProperty(
905
- LOCAL_AUTHORITY_LAYER_ID,
906
- "line-color",
907
- style.boundaries.localAuthorityColor ?? "#0d0d58"
908
- );
909
- map.setPaintProperty(
910
- LOCAL_AUTHORITY_LAYER_ID,
911
- "line-width",
912
- style.boundaries.localAuthorityWidth ?? 1
913
- );
914
- map.setLayoutProperty(LOCAL_AUTHORITY_LAYER_ID, "visibility", "visible");
915
- return;
916
- }
917
- map.addLayer({
918
- id: LOCAL_AUTHORITY_LAYER_ID,
919
- type: "line",
920
- source: LOCAL_AUTHORITY_SOURCE_ID,
921
- "source-layer": LOCAL_AUTHORITY_SOURCE_LAYER,
922
- paint: {
923
- "line-color": style.boundaries.localAuthorityColor ?? "#0d0d58",
924
- "line-width": style.boundaries.localAuthorityWidth ?? 1
925
- },
926
- layout: {
927
- visibility: "visible"
928
- }
929
- });
930
951
  }
931
952
  function hideLocalAuthorityOverlay(map) {
932
- if (map.getLayer(LOCAL_AUTHORITY_LAYER_ID)) {
933
- map.setLayoutProperty(LOCAL_AUTHORITY_LAYER_ID, "visibility", "none");
953
+ for (const { tier } of ZOOM_TIERS) {
954
+ const layerId = localAuthorityLayerId(tier);
955
+ if (map.getLayer(layerId)) {
956
+ map.setLayoutProperty(layerId, "visibility", "none");
957
+ }
934
958
  }
935
959
  }
936
960
  var NHSER_SOURCE_ID = "rcpch-imd-nhser-overlay";
@@ -939,52 +963,62 @@ var ICB_SOURCE_ID = "rcpch-imd-icb-overlay";
939
963
  var ICB_LAYER_ID = "rcpch-imd-icb-overlay-line";
940
964
  var LHB_SOURCE_ID = "rcpch-imd-lhb-overlay";
941
965
  var LHB_LAYER_ID = "rcpch-imd-lhb-overlay-line";
942
- var NHSER_FULL_TABLE_NAME = "public.nhser_tiles_2021";
943
- var NHSER_SOURCE_LAYER = "public.nhser_tiles_2021";
944
- var ICB_FULL_TABLE_NAME = "public.icb_tiles_2023";
945
- var ICB_SOURCE_LAYER = "public.icb_tiles_2023";
946
- var LHB_FULL_TABLE_NAME = "public.lhb_tiles_2022";
947
- var LHB_SOURCE_LAYER = "public.lhb_tiles_2022";
966
+ var NHSER_TABLE_PREFIX = "nhser_tiles_2021";
967
+ var ICB_TABLE_PREFIX = "icb_tiles_2023";
968
+ var LHB_TABLE_PREFIX = "lhb_tiles_2022";
969
+ function overlaySourceId(baseSourceId, tier) {
970
+ return `${baseSourceId}-${tier}`;
971
+ }
972
+ function overlayLayerId(baseLayerId, tier) {
973
+ return `${baseLayerId}-${tier}`;
974
+ }
948
975
  function addOrUpdateBoundaryOverlay(map, tilesBaseUrl, input) {
949
- const tileUrl = buildTileUrl(tilesBaseUrl, input.fullTableName);
950
- const existing = map.getSource(input.sourceId);
951
- if (existing instanceof VectorTileSource) {
952
- existing.setTiles([tileUrl]);
953
- } else {
954
- if (existing) map.removeSource(input.sourceId);
955
- map.addSource(input.sourceId, {
956
- type: "vector",
957
- tiles: [tileUrl],
958
- minzoom: 0,
959
- maxzoom: 14
976
+ for (const { tier, minzoom, maxzoom } of ZOOM_TIERS) {
977
+ const sourceId = overlaySourceId(input.sourceId, tier);
978
+ const layerId = overlayLayerId(input.layerId, tier);
979
+ const fullTableName = buildMvtLayerName(input.tablePrefix, tier);
980
+ const sourceLayer = fullTableName;
981
+ const tileUrl = buildTileUrl(tilesBaseUrl, fullTableName);
982
+ const existing = map.getSource(sourceId);
983
+ if (existing instanceof VectorTileSource) {
984
+ existing.setTiles([tileUrl]);
985
+ } else {
986
+ if (existing) map.removeSource(sourceId);
987
+ map.addSource(sourceId, {
988
+ type: "vector",
989
+ tiles: [tileUrl],
990
+ minzoom: 0,
991
+ maxzoom: 14
992
+ });
993
+ }
994
+ if (map.getLayer(layerId)) {
995
+ map.setPaintProperty(layerId, "line-color", input.lineColor);
996
+ map.setPaintProperty(layerId, "line-width", input.lineWidth);
997
+ map.setLayoutProperty(layerId, "visibility", "visible");
998
+ continue;
999
+ }
1000
+ map.addLayer({
1001
+ id: layerId,
1002
+ type: "line",
1003
+ source: sourceId,
1004
+ "source-layer": sourceLayer,
1005
+ minzoom,
1006
+ maxzoom,
1007
+ paint: {
1008
+ "line-color": input.lineColor,
1009
+ "line-width": input.lineWidth
1010
+ },
1011
+ layout: {
1012
+ visibility: "visible"
1013
+ }
960
1014
  });
961
1015
  }
962
- if (map.getLayer(input.layerId)) {
963
- map.setPaintProperty(input.layerId, "line-color", input.lineColor);
964
- map.setPaintProperty(input.layerId, "line-width", input.lineWidth);
965
- map.setLayoutProperty(input.layerId, "visibility", "visible");
966
- return;
967
- }
968
- map.addLayer({
969
- id: input.layerId,
970
- type: "line",
971
- source: input.sourceId,
972
- "source-layer": input.sourceLayer,
973
- paint: {
974
- "line-color": input.lineColor,
975
- "line-width": input.lineWidth
976
- },
977
- layout: {
978
- visibility: "visible"
979
- }
980
- });
981
1016
  }
982
1017
  function addOrUpdateNhserOverlay(map, tilesBaseUrl, style) {
983
1018
  addOrUpdateBoundaryOverlay(map, tilesBaseUrl, {
984
1019
  sourceId: NHSER_SOURCE_ID,
985
1020
  layerId: NHSER_LAYER_ID,
986
- fullTableName: NHSER_FULL_TABLE_NAME,
987
- sourceLayer: NHSER_SOURCE_LAYER,
1021
+ tablePrefix: NHSER_TABLE_PREFIX,
988
1022
  lineColor: style.boundaries.nhserColor ?? "#e00087",
989
1023
  lineWidth: style.boundaries.nhserWidth ?? 1.5
990
1024
  });
@@ -993,8 +1027,7 @@ function addOrUpdateIcbOverlay(map, tilesBaseUrl, style) {
993
1027
  addOrUpdateBoundaryOverlay(map, tilesBaseUrl, {
994
1028
  sourceId: ICB_SOURCE_ID,
995
1029
  layerId: ICB_LAYER_ID,
996
- fullTableName: ICB_FULL_TABLE_NAME,
997
- sourceLayer: ICB_SOURCE_LAYER,
1030
+ tablePrefix: ICB_TABLE_PREFIX,
998
1031
  lineColor: style.boundaries.icbColor ?? "#57c7f2",
999
1032
  lineWidth: style.boundaries.icbWidth ?? 1
1000
1033
  });
@@ -1003,15 +1036,17 @@ function addOrUpdateLhbOverlay(map, tilesBaseUrl, style) {
1003
1036
  addOrUpdateBoundaryOverlay(map, tilesBaseUrl, {
1004
1037
  sourceId: LHB_SOURCE_ID,
1005
1038
  layerId: LHB_LAYER_ID,
1006
- fullTableName: LHB_FULL_TABLE_NAME,
1007
- sourceLayer: LHB_SOURCE_LAYER,
1039
+ tablePrefix: LHB_TABLE_PREFIX,
1008
1040
  lineColor: style.boundaries.lhbColor ?? "#57c7f2",
1009
1041
  lineWidth: style.boundaries.lhbWidth ?? 1
1010
1042
  });
1011
1043
  }
1012
1044
  function hideOverlay(map, layerId) {
1013
- if (map.getLayer(layerId)) {
1014
- map.setLayoutProperty(layerId, "visibility", "none");
1045
+ for (const { tier } of ZOOM_TIERS) {
1046
+ const tierLayerId = overlayLayerId(layerId, tier);
1047
+ if (map.getLayer(tierLayerId)) {
1048
+ map.setLayoutProperty(tierLayerId, "visibility", "none");
1049
+ }
1015
1050
  }
1016
1051
  }
1017
1052
  function hideNhserOverlay(map) {