@@ -53,9 +53,13 @@ class ShelterAccessApp {
5353 optimalShelters : true ,
5454 statisticalAreas : false ,
5555 habitationClusters : false ,
56- accessibilityHeatmap : false
56+ accessibilityHeatmap : false ,
57+ highlightSchools : false
5758 } ;
5859
60+ // Cached set of building indices covered by school shelters
61+ this . schoolCoveredBuildings = new Set ( ) ;
62+
5963 // Precomputed shelter coverage data
6064 this . shelterCoverageData = null ;
6165
@@ -71,8 +75,8 @@ class ShelterAccessApp {
7175
7276
7377
74- // Mapbox token for terrain and other services (URL restricted )
75- this . mapboxToken = 'pk.eyJ1IjoibnVybGFiMjAyNSIsImEiOiJjbWRrbW51ZnIweGxqMmxzNTJsc3pkODFkIn0.45x41GXrY4QBBqrHBlc7fw ';
78+ // Loaded from js/config.js (gitignored )
79+ this . mapboxToken = window . MAPBOX_TOKEN || ' ';
7680
7781 // Basemap configuration - Mapbox primary, ESRI fallback
7882 this . currentBasemap = 'satellite' ;
@@ -130,7 +134,8 @@ class ShelterAccessApp {
130134 requestedSheltersLayer : document . getElementById ( 'requestedSheltersLayer' ) ,
131135 optimalSheltersLayer : document . getElementById ( 'optimalSheltersLayer' ) ,
132136 statisticalAreasLayer : document . getElementById ( 'statisticalAreasLayer' ) ,
133- habitationClustersLayer : document . getElementById ( 'habitationClustersLayer' )
137+ habitationClustersLayer : document . getElementById ( 'habitationClustersLayer' ) ,
138+ highlightSchoolsLayer : document . getElementById ( 'highlightSchoolsLayer' )
134139 } ;
135140 }
136141
@@ -205,6 +210,27 @@ class ShelterAccessApp {
205210 return this . calculateShelterCoverage ( shelter ) ;
206211 }
207212
213+ /**
214+ * Compute the union of all building indices covered by school-type shelters
215+ */
216+ computeSchoolCoverage ( ) {
217+ const currentData = this . spatialAnalyzer . getCurrentData ( ) ;
218+ if ( ! currentData . shelters ) return ;
219+
220+ const schoolShelters = currentData . shelters . features . filter ( s =>
221+ s . properties && s . properties . type === 'School' && s . properties . status === 'Built'
222+ ) ;
223+
224+ const covered = new Set ( ) ;
225+ for ( const shelter of schoolShelters ) {
226+ const indices = this . getShelterCoverage ( shelter ) ;
227+ for ( const idx of indices ) {
228+ covered . add ( idx ) ;
229+ }
230+ }
231+ this . schoolCoveredBuildings = covered ;
232+ }
233+
208234 /**
209235 * Initialize the application
210236 */
@@ -534,6 +560,18 @@ class ShelterAccessApp {
534560 }
535561 } ) ;
536562
563+ if ( this . elements . highlightSchoolsLayer ) {
564+ this . elements . highlightSchoolsLayer . addEventListener ( 'change' , ( e ) => {
565+ this . layerVisibility . highlightSchools = e . target . checked ;
566+ if ( e . target . checked ) {
567+ this . computeSchoolCoverage ( ) ;
568+ } else {
569+ this . schoolCoveredBuildings = new Set ( ) ;
570+ }
571+ this . updateVisualization ( ) ;
572+ } ) ;
573+ }
574+
537575
538576 }
539577
@@ -902,12 +940,12 @@ class ShelterAccessApp {
902940
903941 // === BUILDINGS LAYER (Simplified GeoJSON) ===
904942 const shouldShowBuildings = this . layerVisibility . buildings ||
905- ( this . hoveredShelter && this . hoveredBuildings . length > 0 ) ;
943+ ( this . hoveredShelter && this . hoveredBuildings . length > 0 ) ||
944+ this . layerVisibility . highlightSchools ;
906945
907946 if ( shouldShowBuildings && currentData . buildings && currentData . buildings . features . length > 0 ) {
908947 let buildingsToShow ;
909- if ( this . layerVisibility . buildings ) {
910- // Show all buildings when layer is enabled
948+ if ( this . layerVisibility . buildings || this . layerVisibility . highlightSchools ) {
911949 buildingsToShow = currentData . buildings . features ;
912950 } else {
913951 // Only show covered buildings when layer is disabled (highlighting mode)
@@ -924,7 +962,7 @@ class ShelterAccessApp {
924962 id : 'buildings-geojson' ,
925963 data : buildingsToShow . map ( ( feature , index ) => ( {
926964 ...feature ,
927- _index : index // Add array index for identification
965+ _index : index
928966 } ) ) ,
929967 pickable : false ,
930968 stroked : true ,
@@ -935,37 +973,51 @@ class ShelterAccessApp {
935973 const buildingIndex = d . _index || 0 ;
936974
937975 if ( this . layerVisibility . buildings ) {
938- // Check if this building is covered by hovered shelter (turn green)
939976 if ( this . hoveredShelter && this . hoveredBuildings . includes ( buildingIndex ) ) {
940- return [ 0 , 255 , 0 , 150 ] ; // Bright green for hover coverage
977+ return [ 0 , 255 , 0 , 150 ] ;
978+ }
979+ if ( this . layerVisibility . highlightSchools && this . schoolCoveredBuildings . has ( buildingIndex ) ) {
980+ return [ 34 , 197 , 94 , 170 ] ; // Green for school-covered buildings
981+ }
982+ return [ 59 , 130 , 246 , 180 ] ;
983+ } else if ( this . layerVisibility . highlightSchools ) {
984+ if ( this . schoolCoveredBuildings . has ( buildingIndex ) ) {
985+ return [ 34 , 197 , 94 , 170 ] ;
941986 }
942- return [ 59 , 130 , 246 , 180 ] ; // Blue for all other buildings when layer is enabled (matches menu buttons)
987+ return [ 59 , 130 , 246 , 60 ] ; // Faded blue for non-covered
943988 } else {
944- // Buildings layer is disabled - only show covered buildings in green
945- return [ 0 , 255 , 0 , 200 ] ; // Bright green for covered buildings
989+ return [ 0 , 255 , 0 , 200 ] ;
946990 }
947991 } ,
948992 getLineColor : d => {
949993 const buildingIndex = d . _index || 0 ;
950994
951995 if ( this . layerVisibility . buildings ) {
952- // Check if this building is covered by hovered shelter (turn green)
953996 if ( this . hoveredShelter && this . hoveredBuildings . includes ( buildingIndex ) ) {
954- return [ 0 , 255 , 0 , 255 ] ; // Bright green outline for hover
997+ return [ 0 , 255 , 0 , 255 ] ;
998+ }
999+ if ( this . layerVisibility . highlightSchools && this . schoolCoveredBuildings . has ( buildingIndex ) ) {
1000+ return [ 34 , 197 , 94 , 255 ] ;
1001+ }
1002+ return [ 59 , 130 , 246 , 220 ] ;
1003+ } else if ( this . layerVisibility . highlightSchools ) {
1004+ if ( this . schoolCoveredBuildings . has ( buildingIndex ) ) {
1005+ return [ 34 , 197 , 94 , 255 ] ;
9551006 }
956- return [ 59 , 130 , 246 , 220 ] ; // Blue outline for all other buildings (matches menu buttons)
1007+ return [ 59 , 130 , 246 , 80 ] ;
9571008 } else {
958- // Buildings layer is disabled - only show covered building outlines in green
959- return [ 0 , 255 , 0 , 255 ] ; // Bright green outline for covered buildings
1009+ return [ 0 , 255 , 0 , 255 ] ;
9601010 }
9611011 } ,
9621012 getLineWidth : d => {
9631013 const buildingIndex = d . _index || 0 ;
9641014
965- // Thicker outline for highlighted buildings
9661015 if ( this . hoveredShelter && this . hoveredBuildings . includes ( buildingIndex ) ) {
9671016 return 2 ;
9681017 }
1018+ if ( this . layerVisibility . highlightSchools && this . schoolCoveredBuildings . has ( buildingIndex ) ) {
1019+ return 2 ;
1020+ }
9691021 return 1 ;
9701022 }
9711023 } ) ) ;
@@ -1110,19 +1162,57 @@ class ShelterAccessApp {
11101162 ) ;
11111163
11121164 if ( existingShelters . length > 0 ) {
1113- layers . push ( new deck . IconLayer ( {
1114- id : 'existing-shelters' ,
1115- data : existingShelters ,
1116- pickable : true ,
1117- getIcon : ( ) => this . getShelterIcon ( 'existing' ) ,
1118- getPosition : d => d . geometry . coordinates ,
1119- onHover : ( info ) => this . handleHover ( info ) ,
1120- onClick : ( info ) => this . handleClick ( info ) ,
1121- loadOptions : this . getSVGLoadOptions ( ) ,
1122- textureParameters : this . getTextureParameters ( ) ,
1123- alphaCutoff : 0.05 , // Clean edges for better visual quality
1124- ...this . getIconSizeConfig ( 'existing' )
1125- } ) ) ;
1165+ if ( this . layerVisibility . highlightSchools ) {
1166+ const nonSchoolShelters = existingShelters . filter ( s => s . properties . type !== 'School' ) ;
1167+ const schoolShelters = existingShelters . filter ( s => s . properties . type === 'School' ) ;
1168+
1169+ if ( nonSchoolShelters . length > 0 ) {
1170+ layers . push ( new deck . IconLayer ( {
1171+ id : 'existing-shelters' ,
1172+ data : nonSchoolShelters ,
1173+ pickable : true ,
1174+ getIcon : ( ) => this . getShelterIcon ( 'existing' ) ,
1175+ getPosition : d => d . geometry . coordinates ,
1176+ onHover : ( info ) => this . handleHover ( info ) ,
1177+ onClick : ( info ) => this . handleClick ( info ) ,
1178+ loadOptions : this . getSVGLoadOptions ( ) ,
1179+ textureParameters : this . getTextureParameters ( ) ,
1180+ alphaCutoff : 0.05 ,
1181+ ...this . getIconSizeConfig ( 'existing' )
1182+ } ) ) ;
1183+ }
1184+ if ( schoolShelters . length > 0 ) {
1185+ layers . push ( new deck . ScatterplotLayer ( {
1186+ id : 'school-shelters' ,
1187+ data : schoolShelters ,
1188+ pickable : true ,
1189+ getPosition : d => d . geometry . coordinates ,
1190+ getRadius : 40 ,
1191+ radiusMinPixels : 6 ,
1192+ radiusMaxPixels : 14 ,
1193+ getFillColor : [ 34 , 197 , 94 , 220 ] ,
1194+ getLineColor : [ 255 , 255 , 255 , 255 ] ,
1195+ lineWidthMinPixels : 2 ,
1196+ stroked : true ,
1197+ onHover : ( info ) => this . handleHover ( info ) ,
1198+ onClick : ( info ) => this . handleClick ( info )
1199+ } ) ) ;
1200+ }
1201+ } else {
1202+ layers . push ( new deck . IconLayer ( {
1203+ id : 'existing-shelters' ,
1204+ data : existingShelters ,
1205+ pickable : true ,
1206+ getIcon : ( ) => this . getShelterIcon ( 'existing' ) ,
1207+ getPosition : d => d . geometry . coordinates ,
1208+ onHover : ( info ) => this . handleHover ( info ) ,
1209+ onClick : ( info ) => this . handleClick ( info ) ,
1210+ loadOptions : this . getSVGLoadOptions ( ) ,
1211+ textureParameters : this . getTextureParameters ( ) ,
1212+ alphaCutoff : 0.05 ,
1213+ ...this . getIconSizeConfig ( 'existing' )
1214+ } ) ) ;
1215+ }
11261216 }
11271217 }
11281218
@@ -1254,6 +1344,15 @@ class ShelterAccessApp {
12541344 } ) ;
12551345 }
12561346
1347+ if ( this . layerVisibility . highlightSchools ) {
1348+ legendItems . push ( {
1349+ type : 'color-dot' ,
1350+ className : 'school-shelter' ,
1351+ label : 'Schools' ,
1352+ color : 'rgb(34, 197, 94)'
1353+ } ) ;
1354+ }
1355+
12571356 // Clear existing legend items
12581357 this . elements . legendItems . innerHTML = '' ;
12591358
@@ -1274,17 +1373,19 @@ class ShelterAccessApp {
12741373 iconDiv . className = `legend-icon ${ item . className } ` ;
12751374
12761375 if ( item . type === 'svg-icon' ) {
1277- // Use actual SVG icons that match the map icons exactly
12781376 const img = document . createElement ( 'img' ) ;
12791377 img . src = item . iconSrc ;
12801378 img . className = 'legend-icon-img' ;
12811379 iconDiv . appendChild ( img ) ;
12821380 } else if ( item . type === 'color-box' ) {
1283- // Use inner div for color boxes to match SVG icon structure
12841381 const colorBox = document . createElement ( 'div' ) ;
12851382 colorBox . className = 'legend-color-box' ;
12861383 colorBox . style . background = item . color ;
12871384 iconDiv . appendChild ( colorBox ) ;
1385+ } else if ( item . type === 'color-dot' ) {
1386+ const dot = document . createElement ( 'div' ) ;
1387+ dot . style . cssText = `width:10px;height:10px;border-radius:50%;background:${ item . color } ;border:2px solid #fff;box-shadow:0 0 2px rgba(0,0,0,0.3);` ;
1388+ iconDiv . appendChild ( dot ) ;
12881389 }
12891390
12901391 const label = document . createElement ( 'span' ) ;
0 commit comments