Adddresses #196 and #201 - adds zoom fade for higher zoom levels

This commit is contained in:
Nat Hillard 2021-06-25 17:37:16 -04:00
parent 41e394972c
commit 5897806bcf
19 changed files with 33652 additions and 3563 deletions

36514
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -32,7 +32,9 @@
"@testing-library/cypress": "^7.0.6",
"@testing-library/jest-dom": "^5.12.0",
"@testing-library/react": "^11.2.7",
"@types/chroma-js": "^2.1.3",
"@types/jest": "^26.0.23",
"@types/mapbox-gl": "^2.3.0",
"@types/node": "^15.3.1",
"@types/ol": "^6.5.1",
"@types/react": "^17.0.1",
@ -69,6 +71,8 @@
},
"dependencies": {
"@trussworks/react-uswds": "github:nathillardusds/react-uswds#nathillardusds/ssr",
"chroma-js": "^2.1.2",
"mapbox-gl": "^1.13.0",
"ol": "^6.5.0",
"ol-mapbox-style": "^6.3.2",
"query-string": "^7.0.0",

View file

@ -1,301 +0,0 @@
import React, {useState, useEffect, useRef} from 'react';
import Map from 'ol/Map';
import View from 'ol/View';
import Feature, {FeatureLike} from 'ol/Feature';
import Geometry from 'ol/geom/Geometry';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import {fromLonLat} from 'ol/proj';
import * as styles from './map.module.scss';
import olms from 'ol-mapbox-style';
import Overlay from 'ol/Overlay';
// import {Table} from '@trussworks/react-uswds';
// @ts-ignore
import zoomIcon from '/node_modules/uswds/dist/img/usa-icons/zoom_in.svg';
const mapConfig = {
'version': 8,
'cursor': 'pointer',
'sources': {
'carto-light': {
'type': 'raster',
'tiles': [
'https://a.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'https://b.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'https://c.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'https://d.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
],
},
'custom': {
'type': 'vector',
'tiles': [
'http://usds-geoplatform-justice40-website.s3-website-us-east-1.amazonaws.com/0624_demo/{z}/{x}/{y}.pbf',
// For local development, use:
// 'http://localhost:8080/data/tl_2010_bg_with_data/{z}/{x}/{y}.pbf',
],
},
'labels': {
'type': 'raster',
'tiles': [
'https://cartodb-basemaps-a.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
'https://cartodb-basemaps-b.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
'https://cartodb-basemaps-c.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
'https://cartodb-basemaps-d.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
],
},
},
'layers': [
{
'id': 'carto-light-layer',
'source': 'carto-light',
'type': 'raster',
'minzoom': 0,
'maxzoom': 22,
},
{
'id': 'blocks',
'type': 'fill',
'source': 'custom',
'source-layer': 'blocks',
'minzoom': 0,
'layout': {
'line-cap': 'round',
'line-join': 'round',
},
// 01=AL, 30=MT, 34=NJ, 35=NM, 36=NY
'filter': ['in', 'STATEFP10', '01', '30', '34', '35', '36'],
'paint': {
'fill-color': [
'interpolate',
['linear'],
['to-number', [
'get',
'Score C (percentile)',
]],
0.0,
'white',
1,
'blue',
],
'fill-opacity': 0.75,
},
},
{
'id': 'labels-only',
'type': 'raster',
'source': 'labels',
'minzoom': 0,
'maxzoom': 22,
},
],
};
interface IMapWrapperProps {
features: Feature<Geometry>[],
};
// The below adapted from
// https://taylor.callsen.me/using-openlayers-with-react-functional-components/
const MapWrapper = ({features}: IMapWrapperProps) => {
const [map, setMap] = useState<Map>();
const [featuresLayer, setFeaturesLayer] = useState<VectorLayer>();
const [currentOverlay, setCurrentOverlay] = useState<Overlay>();
const [selectedFeature, setSelectedFeature] = useState<FeatureLike>();
const [currentZoom, setCurrentZoom] = useState<number>(0);
const mapElement = useRef() as
React.MutableRefObject<HTMLInputElement>;
// create state ref that can be accessed in OpenLayers onclick callback function
// https://stackoverflow.com/a/60643670
const mapRef = useRef() as React.MutableRefObject<Map>;
if (map) {
mapRef.current = map;
}
const popupContainer = React.useRef<HTMLDivElement>(null);
const popupCloser = React.useRef<HTMLAnchorElement>(null);
const popupContent = React.useRef<HTMLDivElement>(null);
const overlayRef = useRef() as React.MutableRefObject<Overlay>;
overlayRef.current = currentOverlay!;
useEffect( () => {
// create and add initial vector source layer, to be replaced layer
const initialFeaturesLayer = new VectorLayer({
source: new VectorSource(),
});
if (!popupCloser || !popupContainer) {
return;
}
popupCloser.current!.onclick = function() {
overlay.setPosition(undefined);
popupCloser.current!.blur();
return false;
};
const overlay = new Overlay({
// Using the non-null assertion operator as we check for null above
element: popupContainer.current!,
autoPan: true,
autoPanAnimation: {
duration: 250,
},
});
const initialMap = new Map({
target: mapElement.current,
view: new View({
center: fromLonLat([-86.502136, 32.4687126]),
zoom: 4,
}),
controls: [],
overlays: [overlay],
});
const currentZoom = Math.floor(initialMap.getView().getZoom());
initialMap.on('moveend', handleMoveEnd);
initialMap.on('click', handleMapClick);
setMap(initialMap);
setCurrentZoom(currentZoom);
setCurrentOverlay(overlay);
setFeaturesLayer(initialFeaturesLayer);
olms(initialMap, mapConfig);
}, []);
// update map if features prop changes
useEffect( () => {
if (features.length) { // may be empty on first render
// set features to map
featuresLayer?.setSource(
new VectorSource({
features: features,
}),
);
const extent = featuresLayer?.getSource().getExtent();
if (extent != null) {
// fit map to feature extent (with 100px of padding)
map?.getView().fit(extent, {
padding: [100, 100, 100, 100],
});
}
}
}, [features]);
const handleMapClick = (event: { pixel: any; }) => {
const clickedCoord = mapRef.current.getCoordinateFromPixel(event.pixel);
mapRef.current.forEachFeatureAtPixel(event.pixel, (feature) => {
setSelectedFeature(feature);
return true;
});
overlayRef.current.setPosition(clickedCoord);
};
const handleMoveEnd = () => {
const newZoom = Math.floor(mapRef.current.getView().getZoom()!);
if (currentZoom != newZoom) {
setCurrentZoom(newZoom);
}
};
const readablePercent = (percent: number) => {
return `${(percent * 100).toFixed(2)}%`;
};
let properties;
if (selectedFeature) {
properties = selectedFeature.getProperties();
}
const getCategorization = (percentile: number) => {
let categorization = '';
if (percentile >= 0.75 ) {
categorization = 'Prioritized';
} else if (0.60 <= percentile && percentile < 0.75) {
categorization = 'Threshold';
} else {
categorization = 'Non-prioritized';
}
return categorization;
};
const getTitleContent = (properties: { [key: string]: any; }) => {
const blockGroup = properties['GEOID10'];
const score = properties['Score C (percentile)'];
return (
<table className={styles.popupHeaderTable}>
<tbody>
<tr>
<td><strong>Census Block Group:</strong></td>
<td>{blockGroup}</td>
</tr>
<tr>
<td><strong>Just Progress Categorization:</strong></td>
<td>{getCategorization(score)}</td>
</tr>
<tr>
<td><strong>Cumulative Index Score:</strong></td>
<td>{readablePercent(score)}</td>
</tr>
</tbody>
</table>
);
};
// const propertyTest = (propertyName:string) => {
// // Filter out properties in all caps
// return !propertyName.match(/^[A-Z0-9]+$/);
// };
return (
<div className={styles.mapWrapperContainer}>
<div ref={mapElement} className={styles.mapContainer} />
<div ref={popupContainer} className={styles.popupContainer}>
<a href="#" ref={popupCloser} className={styles.popupCloser}></a>
<div ref={popupContent} className={styles.popupContent}>
{(selectedFeature && properties) ?
<div>
{getTitleContent(properties)}
{/* <Table bordered={false}>
<thead>
<tr>
<th scope="col">Indicator</th>
<th scope="col">Percentile(0-100)</th>
</tr>
</thead>
<tbody>
{
Object.keys(properties).map((key, index) => (
(propertyTest(key)) &&
<tr key={key} >
<td>{key}</td>
<td>{properties[key]}</td>
</tr>
))
}
</tbody>
</Table> */}
</div> :
''}
</div>
</div>
{ currentZoom < 5 ?
<div className={styles.zoomWarning}>
<img
src={zoomIcon} alt={'zoom icon'}/>
Zoom in to the state or regional level to see prioritized communities on the map.
</div> :
''
}
</div>
);
};
export default MapWrapper;

View file

@ -1,10 +1,3 @@
.mapContainer {
height: 676px;
margin-bottom: 29px;
max-width: revert;
margin-top: 50px;
}
.popupContainer {
position: absolute;
background-color: white;
@ -15,6 +8,7 @@
bottom: 12px;
left: -50px;
min-width: 280px;
overflow: scroll;
}
.popupContainer:after,
@ -55,31 +49,4 @@
.popupContent {
max-height: 300px;
overflow: scroll;
}
.popupHeaderTable {
border-collapse: collapse;
border-spacing: 0;
font: normal 12px;
border: none;
}
.popupHeaderTable tbody td:first {
font-weight: bold;
}
.zoomWarning {
background-color: #953a10;
height: 8%;
width: 66%;
margin: auto;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
.zoomWarning > img {
filter: invert(100%);
}

View file

@ -0,0 +1,14 @@
declare namespace MapControlModuleScssNamespace {
export interface IMapControlModuleScss {
popupContainer: string;
popupCloser: string;
popupContent: string;
}
}
declare const MapControlModuleScssModule: MapControlModuleScssNamespace.IMapControlModuleScss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: MapControlModuleScssNamespace.IMapControlModuleScss;
};
export = MapControlModuleScssModule;

View file

@ -0,0 +1,130 @@
import React, {useRef, useEffect, useState} from 'react';
import * as styles from './mapPopup.module.scss';
import Overlay from 'ol/Overlay';
import {Table} from '@trussworks/react-uswds';
import {Coordinate} from 'ol/Coordinate';
import Map from 'ol/Map';
import {FeatureLike} from 'ol/Feature';
interface IMapPopupProps {
map: Map,
selectedFeature: FeatureLike;
position: Coordinate;
}
const MapPopup = ({map, selectedFeature, position}: IMapPopupProps) => {
const popupContainerElement = useRef<HTMLDivElement>(null);
const popupCloserElement = useRef<HTMLAnchorElement>(null);
const popupContentElement = useRef<HTMLDivElement>(null);
const [currentOverlay, setCurrentOverlay] = useState<Overlay>();
const readablePercent = (percent: number) => {
return `${(percent * 100).toFixed(2)}%`;
};
useEffect(() => {
if (!popupCloserElement && !popupContainerElement) return;
popupCloserElement.current!.onclick = function() {
overlay.setPosition(undefined);
popupCloserElement.current!.blur();
return false;
};
const overlay = new Overlay({
element: popupContainerElement.current!,
autoPan: true,
autoPanAnimation: {
duration: 250,
},
});
setCurrentOverlay(overlay);
map.addOverlay(overlay);
}, []);
useEffect( () => {
if (position && currentOverlay) { // may be empty on first render
currentOverlay.setPosition(position);
}
}, [position]);
const getCategorization = (percentile: number) => {
let categorization;
if (percentile >= 0.75 ) {
categorization = 'Prioritized';
} else if (0.60 <= percentile && percentile < 0.75) {
categorization = 'Threshold';
} else {
categorization = 'Non-prioritized';
}
return categorization;
};
const getTitleContent = () => {
const properties = selectedFeature.getProperties();
const blockGroup = properties['GEOID10'];
const score = properties['Score C (percentile)'];
return (
<table>
<tbody>
<tr>
<td><strong>Census Block Group:</strong></td>
<td>{blockGroup}</td>
</tr>
<tr>
<td><strong>Just Progress Categorization:</strong></td>
<td>{getCategorization(score)}</td>
</tr>
<tr>
<td><strong>Cumulative Index Score:</strong></td>
<td>{readablePercent(score)}</td>
</tr>
</tbody>
</table>
);
};
const getBodyContent = () => {
const properties = selectedFeature.getProperties();
const rows = [];
for (const [key, value] of Object.entries(properties)) {
// Filter out all caps
if (!key.match(/^[A-Z0-9]+$/)) {
rows.push(<tr key={key} >
<td>{key}</td>
<td>{value}</td>
</tr>);
}
}
return rows;
};
const popupContent = (selectedFeature) ? (
<div>
{getTitleContent()}
<Table bordered={false}>
<thead>
<tr>
<th scope="col">Indicator</th>
<th scope="col">Percentile(0-100)</th>
</tr>
</thead>
<tbody>
{getBodyContent()}
</tbody>
</Table>
</div>
) : null;
return (
<>
<div ref={popupContainerElement} className={styles.popupContainer}>
<a href="#" ref={popupCloserElement} className={styles.popupCloser}></a>
<div ref={popupContentElement} className={styles.popupContent}>
{popupContent}
</div>
</div>
</>
);
};
export default MapPopup;

View file

@ -0,0 +1,20 @@
import * as React from 'react';
import {useFlags} from '../contexts/FlagContext';
import MapboxMap from './mapboxMap';
import OpenLayersMap from './openlayersMap';
const MapWrapper = () => {
const flags = useFlags();
return (
<div>
{
flags.includes('mb') ?
<MapboxMap /> :
<OpenLayersMap features={[]}/>
}
</div>
);
};
export default MapWrapper;

View file

@ -0,0 +1,22 @@
$sidebar-background: rgba(35, 55, 75, 0.9);
$sidebar-color: #ffffff;
.mapContainer {
height: 676px;
margin-bottom: 29px;
max-width: revert;
margin-top: 50px;
}
.sidebar {
background-color: $sidebar-background;
color: $sidebar-color;
padding: 6px 12px;
font-family: monospace;
z-index: 1;
position: absolute;
top: 300px;
left: 0;
margin: 12px;
border-radius: 4px;
}

View file

@ -0,0 +1,13 @@
declare namespace MapboxMapModuleScssNamespace {
export interface IMapboxMapModuleScss {
sidebar: string;
mapContainer: string;
}
}
declare const MapboxMapModuleScssModule: MapboxMapModuleScssNamespace.IMapboxMapModuleScss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: MapboxMapModuleScssNamespace.IMapboxMapModuleScss;
};
export = MapboxMapModuleScssModule;

