mirror of
https://github.com/DOI-DO/j40-cejst-2.git
synced 2025-08-13 00:34:18 -07:00
Refactor Sources/Layers to allow for tribal switching
- Remove census tracts layers into it's own component - Create a tribal layer component - Update LayerSelector component tests - update OS map to react to layer selector -
This commit is contained in:
parent
70b9072559
commit
d4aed789cc
10 changed files with 399 additions and 154 deletions
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable valid-jsdoc */
|
||||
/* eslint-disable no-unused-vars */
|
||||
// External Libs:
|
||||
import React, {useRef, useState, useMemo} from 'react';
|
||||
import React, {useRef, useState} from 'react';
|
||||
import {Map, MapboxGeoJSONFeature, LngLatBoundsLike} from 'maplibre-gl';
|
||||
import ReactMapGL, {
|
||||
MapEvent,
|
||||
|
@ -12,7 +12,7 @@ import ReactMapGL, {
|
|||
Popup,
|
||||
FlyToInterpolator,
|
||||
FullscreenControl,
|
||||
MapRef, Source, Layer} from 'react-map-gl';
|
||||
MapRef} from 'react-map-gl';
|
||||
import bbox from '@turf/bbox';
|
||||
import * as d3 from 'd3-ease';
|
||||
import {isMobile} from 'react-device-detect';
|
||||
|
@ -26,6 +26,7 @@ import {useFlags} from '../contexts/FlagContext';
|
|||
import AreaDetail from './AreaDetail';
|
||||
import MapInfoPanel from './mapInfoPanel';
|
||||
import MapSearch from './MapSearch';
|
||||
import MapTractLayers from './MapTractLayers/MapTractLayers';
|
||||
import LayerSelector from './LayerSelector';
|
||||
import TerritoryFocusControl from './territoryFocusControl';
|
||||
import {getOSBaseMap} from '../data/getOSBaseMap';
|
||||
|
@ -34,9 +35,7 @@ import {getOSBaseMap} from '../data/getOSBaseMap';
|
|||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import * as constants from '../data/constants';
|
||||
import * as styles from './J40Map.module.scss';
|
||||
import * as COMMON_COPY from '../data/copy/common';
|
||||
|
||||
|
||||
import MapTribalLayer from './MapTribalLayer/MapTribalLayer';
|
||||
declare global {
|
||||
interface Window {
|
||||
Cypress?: object;
|
||||
|
@ -56,49 +55,6 @@ export interface IDetailViewInterface {
|
|||
properties: constants.J40Properties,
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will determine the URL for the map tiles. It will read in a string that will designate either
|
||||
* high or low tiles. It will allow to overide the URL to the pipeline staging tile URL via feature flag.
|
||||
* Lastly, it allows to set the tiles to be local or via the CDN as well.
|
||||
*
|
||||
* @param {string} tilesetName
|
||||
* @returns {string}
|
||||
*/
|
||||
export const featureURLForTilesetName = (tilesetName: string): string => {
|
||||
const flags = useFlags();
|
||||
|
||||
const pipelineStagingBaseURL = `https://justice40-data.s3.amazonaws.com/data-pipeline-staging`;
|
||||
const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
|
||||
|
||||
if ('stage_hash' in flags) {
|
||||
// Check if the stage_hash is valid
|
||||
const regex = /^[0-9]{4}\/[a-f0-9]{40}$/;
|
||||
if (!regex.test(flags['stage_hash'])) {
|
||||
console.error(COMMON_COPY.CONSOLE_ERROR.STAGE_URL);
|
||||
}
|
||||
|
||||
return `${pipelineStagingBaseURL}/${flags['stage_hash']}/data/score/tiles/${tilesetName}/${XYZ_SUFFIX}`;
|
||||
} else {
|
||||
// The feature tile base URL and path can either point locally or the CDN.
|
||||
// This is selected based on the DATA_SOURCE env variable.
|
||||
const featureTileBaseURL = process.env.DATA_SOURCE === 'local' ?
|
||||
process.env.GATSBY_LOCAL_TILES_BASE_URL :
|
||||
process.env.GATSBY_CDN_TILES_BASE_URL;
|
||||
|
||||
const featureTilePath = process.env.DATA_SOURCE === 'local' ?
|
||||
process.env.GATSBY_DATA_PIPELINE_SCORE_PATH_LOCAL :
|
||||
process.env.GATSBY_DATA_PIPELINE_SCORE_PATH;
|
||||
|
||||
return [
|
||||
featureTileBaseURL,
|
||||
featureTilePath,
|
||||
process.env.GATSBY_MAP_TILES_PATH,
|
||||
tilesetName,
|
||||
XYZ_SUFFIX,
|
||||
].join('/');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const J40Map = ({location}: IJ40Interface) => {
|
||||
/**
|
||||
|
@ -128,13 +84,13 @@ const J40Map = ({location}: IJ40Interface) => {
|
|||
const [transitionInProgress, setTransitionInProgress] = useState<boolean>(false);
|
||||
const [geolocationInProgress, setGeolocationInProgress] = useState<boolean>(false);
|
||||
const [isMobileMapState, setIsMobileMapState] = useState<boolean>(false);
|
||||
const [censusSelected, setCensusSelected] = useState(true);
|
||||
const {width: windowWidth} = useWindowSize();
|
||||
|
||||
const mapRef = useRef<MapRef>(null);
|
||||
const flags = useFlags();
|
||||
|
||||
const selectedFeatureId = (selectedFeature && selectedFeature.id) || '';
|
||||
const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]);
|
||||
|
||||
const zoomLatLngHash = mapRef.current?.getMap()._hash._getCurrentHash();
|
||||
|
||||
|
@ -352,7 +308,7 @@ const J40Map = ({location}: IJ40Interface) => {
|
|||
|
||||
|
||||
{/* This will allow to select between the census tract layer and the tribal lands layer */}
|
||||
<LayerSelector />
|
||||
<LayerSelector censusSelected={censusSelected} setCensusSelected={setCensusSelected}/>
|
||||
|
||||
{/**
|
||||
* The ReactMapGL component's props are grouped by the API's documentation. The component also has
|
||||
|
@ -368,7 +324,7 @@ const J40Map = ({location}: IJ40Interface) => {
|
|||
// ****** Map state props: ******
|
||||
// http://visgl.github.io/react-map-gl/docs/api-reference/interactive-map#map-state
|
||||
{...viewport}
|
||||
mapStyle={process.env.MAPBOX_STYLES_READ_TOKEN ? mapBoxBaseLayer : getOSBaseMap()}
|
||||
mapStyle={process.env.MAPBOX_STYLES_READ_TOKEN ? mapBoxBaseLayer : getOSBaseMap(censusSelected)}
|
||||
width="100%"
|
||||
// Ajusting this height with a conditional statement will not render the map on staging.
|
||||
// The reason for this issue is unknown. Consider styling the parent container via SASS.
|
||||
|
@ -382,7 +338,8 @@ const J40Map = ({location}: IJ40Interface) => {
|
|||
minZoom={constants.GLOBAL_MIN_ZOOM}
|
||||
dragRotate={false}
|
||||
touchRotate={false}
|
||||
interactiveLayerIds={[constants.HIGH_ZOOM_LAYER_ID, constants.PRIORITIZED_HIGH_ZOOM_LAYER_ID]}
|
||||
// eslint-disable-next-line max-len
|
||||
interactiveLayerIds={censusSelected ? [constants.HIGH_ZOOM_LAYER_ID, constants.PRIORITIZED_HIGH_ZOOM_LAYER_ID] : [constants.TRIBAL_LAYER_ID]}
|
||||
|
||||
|
||||
// ****** Callback props: ******
|
||||
|
@ -396,96 +353,13 @@ const J40Map = ({location}: IJ40Interface) => {
|
|||
ref={mapRef}
|
||||
data-cy={'reactMapGL'}
|
||||
>
|
||||
{/**
|
||||
* The low zoom source
|
||||
*/}
|
||||
<Source
|
||||
id={constants.LOW_ZOOM_SOURCE_NAME}
|
||||
type="vector"
|
||||
promoteId={constants.GEOID_PROPERTY}
|
||||
tiles={[featureURLForTilesetName('low')]}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_LOW}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_LOW}
|
||||
>
|
||||
|
||||
{/* Low zoom layer - prioritized features only */}
|
||||
<Layer
|
||||
id={constants.LOW_ZOOM_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={['>', constants.SCORE_PROPERTY_LOW, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||
'fill-opacity': constants.LOW_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY}}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_LOW}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_LOW}
|
||||
/>
|
||||
</Source>
|
||||
|
||||
{/**
|
||||
* The high zoom source
|
||||
*/}
|
||||
<Source
|
||||
id={constants.HIGH_ZOOM_SOURCE_NAME}
|
||||
type="vector"
|
||||
promoteId={constants.GEOID_PROPERTY}
|
||||
tiles={[featureURLForTilesetName('high')]}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_HIGH}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
>
|
||||
|
||||
{/* High zoom layer - non-prioritized features only */}
|
||||
<Layer
|
||||
id={constants.HIGH_ZOOM_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={['<', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-opacity': constants.NON_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
|
||||
{/* High zoom layer - prioritized features only */}
|
||||
<Layer
|
||||
id={constants.PRIORITIZED_HIGH_ZOOM_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={['>', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||
'fill-opacity': constants.HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
|
||||
{/* High zoom layer - controls the border between features */}
|
||||
<Layer
|
||||
id={constants.FEATURE_BORDER_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.FEATURE_BORDER_COLOR,
|
||||
'line-width': constants.FEATURE_BORDER_WIDTH,
|
||||
'line-opacity': constants.FEATURE_BORDER_OPACITY,
|
||||
}}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_FEATURE_BORDER}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_FEATURE_BORDER}
|
||||
/>
|
||||
|
||||
{/* High zoom layer - border styling around the selected feature */}
|
||||
<Layer
|
||||
id={constants.SELECTED_FEATURE_BORDER_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={filter} // This filter filters out all other features except the selected feature.
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.SELECTED_FEATURE_BORDER_COLOR,
|
||||
'line-width': constants.SELECTED_FEATURE_BORDER_WIDTH,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
</Source>
|
||||
{/* Load either the Tribal layer or census layer */}
|
||||
{
|
||||
censusSelected ?
|
||||
<MapTractLayers selectedFeature={selectedFeature} selectedFeatureId={selectedFeatureId}/> :
|
||||
<MapTribalLayer />
|
||||
}
|
||||
|
||||
{/* This will add the navigation controls of the zoom in and zoom out buttons */}
|
||||
{ windowWidth > constants.USWDS_BREAKPOINTS.MOBILE_LG && <NavigationControl
|
||||
|
|
|
@ -4,13 +4,21 @@ import {LocalizedComponent} from '../../test/testHelpers';
|
|||
import LayerSelector from './LayerSelector';
|
||||
|
||||
describe('rendering of the LayerSelector', () => {
|
||||
const {asFragment} = render(
|
||||
<LocalizedComponent>
|
||||
<LayerSelector />
|
||||
</LocalizedComponent>,
|
||||
);
|
||||
it('checks if component renders census tracts selected', () => {
|
||||
const {asFragment} = render(
|
||||
<LocalizedComponent>
|
||||
<LayerSelector censusSelected={true} setCensusSelected={() => {}}/>
|
||||
</LocalizedComponent>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('checks if component renders', () => {
|
||||
it('checks if component renders tribal selected', () => {
|
||||
const {asFragment} = render(
|
||||
<LocalizedComponent>
|
||||
<LayerSelector censusSelected={false} setCensusSelected={() => {}}/>
|
||||
</LocalizedComponent>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import React, {useEffect, useState, Dispatch} from 'react';
|
||||
import {useIntl} from 'gatsby-plugin-intl';
|
||||
import {Button, ButtonGroup} from '@trussworks/react-uswds';
|
||||
import {useWindowSize} from 'react-use';
|
||||
|
@ -7,9 +7,13 @@ import {useWindowSize} from 'react-use';
|
|||
import * as styles from './LayerSelector.module.scss';
|
||||
import * as EXPLORE_COPY from '../../data/copy/explore';
|
||||
|
||||
const LayerSelector = () => {
|
||||
interface ILayerSelector {
|
||||
censusSelected: boolean,
|
||||
setCensusSelected: Dispatch<boolean>,
|
||||
}
|
||||
|
||||
const LayerSelector = ({censusSelected, setCensusSelected}:ILayerSelector) => {
|
||||
const intl = useIntl();
|
||||
const [censusSelected, setCensusSelected] = useState(true);
|
||||
|
||||
/**
|
||||
* At compile-time, the width/height returned by useWindowSize will be X. When the client requests the
|
||||
|
@ -45,6 +49,7 @@ const LayerSelector = () => {
|
|||
|
||||
return (
|
||||
<div className={styles.layerSelectorContainer}>
|
||||
{/* // Todo: set i18n here */}
|
||||
<label htmlFor="layer-group">Select layer</label>
|
||||
<ButtonGroup id="layer-group" type="segmented">
|
||||
<Button id="census" type="button" outline={!censusSelected} onClick={(e) => buttonClickHandler(e)}>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rendering of the LayerSelector checks if component renders 1`] = `
|
||||
exports[`rendering of the LayerSelector checks if component renders census tracts selected 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
<label
|
||||
|
@ -40,3 +40,44 @@ exports[`rendering of the LayerSelector checks if component renders 1`] = `
|
|||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`rendering of the LayerSelector checks if component renders tribal selected 1`] = `
|
||||
<DocumentFragment>
|
||||
<div>
|
||||
<label
|
||||
for="layer-group"
|
||||
>
|
||||
Select layer
|
||||
</label>
|
||||
<ul
|
||||
class="usa-button-group usa-button-group--segmented"
|
||||
id="layer-group"
|
||||
>
|
||||
<li
|
||||
class="usa-button-group__item"
|
||||
>
|
||||
<button
|
||||
class="usa-button usa-button--outline"
|
||||
data-testid="button"
|
||||
id="census"
|
||||
type="button"
|
||||
>
|
||||
Census Tracts
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
class="usa-button-group__item"
|
||||
>
|
||||
<button
|
||||
class="usa-button"
|
||||
data-testid="button"
|
||||
id="tribal"
|
||||
type="button"
|
||||
>
|
||||
Tribal Lands
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
155
client/src/components/MapTractLayers/MapTractLayers.tsx
Normal file
155
client/src/components/MapTractLayers/MapTractLayers.tsx
Normal file
|
@ -0,0 +1,155 @@
|
|||
import React, {useMemo} from 'react';
|
||||
import {Source, Layer} from 'react-map-gl';
|
||||
|
||||
// Contexts:
|
||||
import {useFlags} from '../../contexts/FlagContext';
|
||||
|
||||
import * as constants from '../../data/constants';
|
||||
import * as COMMON_COPY from '../../data/copy/common';
|
||||
|
||||
// Todo: Update with real types if this works:
|
||||
interface IMapTractLayers {
|
||||
selectedFeatureId: any,
|
||||
selectedFeature: any
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will determine the URL for the map tiles. It will read in a string that will designate either
|
||||
* high or low tiles. It will allow to overide the URL to the pipeline staging tile URL via feature flag.
|
||||
* Lastly, it allows to set the tiles to be local or via the CDN as well.
|
||||
*
|
||||
* @param {string} tilesetName
|
||||
* @return {string}
|
||||
*/
|
||||
export const featureURLForTilesetName = (tilesetName: string): string => {
|
||||
const flags = useFlags();
|
||||
|
||||
const pipelineStagingBaseURL = `https://justice40-data.s3.amazonaws.com/data-pipeline-staging`;
|
||||
const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
|
||||
|
||||
if ('stage_hash' in flags) {
|
||||
// Check if the stage_hash is valid
|
||||
const regex = /^[0-9]{4}\/[a-f0-9]{40}$/;
|
||||
if (!regex.test(flags['stage_hash'])) {
|
||||
console.error(COMMON_COPY.CONSOLE_ERROR.STAGE_URL);
|
||||
}
|
||||
|
||||
return `${pipelineStagingBaseURL}/${flags['stage_hash']}/data/score/tiles/${tilesetName}/${XYZ_SUFFIX}`;
|
||||
} else {
|
||||
// The feature tile base URL and path can either point locally or the CDN.
|
||||
// This is selected based on the DATA_SOURCE env variable.
|
||||
const featureTileBaseURL = process.env.DATA_SOURCE === 'local' ?
|
||||
process.env.GATSBY_LOCAL_TILES_BASE_URL :
|
||||
process.env.GATSBY_CDN_TILES_BASE_URL;
|
||||
|
||||
const featureTilePath = process.env.DATA_SOURCE === 'local' ?
|
||||
process.env.GATSBY_DATA_PIPELINE_SCORE_PATH_LOCAL :
|
||||
process.env.GATSBY_DATA_PIPELINE_SCORE_PATH;
|
||||
|
||||
return [
|
||||
featureTileBaseURL,
|
||||
featureTilePath,
|
||||
process.env.GATSBY_MAP_TILES_PATH,
|
||||
tilesetName,
|
||||
XYZ_SUFFIX,
|
||||
].join('/');
|
||||
}
|
||||
};
|
||||
|
||||
const MapTractLayers = ({selectedFeatureId, selectedFeature}: IMapTractLayers) => {
|
||||
const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Source
|
||||
id={constants.LOW_ZOOM_SOURCE_NAME}
|
||||
type="vector"
|
||||
promoteId={constants.GEOID_PROPERTY}
|
||||
tiles={[featureURLForTilesetName('low')]}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_LOW}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_LOW}
|
||||
>
|
||||
|
||||
{/* Low zoom layer - prioritized features only */}
|
||||
<Layer
|
||||
id={constants.LOW_ZOOM_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={['>', constants.SCORE_PROPERTY_LOW, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||
'fill-opacity': constants.LOW_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY}}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_LOW}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_LOW}
|
||||
/>
|
||||
</Source>
|
||||
|
||||
{/**
|
||||
* The high zoom source
|
||||
*/}
|
||||
<Source
|
||||
id={constants.HIGH_ZOOM_SOURCE_NAME}
|
||||
type="vector"
|
||||
promoteId={constants.GEOID_PROPERTY}
|
||||
tiles={[featureURLForTilesetName('high')]}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_HIGH}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
>
|
||||
|
||||
{/* High zoom layer - non-prioritized features only */}
|
||||
<Layer
|
||||
id={constants.HIGH_ZOOM_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={['<', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-opacity': constants.NON_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
|
||||
{/* High zoom layer - prioritized features only */}
|
||||
<Layer
|
||||
id={constants.PRIORITIZED_HIGH_ZOOM_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={['>', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||
'fill-opacity': constants.HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
|
||||
{/* High zoom layer - controls the border between features */}
|
||||
<Layer
|
||||
id={constants.FEATURE_BORDER_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.FEATURE_BORDER_COLOR,
|
||||
'line-width': constants.FEATURE_BORDER_WIDTH,
|
||||
'line-opacity': constants.FEATURE_BORDER_OPACITY,
|
||||
}}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_FEATURE_BORDER}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_FEATURE_BORDER}
|
||||
/>
|
||||
|
||||
{/* High zoom layer - border styling around the selected feature */}
|
||||
<Layer
|
||||
id={constants.SELECTED_FEATURE_BORDER_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
filter={filter} // This filter filters out all other features except the selected feature.
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.SELECTED_FEATURE_BORDER_COLOR,
|
||||
'line-width': constants.SELECTED_FEATURE_BORDER_WIDTH,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
</Source>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapTractLayers;
|
3
client/src/components/MapTractLayers/index.tsx
Normal file
3
client/src/components/MapTractLayers/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import MapTractLayers from './MapTractLayers';
|
||||
|
||||
export default MapTractLayers;
|
75
client/src/components/MapTribalLayer/MapTribalLayer.tsx
Normal file
75
client/src/components/MapTribalLayer/MapTribalLayer.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
import React from 'react';
|
||||
import {Source, Layer} from 'react-map-gl';
|
||||
|
||||
import * as constants from '../../data/constants';
|
||||
|
||||
/**
|
||||
* This function will determine the URL for the tribal tiles.
|
||||
* @return {string}
|
||||
*/
|
||||
export const tribalURL = (): string => {
|
||||
const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
|
||||
|
||||
return [
|
||||
process.env.GATSBY_CDN_TILES_BASE_URL,
|
||||
process.env.GATSBY_DATA_PIPELINE_TRIBAL_PATH,
|
||||
process.env.GATSBY_MAP_TILES_PATH,
|
||||
XYZ_SUFFIX,
|
||||
].join('/');
|
||||
};
|
||||
|
||||
const MapTribalLayer = () => {
|
||||
return (
|
||||
<Source
|
||||
id={constants.TRIBAL_SOURCE_NAME}
|
||||
type="vector"
|
||||
promoteId={constants.TRIBAL_ID}
|
||||
tiles={[tribalURL()]}
|
||||
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||
maxzoom={constants.TRIBAL_MAX_ZOOM}
|
||||
>
|
||||
|
||||
{/* Low zoom layer - prioritized features only */}
|
||||
<Layer
|
||||
id={constants.TRIBAL_LAYER_ID}
|
||||
source-layer={constants.TRIBAL_SOURCE_LAYER}
|
||||
// filter={['>', constants.SCORE_PROPERTY_LOW, constants.SCORE_BOUNDARY_THRESHOLD]}
|
||||
type='fill'
|
||||
paint={{
|
||||
'fill-color': constants.TRIBAL_FILL_COLOR,
|
||||
'fill-opacity': constants.TRIBAL_FEATURE_FILL_OPACITY}}
|
||||
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||
maxzoom={constants.TRIBAL_MAX_ZOOM}
|
||||
/>
|
||||
|
||||
{/* Tribal layer - controls the border between features */}
|
||||
<Layer
|
||||
id={constants.FEATURE_BORDER_LAYER_ID}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.TRIBAL_BORDER_COLOR,
|
||||
'line-width': constants.FEATURE_BORDER_WIDTH,
|
||||
'line-opacity': constants.FEATURE_BORDER_OPACITY,
|
||||
}}
|
||||
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||
maxzoom={constants.TRIBAL_MAX_ZOOM}
|
||||
/>
|
||||
|
||||
{/* Tribal layer - border styling around the selected feature */}
|
||||
<Layer
|
||||
id={constants.SELECTED_FEATURE_BORDER_LAYER_ID}
|
||||
source-layer={constants.TRIBAL_SOURCE_NAME}
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.SELECTED_FEATURE_BORDER_COLOR,
|
||||
'line-width': constants.SELECTED_FEATURE_BORDER_WIDTH,
|
||||
}}
|
||||
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||
/>
|
||||
</Source>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapTribalLayer;
|
||||
//
|
3
client/src/components/MapTribalLayer/index.tsx
Normal file
3
client/src/components/MapTribalLayer/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
import MapTribalLayer from './MapTribalLayer';
|
||||
|
||||
export default MapTribalLayer;
|
|
@ -25,6 +25,9 @@ export type J40Properties = { [key: string]: any };
|
|||
|
||||
// ****** SIDE PANEL BACKEND SIGNALS ***********
|
||||
|
||||
// Tribal signals
|
||||
export const TRIBAL_ID = 'tribalId';
|
||||
|
||||
// Set the threshold percentile used by most indicators in the side panel
|
||||
export const DEFAULT_THRESHOLD_PERCENTILE = 90;
|
||||
|
||||
|
@ -187,15 +190,18 @@ export const ISLAND_AREA_LOW_HS_EDU = 'IALHE';
|
|||
export const BASE_MAP_SOURCE_NAME = 'base-map-source-name';
|
||||
export const HIGH_ZOOM_SOURCE_NAME = 'high-zoom-source-name';
|
||||
export const LOW_ZOOM_SOURCE_NAME = 'low-zoom-source-name';
|
||||
export const TRIBAL_SOURCE_NAME = 'tribal-source-name';
|
||||
|
||||
// Layer ID constants
|
||||
export const SCORE_SOURCE_LAYER = 'blocks'; // The name of the layer within the tiles that contains the score
|
||||
export const TRIBAL_SOURCE_LAYER = 'blocks';
|
||||
export const BASE_MAP_LAYER_ID = 'base-map-layer-id';
|
||||
export const HIGH_ZOOM_LAYER_ID = 'high-zoom-layer-id';
|
||||
export const PRIORITIZED_HIGH_ZOOM_LAYER_ID = 'prioritized-high-zoom-layer-id';
|
||||
export const LOW_ZOOM_LAYER_ID = 'low-zoom-layer-id';
|
||||
export const FEATURE_BORDER_LAYER_ID = 'feature-border-layer-id';
|
||||
export const SELECTED_FEATURE_BORDER_LAYER_ID = 'selected-feature-border-layer-id';
|
||||
export const TRIBAL_LAYER_ID = 'tribal-layer-id';
|
||||
|
||||
// Used in layer filters:
|
||||
export const SCORE_PROPERTY_LOW = 'M_SCORE';
|
||||
|
@ -213,18 +219,26 @@ export const GLOBAL_MAX_ZOOM_HIGH = 11;
|
|||
|
||||
export const GLOBAL_MIN_ZOOM_FEATURE_BORDER = 5;
|
||||
export const GLOBAL_MAX_ZOOM_FEATURE_BORDER = 22;
|
||||
export const TRIBAL_MIN_ZOOM = 3;
|
||||
export const TRIBAL_MAX_ZOOM = 22;
|
||||
|
||||
// Opacity
|
||||
export const FEATURE_BORDER_OPACITY = 0.5;
|
||||
export const HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY = 0.3;
|
||||
export const LOW_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY = 0.6;
|
||||
export const NON_PRIORITIZED_FEATURE_FILL_OPACITY = 0;
|
||||
export const TRIBAL_FEATURE_FILL_OPACITY = 0.3;
|
||||
|
||||
// Colors
|
||||
export const FEATURE_BORDER_COLOR = '#4EA5CF';
|
||||
export const SELECTED_FEATURE_BORDER_COLOR = '#1A4480';
|
||||
export const PRIORITIZED_FEATURE_FILL_COLOR = '#768FB3';
|
||||
|
||||
export const TRIBAL_BORDER_COLOR = '#0000FF';
|
||||
export const SELECTED_TRIBAL_BORDER_COLOR = '#FF0000';
|
||||
export const TRIBAL_FILL_COLOR = '#00FF00';
|
||||
|
||||
|
||||
// Widths
|
||||
export const FEATURE_BORDER_WIDTH = 0.8;
|
||||
export const SELECTED_FEATURE_BORDER_WIDTH = 5.0;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {Style} from 'maplibre-gl';
|
||||
import * as constants from '../data/constants';
|
||||
import {featureURLForTilesetName} from '../components/J40Map';
|
||||
import {featureURLForTilesetName} from '../components/MapTractLayers/MapTractLayers';
|
||||
import {tribalURL} from '../components/MapTribalLayer/MapTribalLayer';
|
||||
|
||||
// *********** BASE MAP SOURCES ***************
|
||||
const imageSuffix = constants.isMobile ? '' : '@2x';
|
||||
|
@ -25,8 +26,12 @@ const cartoLightBaseLayer = {
|
|||
|
||||
// Utility function to get OpenSource base maps that are in accordance to JSON spec of MapBox
|
||||
// https://docs.mapbox.com/mapbox-gl-js/style-spec/
|
||||
export const getOSBaseMap = () : Style => {
|
||||
return {
|
||||
export const getOSBaseMap = (censusSelected: boolean) : Style => {
|
||||
return !censusSelected ? {
|
||||
|
||||
/**
|
||||
* Tribal Source
|
||||
*/
|
||||
'version': 8,
|
||||
|
||||
/**
|
||||
|
@ -44,6 +49,68 @@ export const getOSBaseMap = () : Style => {
|
|||
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
||||
},
|
||||
|
||||
/**
|
||||
* Tribal source
|
||||
*/
|
||||
[constants.TRIBAL_SOURCE_NAME]: {
|
||||
'type': 'vector',
|
||||
'promoteId': constants.TRIBAL_ID,
|
||||
'tiles': [tribalURL()],
|
||||
'minzoom': constants.TRIBAL_MIN_ZOOM,
|
||||
'maxzoom': constants.TRIBAL_MAX_ZOOM,
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Tribal Layers
|
||||
*/
|
||||
'layers': [
|
||||
|
||||
// The baseMapLayer
|
||||
{
|
||||
'id': constants.BASE_MAP_LAYER_ID,
|
||||
'source': constants.BASE_MAP_SOURCE_NAME,
|
||||
'type': 'raster',
|
||||
'minzoom': constants.GLOBAL_MIN_ZOOM,
|
||||
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
||||
},
|
||||
|
||||
/**
|
||||
* Tribal layer
|
||||
*/
|
||||
{
|
||||
'id': constants.TRIBAL_LAYER_ID,
|
||||
'source': constants.TRIBAL_SOURCE_NAME,
|
||||
'source-layer': constants.TRIBAL_SOURCE_LAYER,
|
||||
'type': 'fill',
|
||||
'paint': {
|
||||
'fill-color': constants.TRIBAL_FILL_COLOR,
|
||||
'fill-opacity': constants.TRIBAL_FEATURE_FILL_OPACITY,
|
||||
},
|
||||
'minzoom': constants.TRIBAL_MIN_ZOOM,
|
||||
'maxzoom': constants.TRIBAL_MAX_ZOOM,
|
||||
},
|
||||
],
|
||||
} :
|
||||
{
|
||||
'version': 8,
|
||||
|
||||
/**
|
||||
* Census Tract Source
|
||||
* */
|
||||
'sources': {
|
||||
|
||||
/**
|
||||
* The base map source source allows us to define where the tiles can be fetched from.
|
||||
*/
|
||||
[constants.BASE_MAP_SOURCE_NAME]: {
|
||||
'type': 'raster',
|
||||
'tiles': cartoLightBaseLayer.noLabels,
|
||||
'minzoom': constants.GLOBAL_MIN_ZOOM,
|
||||
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
||||
},
|
||||
|
||||
// The High zoom source:
|
||||
[constants.HIGH_ZOOM_SOURCE_NAME]: {
|
||||
// It is only shown at high zoom levels to avoid performance issues at lower zooms
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue