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.