View file

@ -0,0 +1,50 @@
/* eslint-disable no-unused-vars */
import React, {useRef, useEffect, useState} from 'react';
import {Map, NavigationControl} from 'mapbox-gl';
import * as styles from './mapboxMap.module.scss';
import mapStyle from '../data/mapStyle';
import ZoomWarning from './zoomWarning';
const MapboxMap = () => {
const mapContainer = React.useRef<HTMLDivElement>(null);
const map = useRef<Map>() as React.MutableRefObject<Map>;
const [lng, setLng] = useState(-86.502136);
const [lat, setLat] = useState(32.4687126);
const [zoom, setZoom] = useState(3);
useEffect(() => {
// Only initialize once
if (map.current && mapContainer.current) return;
const initialMap = new Map({
container: mapContainer.current!,
style: mapStyle,
center: [lng, lat],
zoom: zoom,
});
initialMap.addControl(new NavigationControl());
map.current = initialMap;
});
useEffect(() => {
if (!map.current) return; // wait for map to initialize
map.current.on('move', () => {
setLng(map.current.getCenter().lng);
setLat(map.current.getCenter().lat);
setZoom(map.current.getZoom());
});
});
return (
<div>
<div className={styles.sidebar}>
Longitude: {lng.toFixed(4)} | Latitude: {lat.toFixed(4)} | Zoom: {zoom.toFixed(2)}
</div>
<div ref={mapContainer} className={styles.mapContainer}/>
<ZoomWarning zoomLevel={zoom} />
</div>
);
};
export default MapboxMap;

