diff --git a/lib/interviewer/containers/Interfaces/Geospatial.tsx b/lib/interviewer/containers/Interfaces/Geospatial.tsx index 43a82629..89eb70b6 100644 --- a/lib/interviewer/containers/Interfaces/Geospatial.tsx +++ b/lib/interviewer/containers/Interfaces/Geospatial.tsx @@ -1,96 +1,131 @@ import mapboxgl from 'mapbox-gl'; import 'mapbox-gl/dist/mapbox-gl.css'; import { useEffect, useRef, useState } from 'react'; -import { type StageProps } from '../Stage'; +import type { Protocol } from '~/lib/protocol-validation/schemas/src/8.zod'; const INITIAL_ZOOM = 10; // should this be configurable? -export default function GeospatialInterface({ stage }: { stage: StageProps }) { +type GeospatialStage = Extract< + Protocol['stages'][number], + { type: 'Geospatial' } +>; + +export default function GeospatialInterface({ + stage, +}: { + stage: GeospatialStage; +}) { const mapRef = useRef(); const mapContainerRef = useRef(); - const [selectedCensusTract, setSelectedCensusTract] = useState(null); - const { center, data, token } = stage; + const [selection, setSelection] = useState(null); + const { center, token, layers, prompts } = stage; + + const filterLayer = layers.find((layer) => layer.filter); + + const currentPrompt = prompts[0]; // only one prompt for now const handleReset = () => { mapRef.current.flyTo({ zoom: INITIAL_ZOOM, center, }); - setSelectedCensusTract(null); - mapRef.current.setFilter('selectedCensusTract', ['==', 'namelsad10', '']); + + setSelection(null); + + if (filterLayer) { + mapRef.current.setFilter(filterLayer.id, ['==', filterLayer.filter, '']); + } }; useEffect(() => { mapboxgl.accessToken = token; + const dataSources = [...new Set(layers.map((layer) => layer.data))]; + mapRef.current = new mapboxgl.Map({ container: mapContainerRef.current, center, zoom: INITIAL_ZOOM, - style: 'mapbox://styles/mapbox/light-v11', + style: 'mapbox://styles/mapbox/light-v11', // should this be configurable? }); mapRef.current.on('load', () => { - mapRef.current.addSource('geojson-data', { - type: 'geojson', - data, - }); + if (!layers) return; - // census tract outlines - mapRef.current.addLayer({ - id: 'censusTractsOutlineLayer', - type: 'line', - source: 'geojson-data', - paint: { - 'line-color': 'purple', - 'line-width': 2, - }, - }); - mapRef.current.addLayer({ - id: 'censusTractsFillLayer', - type: 'fill', - source: 'geojson-data', - paint: { - 'fill-color': 'purple', - 'fill-opacity': 0.1, - }, + dataSources.forEach((dataSource) => { + mapRef.current.addSource('geojson-data', { + // hardcoded source name for now + type: 'geojson', + data: dataSource, + }); }); - mapRef.current.addLayer({ - id: 'selectedCensusTract', - type: 'fill', - source: 'geojson-data', - paint: { - 'fill-color': 'green', - 'fill-opacity': 0.5, - }, - filter: ['==', 'namelsad10', ''], + // add layers based on the protocol + layers.forEach((layer) => { + if (layer.type === 'line') { + mapRef.current?.addLayer({ + id: layer.id, + type: 'line', + source: 'geojson-data', + paint: { + 'line-color': layer.color, + 'line-width': 2, + }, + }); + } else if (layer.type === 'fill' && !layer.filter) { + mapRef.current?.addLayer({ + id: layer.id, + type: 'fill', + source: 'geojson-data', + paint: { + 'fill-color': layer.color, + 'fill-opacity': layer.opacity, + }, + }); + } else if (layer.type === 'fill' && layer.filter) { + mapRef.current?.addLayer({ + id: layer.id, + type: 'fill', + source: 'geojson-data', + paint: { + 'fill-color': layer.color, + 'fill-opacity': layer.opacity, + }, + filter: ['==', layer.filter, ''], + }); + } }); - // handle click of census tracts - mapRef.current.on('click', 'censusTractsFillLayer', (e) => { - const feature = e.features[0]; - const tractId = feature.properties.namelsad10; // census tract name prop. comes from the geojson. this will need to be configured based on the geojson - setSelectedCensusTract(tractId); - - mapRef.current.setFilter('selectedCensusTract', [ - '==', - 'namelsad10', - tractId, - ]); - }); + // if there's a prompt, configure the click event + if (currentPrompt) { + mapRef.current.on('click', currentPrompt.layer, (e) => { + const feature = e.features[0]; + const propToSelect = currentPrompt.mapVariable; // Variable from geojson data + const selected = feature.properties[propToSelect]; + setSelection(selected); + + // Apply the filter to the selection layer if it exists + filterLayer && + mapRef.current.setFilter(filterLayer.id, [ + '==', + filterLayer.filter, + selected, + ]); + }); + } }); return () => { mapRef.current.remove(); }; - }, [center, data, token]); + }, [center, currentPrompt, filterLayer, filterLayer?.filter, layers, token]); return (
Selected: {selectedCensusTract}
+Selected: {selection}