Skip to content

Commit

Permalink
first pass at geospatial interface
Browse files Browse the repository at this point in the history
set up interface, mapbox, tileset for census tracts, selected tract handling
  • Loading branch information
buckhalt committed Dec 6, 2024
1 parent 2ff4c0e commit 4d25c15
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 0 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#SANDBOX_MODE=false # true or false - if true, the app will use the sandbox mode, which disables resetting the database and other features
#PUBLIC_URL="http://yourdomain.com" # When using advanced deployment, this is required. Set to the domain name of your app
#INSTALLATION_ID="your-app-name" # A unique identifier for your app, used for analytics. Generated automatically if not set.
#NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN='' # Public mapbox access token for geospatial feature

# -------------------
# Required environment variables
Expand Down
3 changes: 3 additions & 0 deletions env.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const env = createEnv({
SANDBOX_MODE: strictBooleanSchema,
APP_VERSION: z.string().optional(),
COMMIT_HASH: z.string().optional(),
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN: z.string().optional(),
},
/**
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
Expand All @@ -53,6 +54,8 @@ export const env = createEnv({
SANDBOX_MODE: process.env.SANDBOX_MODE,
APP_VERSION: process.env.APP_VERSION,
COMMIT_HASH: process.env.COMMIT_HASH,
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN:
process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
Expand Down
102 changes: 102 additions & 0 deletions lib/interviewer/containers/Interfaces/Geospatial.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
import { useEffect, useRef, useState } from 'react';
import { env } from '~/env';

const INITIAL_CENTER = [-87.6298, 41.8781]; //chicago
const INITIAL_ZOOM = 10;
const TILESET_URL = 'mapbox://buckhalt.8xmfmn5d'; // chicago census tracts tileset. private -- needs to be used with corresponding mapbox token.

export default function GeospatialInterface() {
const mapRef = useRef();
const mapContainerRef = useRef();
const [selectedCensusTract, setSelectedCensusTract] = useState(null);

const handleReset = () => {
mapRef.current.flyTo({
center: INITIAL_CENTER,
zoom: INITIAL_ZOOM,
});
setSelectedCensusTract(null);
};

useEffect(() => {
mapboxgl.accessToken = env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
mapRef.current = new mapboxgl.Map({
container: mapContainerRef.current,
center: INITIAL_CENTER,
zoom: INITIAL_ZOOM,
style: 'mapbox://styles/mapbox/light-v11',
});

mapRef.current.on('load', () => {
// add census geojson using tileset
mapRef.current.addSource('chicago-census-tiles', {
type: 'vector',
url: TILESET_URL,
});

// census tract outlines
mapRef.current.addLayer({
'id': 'censusTractsOutlineLayer',
'type': 'line',
'source': 'chicago-census-tiles',
'source-layer': 'Census_Tracts_2010-6h8rw5',
'paint': {
'line-color': 'purple',
'line-width': 2,
},
});
mapRef.current.addLayer({
'id': 'censusTractsFillLayer',
'type': 'fill',
'source': 'chicago-census-tiles',
'source-layer': 'Census_Tracts_2010-6h8rw5',
'paint': {
'fill-color': 'purple',
'fill-opacity': 0.1,
},
});

mapRef.current.addLayer({
'id': 'selectedCensusTract',
'type': 'fill',
'source': 'chicago-census-tiles',
'source-layer': 'Census_Tracts_2010-6h8rw5',
'paint': {
'fill-color': 'green',
'fill-opacity': 0.5,
},
'filter': ['==', 'namelsad10', ''],
});

// 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 in the tileset. comes from the geojson.
setSelectedCensusTract(tractId);

mapRef.current.setFilter('selectedCensusTract', [
'==',
'namelsad10',
tractId,
]);
});
});

return () => {
mapRef.current.remove();
};
}, []);

return (
<div className="interface">
<h1>Geospatial Interface</h1>
<div>
<button onClick={handleReset}>Reset</button>
<p>Selected: {selectedCensusTract}</p>
</div>
<div className="h-full w-full p-2" ref={mapContainerRef} />
</div>
);
}
5 changes: 5 additions & 0 deletions lib/protocol-validation/schemas/src/8.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,10 @@ const familyTreeCensusStage = baseStageSchema.extend({
type: z.literal('FamilyTreeCensus'),
});

const geospatialStage = baseStageSchema.extend({
type: z.literal('Geospatial'),
});

// Combine all stage types
const stageSchema = z.discriminatedUnion('type', [
egoFormStage,
Expand All @@ -482,6 +486,7 @@ const stageSchema = z.discriminatedUnion('type', [
anonymisationStage,
oneToManyDyadCensusStage,
familyTreeCensusStage,
geospatialStage,
]);

// Main Protocol Schema
Expand Down
5 changes: 5 additions & 0 deletions lib/test-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { type Protocol } from './protocol-validation/schemas/src/8.zod';

export const protocol: Protocol = {
stages: [
{
id: 'geospatial-interface',
label: 'Geospatial Interface',
type: 'Geospatial',
},
{
id: 'anonymisation-interface',
label: 'Anonymisation Interface',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"lucia": "^2.7.7",
"lucide-react": "^0.314.0",
"luxon": "^3.5.0",
"mapbox-gl": "^3.8.0",
"motion": "^11.12.0",
"next": "^14.2.16",
"nuqs": "^1.19.1",
Expand Down
Loading

0 comments on commit 4d25c15

Please sign in to comment.