View file

@ -0,0 +1,6 @@
.mapContainer {
height: 676px;
margin-bottom: 29px;
max-width: revert;
margin-top: 50px;
}

View file

@ -1,12 +1,6 @@
declare namespace MapModuleScssNamespace {
export interface IMapModuleScss {
mapContainer: string;
popupContainer: string;
popupCloser: string;
popupContent: string;
popupHeaderTable: string;
zoomWarning:string;
mapWrapperContainer:string;
mapContainer:string;
}
}

View file

@ -0,0 +1,118 @@
import React, {useState, useEffect, useRef} from 'react';
import Map from 'ol/Map';
import View from 'ol/View';
import Feature, {FeatureLike} from 'ol/Feature';
import Geometry from 'ol/geom/Geometry';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import {fromLonLat} from 'ol/proj';
import {Coordinate} from 'ol/coordinate';
import olms from 'ol-mapbox-style';
import mapStyle from '../data/mapStyle';
import ZoomWarning from './zoomWarning';
import MapPopup from './mapPopup';
import * as styles from './openlayersMap.module.scss';
const DEFAULT_ZOOM = 4;
const DEFAULT_US_CENTER = [-86.502136, 32.4687126];
interface IMapWrapperProps {
features: Feature<Geometry>[],
}
// The below adapted from
// https://taylor.callsen.me/using-openlayers-with-react-functional-components/
const MapWrapper = ({features}: IMapWrapperProps) => {
const [map, setMap] = useState<Map>();
const [featuresLayer, setFeaturesLayer] = useState<VectorLayer>();
const [selectedFeature, setSelectedFeature] = useState<FeatureLike>();
const [currentZoom, setCurrentZoom] = useState<number>(4);
const [currentOverlayPosition, setCurrentOverlayPosition] = useState<Coordinate>([]);
const mapElement = useRef() as
React.MutableRefObject<HTMLInputElement>;
// create state ref that can be accessed in OpenLayers onclick callback function
// https://stackoverflow.com/a/60643670
const mapRef = useRef() as React.MutableRefObject<Map>;
if (map) {
mapRef.current = map;
}
useEffect( () => {
const view = new View({
center: fromLonLat(DEFAULT_US_CENTER),
zoom: 4,
});
// create and add initial vector source layer, to be replaced layer
const initialFeaturesLayer = new VectorLayer({
source: new VectorSource(),
});
const initialMap = new Map({
target: mapElement.current,
view: view,
controls: [],
});
const currentZoom = Math.floor(initialMap.getView().getZoom() || DEFAULT_ZOOM);
initialMap.on('moveend', handleMoveEnd);
initialMap.on('click', handleMapClick);
setMap(initialMap);
setCurrentZoom(currentZoom);
setFeaturesLayer(initialFeaturesLayer);
olms(initialMap, mapStyle);
}, []);
// update map if features prop changes
useEffect( () => {
if (features.length) { // may be empty on first render
// set features to map
featuresLayer?.setSource(
new VectorSource({
features: features,
}),
);
const extent = featuresLayer?.getSource().getExtent();
if (extent) {
// fit map to feature extent (with 100px of padding)
map?.getView().fit(extent, {
padding: [100, 100, 100, 100],
});
}
}
}, [features]);
const handleMapClick = (event: { pixel: any }) => {
const clickedCoord = mapRef.current.getCoordinateFromPixel(event.pixel);
mapRef.current.forEachFeatureAtPixel(event.pixel, (feature) => {
setSelectedFeature(feature);
return true;
});
setCurrentOverlayPosition(clickedCoord);
};
const handleMoveEnd = () => {
const newZoom = Math.floor(mapRef.current.getView().getZoom() || DEFAULT_ZOOM);
if (currentZoom != newZoom) {
setCurrentZoom(newZoom);
}
};
return (
<>
<div ref={mapElement} className={styles.mapContainer}/>
{map?
<MapPopup selectedFeature={selectedFeature!} map={map!} position={currentOverlayPosition} /> :
''
}
<ZoomWarning zoomLevel={currentZoom} />
</>
);
};
export default MapWrapper;

