PageSourceSearch

https://bikesontransit.org/js/map-view/overlay-registry.js

js bikesontransit.org collected 2026-09-27 01:48:22 UTC 8,655 bytes, 218 lines download raw bytes

1/* =====================================================================
2 * One description of every reference overlay.
3 *
4 * Each overlay is a self-contained module (crash-layer.js, lts-layer.js,
5 * sabs-layer.js, …) that gates on its own `enable_*` project setting.
6 * That part works. What did not work was that the facts *about* each
7 * overlay — which module to load, which checkbox drives it, which Mapbox
8 * layer ids it creates — were written out again in every consumer:
9 * the mount loop, the master toggle, saved views, the story viewer, the
10 * QGIS export, the PNG legend.
11 *
12 * Six hand-maintained lists, and they drifted. Saved views persisted 4
13 * layer ids out of ~30. The master toggle missed SABS and FHWA, so
14 * "Hide all" left them on the map. The story viewer had no entry for
15 * GTFS, so no chapter could toggle it, and its SABS list
16 * predated the casing and label layers.
17 *
18 * This module is the single description those consumers read.
19 *
20 * Consumed by: map initialization (index.js), master visibility
21 * (overlay-master-toggle.js), saved views and the PNG export legend
22 * (map-views.js), and story layer_rules (story-viewer/viewer-map.js).
23 *
24 * NOT consumed by the QGIS export, and that is not an oversight. QGIS
25 * exports server-side snapshot files keyed by saveOriginal(), which is a
26 * different set from the layers drawn on the map: SABS and FHWA have no
27 * per-project original at all. See routes/export/qgis.js.
28 *
29 * Presentation still lives in each consumer. PNG legend blocks, QGIS
30 * styling and on-screen legend rows each describe colour and class
31 * labels their own way; unifying that is later work.
32 *
33 * ── Adding an overlay ────────────────────────────────────────────────
34 * Add one entry below, plus a PNG legend builder in map-views.js. Mount,
35 * master show/hide, saved-view capture and story rules then work with no
36 * further edits, and tests/__tests__/overlay-registry.spec.js checks the
37 * entry against the module it describes.
38 * ================================================================== */
39
40import { AREA_COVERAGE, DISCRETE } from './overlay-start-visibility.js';
41
42// SABS builds six Mapbox layers per school level. Spelled out here so
43// saved views capture all of them; the story viewer's copy of this list
44// predates the casing/label layers and is missing them.
45const SABS_LEVELS = ['elem', 'middle', 'high'];
46const SABS_LAYER_IDS = SABS_LEVELS.flatMap(k => [
47  `sabs-fill-${k}`, `sabs-casing-${k}`, `sabs-line-${k}`,
48  `sabs-label-${k}`, `sabs-point-${k}`, `sabs-school-label-${k}`,
49]);
50
51/**
52 * Fields:
53 *   key           stable identifier, also the story viewer's overlay name
54 *   name          human label (legend, master-toggle copy)
55 *   module        import path, absolute from the web root. Deliberately
56 *                 not relative: consumers live in different directories
57 *                 (the story viewer is under html/js/story-viewer/), and
58 *                 a './name.js' here would resolve against the importer,
59 *                 not against this file.
60 *   init          exported init function name, called with (map)
61 *   settingsFlags project settings that enable it; the module still does
62 *                 its own gating, this is for reporting and tooling
63 *   activeGlobal  window flag the module sets once mounted
64 *   toggleClass   CSS class of its legend checkbox, or null when the
65 *                 overlay drives visibility some other way (SABS radios)
66 *   coverage      AREA_COVERAGE or DISCRETE, see overlay-start-visibility
67 *   layerIds      Mapbox layer ids the module creates
68 *   layerIdPrefix set when sublayers are built dynamically and cannot be
69 *                 enumerated ahead of time; resolved against the live map
70 *   capturesFilter  whether a saved view should also snapshot getFilter()
71 */
72export const OVERLAY_REGISTRY = [
73  {
74    key: 'epa-walkability',
75    name: 'EPA Walkability',
76    module: '/js/map-view/epa-walkability-layer.js',
77    init: 'initEpaWalkabilityLayer',
78    settingsFlags: ['enable_epa_walkability'],
79    activeGlobal: '_epaWalkabilityActive',
80    toggleClass: 'epa-walkability-toggle',
81    coverage: AREA_COVERAGE,
82    layerIds: ['epa-walkability-fill', 'epa-walkability-stroke'],
83  },
84  {
85    key: 'usda-food-access',
86    name: 'USDA Food Access',
87    module: '/js/map-view/usda-food-access-layer.js',
88    init: 'initUsdaFoodAccessLayer',
89    settingsFlags: ['enable_usda_food_access'],
90    activeGlobal: '_usdaFoodAccessActive',
91    toggleClass: 'usda-food-access-toggle',
92    coverage: AREA_COVERAGE,
93    layerIds: ['usda-food-access-fill', 'usda-food-access-stroke'],
94  },
95  {
96    key: 'walkscore-open',
97    name: 'Walk Score (open)',
98    module: '/js/map-view/walkscore-open-layer.js',
99    init: 'initWalkScoreOpenLayer',
100    settingsFlags: ['enable_walkscore_open'],
101    activeGlobal: '_walkScoreOpenActive',
102    toggleClass: 'walkscore-open-toggle',
103    coverage: AREA_COVERAGE,
104    layerIds: ['walkscore-open-fill', 'walkscore-open-stroke'],
105  },
106  {
107    key: 'fhwa-app',
108    name: 'Persistent-Poverty Areas',
109    module: '/js/map-view/fhwa-app-layer.js',
110    init: 'initFhwaAppLayer',
111    settingsFlags: ['enable_fhwa_app_counties', 'enable_fhwa_app_tracts'],
112    activeGlobal: '_fhwaAppActive',
113    toggleClass: 'fhwa-app-toggle',
114    coverage: AREA_COVERAGE,
115    layerIds: [
116      'fhwa-app-fill-counties', 'fhwa-app-line-counties',
117      'fhwa-app-fill-tracts', 'fhwa-app-line-tracts',
118    ],
119  },
120  {
121    key: 'sabs',
122    name: 'School Attendance Zones',
123    module: '/js/map-view/sabs-layer.js',
124    init: 'initSabsLayer',
125    settingsFlags: ['enable_sabs'],
126    activeGlobal: '_sabsActive',
127    // Radio group, not a checkbox: the three levels are nested coverages
128    // and only one reads at a time. Master show/hide reaches it through
129    // the wm:legend-toggle-all event instead.
130    toggleClass: null,
131    coverage: AREA_COVERAGE,
132    layerIds: SABS_LAYER_IDS,
133  },
134  {
135    key: 'lts',
136    name: 'Bicycle LTS',
137    module: '/js/map-view/lts-layer.js',
138    init: 'initLtsLayer',
139    settingsFlags: ['enable_lts'],
140    activeGlobal: '_ltsActive',
141    toggleClass: 'lts-toggle',
142    coverage: DISCRETE,
143    layerIds: ['lts-line'],
144  },
145  {
146    key: 'bicycle-network',
147    name: 'Bicycle Network',
148    module: '/js/map-view/bicycle-network-layer.js',
149    init: 'initBicycleNetworkLayer',
150    settingsFlags: ['enable_bicycle_network'],
151    activeGlobal: '_bicycleNetworkActive',
152    toggleClass: 'bicycle-network-toggle',
153    coverage: DISCRETE,
154    layerIds: ['bicycle-network-line'],
155  },
156  {
157    key: 'crashes',
158    name: 'Crashes',
159    module: '/js/map-view/crash-layer.js',
160    init: 'initCrashLayer',
161    settingsFlags: ['enable_crashes'],
162    activeGlobal: '_crashesActive',
163    toggleClass: 'crash-toggle',
164    coverage: DISCRETE,
165    layerIds: ['crashes-bike', 'crashes-ped', 'crashes-allmodes'],
166    layerIdPrefix: 'crashes-',
167    // A crash layer's filter says which modes and severities are shown.
168    // A saved view that remembers only "crashes on" loses the slice it
169    // was composed with.
170    capturesFilter: true,
171  },
172  {
173    key: 'gtfs',
174    name: 'Transit (GTFS)',
175    module: '/js/map-view/gtfs-layer.js',
176    init: 'initGtfsLayer',
177    settingsFlags: ['enable_gtfs'],
178    activeGlobal: '_gtfsActive',
179    toggleClass: 'gtfs-toggle',
180    coverage: DISCRETE,
181    layerIds: ['gtfs-routes-line', 'gtfs-stops-circle'],
182  },
183];
184
185/* ---------------- Derived views ---------------- */
186
187/** Overlays that actually mounted on this project. */
188export function activeOverlays() {
189  return OVERLAY_REGISTRY.filter(o => window[o.activeGlobal]);
190}
191
192/** Legend checkbox classes. Excludes overlays with no checkbox. */
193export function overlayToggleClasses() {
194  return OVERLAY_REGISTRY.map(o => o.toggleClass).filter(Boolean);
195}
196
197/**
198 * Every Mapbox layer id this overlay currently has on the map.
199 * Enumerated ids are filtered to those that exist; a layerIdPrefix
200 * additionally sweeps the live style for dynamically built sublayers.
201 */
202export function resolveLayerIds(entry, map) {
203  if (!map) return [];
204  const ids = new Set(entry.layerIds.filter(id => map.getLayer(id)));
205  if (entry.layerIdPrefix) {
206    for (const l of map.getStyle()?.layers || []) {
207      if (l.id.startsWith(entry.layerIdPrefix)) ids.add(l.id);
208    }
209  }
210  return [...ids];
211}
212
213/** Flat list of live overlay layer ids, paired with their entry. */
214export function allOverlayLayers(map) {
215  return OVERLAY_REGISTRY.flatMap(entry =>
216    resolveLayerIds(entry, map).map(id => ({ id, entry }))
217  );
218}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.