View file

@ -0,0 +1,17 @@
.zoomWarning {
background-color: #953a10;
height: 5.5%;
width: 66%;
margin: auto;
color: white;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 50%;
left: 20%;
}
.zoomWarning > img {
filter: invert(100%);
}

View file

@ -0,0 +1,13 @@
declare namespace ZoomWarningModuleScssNamespace {
export interface IZoomWarningModuleScss {
zoomWarning: string;
}
}
declare const ZoomWarningModuleScssModule: ZoomWarningModuleScssNamespace.IZoomWarningModuleScss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: ZoomWarningModuleScssNamespace.IZoomWarningModuleScss;
};
export = ZoomWarningModuleScssModule;

View file

@ -0,0 +1,26 @@
import * as React from 'react';
import * as styles from './zoomWarning.module.scss';
// @ts-ignore
import zoomIcon from '/node_modules/uswds/dist/img/usa-icons/zoom_in.svg';
interface IZoomWarningProps {
zoomLevel: number
}
const ZoomWarning = ({zoomLevel}: IZoomWarningProps) => {
return (
<>
{zoomLevel <= 5 ? (
<div className={styles.zoomWarning}>
<img src={zoomIcon} alt={'zoom icon'}/>
Zoom in to the state or regional level to see prioritized communities on the map.
</div>
) :
''
}
</>
);
};
export default ZoomWarning;

View file

@ -0,0 +1,179 @@
import {Style, FillPaint} from 'mapbox-gl';
import chroma from 'chroma-js';
// eslint-disable-next-line require-jsdoc
function hexToHSLA(hex:string, alpha:number) {
return chroma(hex).alpha(alpha).css('hsl');
}
/**
* `MakePaint` generates a zoom-faded Mapbox style formatted layer given a set of parameters.
*
* @param {string} field : the field within the data to consult
* @param {number} minRamp : the minimum value this can assume
* @param {number} medRamp : the medium value this can assume
* @param {number} maxRamp : the maximum value this can assume
* @param {boolean} high : whether this is a "high" or "low" layer
* @return {FillPaint} a mapboxgl fill layer
**/
function makePaint({
field,
minRamp,
medRamp,
maxRamp,
high,
}: {
field: string;
minRamp: number;
medRamp: number;
maxRamp: number;
high: boolean;
}): FillPaint {
const minColor = 'white'; // '232, 88%, 100%';
const medColor = '#D1DAE6';
const maxColor = '#768FB3'; // '0, 98%, 56%';
return {
'fill-color': [
'interpolate',
['linear'],
['zoom'],
high ? 9 : 0,
[
'step',
['get', field],
hexToHSLA(minColor, high ? 0 : 0.5 ),
minRamp,
hexToHSLA(minColor, high ? 0 : 0.5 ),
medRamp,
hexToHSLA(medColor, high ? 0 : 0.5 ),
maxRamp,
hexToHSLA(maxColor, high ? 0 : 0.5 ),
],
high ? 11 : 9,
[
'step',
['get', field],
hexToHSLA(minColor, high ? 0.5 : 0.5 ),
minRamp,
hexToHSLA(minColor, high ? 0.5 : 0.5 ),
medRamp,
hexToHSLA(medColor, high ? 0.5 : 0.5 ),
maxRamp,
hexToHSLA(maxColor, high ? 0.5 : 0.5 ),
],
high ? 22 : 11,
[
'step',
['get', field],
hexToHSLA(minColor, high ? 0.5 : 0 ),
minRamp,
hexToHSLA(minColor, high ? 0.5 : 0 ),
medRamp,
hexToHSLA(medColor, high ? 0.5 : 0 ),
maxRamp,
hexToHSLA(maxColor, high ? 0.5 : 0 ),
],
],
};
}
const mapStyle : Style = {
'version': 8,
'sources': {
'carto': {
'type': 'raster',
'tiles': [
'https://a.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'https://b.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'https://c.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
'https://d.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png',
],
},
'geo': {
'type': 'raster',
'tiles': [
'https://mt0.google.com/vt/lyrs=p&hl=en&x={x}&y={y}&z={z}',
],
},
'custom': {
'type': 'vector',
'tiles': [
'https://d2zjid6n5ja2pt.cloudfront.net/0624_demo/{z}/{x}/{y}.pbf',
// For local development, use:
// 'http://localhost:8080/data/tl_2010_bg_with_data/{z}/{x}/{y}.pbf',
],
},
'labels': {
'type': 'raster',
'tiles': [
'https://cartodb-basemaps-a.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
'https://cartodb-basemaps-b.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
'https://cartodb-basemaps-c.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
'https://cartodb-basemaps-d.global.ssl.fastly.net/light_only_labels/{z}/{x}/{y}@2x.png',
],
},
},
'layers': [
{
'id': 'carto',
'source': 'carto',
'type': 'raster',
'minzoom': 3,
'maxzoom': 11,
},
{
'id': 'geo',
'source': 'geo',
'type': 'raster',
'minzoom': 3,
'maxzoom': 11,
'layout': {
// Make the layer visible by default.
'visibility': 'none',
},
},
{
'id': 'score-low',
'source': 'custom',
'source-layer': 'blocks',
'type': 'fill',
'filter': ['all',
['>', 'Score C (percentile)', 0.6],
// ['in', 'STATEFP10', '01', '30', '34', '35', '36'],
],
'paint': makePaint({
field: 'Score C (percentile)',
minRamp: 0,
medRamp: 0.6,
maxRamp: 0.75,
high: false,
}),
},
{
'id': 'score-high',
'source': 'custom',
'source-layer': 'blocks',
'type': 'fill',
'filter': ['all',
['>', 'Score C (percentile)', 0.6],
['in', 'STATEFP10', '01', '30', '34', '35', '36'],
],
'paint': makePaint({
field: 'Score C (percentile)',
minRamp: 0,
medRamp: 0.6,
maxRamp: 1.0,
high: true,
}),
},
{
'id': 'labels-only',
'type': 'raster',
'source': 'labels',
'minzoom': 4,
'maxzoom': 11,
},
],
};
export default mapStyle;

View file

@ -1,9 +1,8 @@
import React, {useState} from 'react';
import React from 'react';
import Layout from '../components/layout';
import MapWrapper from '../components/map';
// import MapWrapper from '../components/map';
import MapWrapper from '../components/mapWrapper';
import HowYouCanHelp from '../components/HowYouCanHelp';
import Feature from 'ol/Feature';
import Geometry from 'ol/geom/Geometry';
import {Alert} from '@trussworks/react-uswds';
import * as styles from './cejst.module.scss';
@ -15,7 +14,6 @@ interface IMapPageProps {
const CEJSTPage = ({location}: IMapPageProps) => {
// We temporarily removed MapControls, which would enable you to `setFeatures` also, for now
// We will bring back later when we have interactive controls.
const [features] = useState<Feature<Geometry>[]>([]);
return (
<Layout location={location}>
<main id="main-content" role="main">
@ -50,7 +48,7 @@ const CEJSTPage = ({location}: IMapPageProps) => {
</p>
</Alert>
<h2>Explore the Tool</h2>
<MapWrapper features={features} />
<MapWrapper/>
<HowYouCanHelp />
</main>
</Layout>

View file

@ -13,6 +13,7 @@
"noUnusedParameters": true,
"removeComments": false,
"allowJs": true,
"resolveJsonModule": true,
"baseUrl": "./",
"paths": {
"ol": ["node_modules/ol/src"],
@ -22,8 +23,8 @@
}
},
"include": [
"./src/**/*",
"**/*.ts",
"./src/**/*ts",
"./src/**/*tsx",
"node_modules/ol/**/*",
"node_modules/ol-mapbox-style/**/*"
],