mirror of
https://github.com/DOI-DO/j40-cejst-2.git
synced 2025-02-23 10:04:18 -08:00
Merge branch 'vimusds/1692-tribal-layer-toggle' into vimusds/release/frontend-narwhal-2
This commit is contained in:
commit
90a4415cd6
22 changed files with 1040 additions and 277 deletions
|
@ -8,6 +8,7 @@ GATSBY_LOCAL_TILES_BASE_URL=http://localhost:5000/data/data-pipeline
|
||||||
|
|
||||||
GATSBY_DATA_PIPELINE_SCORE_PATH_LOCAL=data_pipeline/data/score
|
GATSBY_DATA_PIPELINE_SCORE_PATH_LOCAL=data_pipeline/data/score
|
||||||
GATSBY_DATA_PIPELINE_SCORE_PATH=data-pipeline/data/score
|
GATSBY_DATA_PIPELINE_SCORE_PATH=data-pipeline/data/score
|
||||||
|
GATSBY_DATA_PIPELINE_TRIBAL_PATH=data-pipeline/data/tribal
|
||||||
|
|
||||||
GATSBY_FILE_DL_PATH_SCREENING_TOOL_DATA_ZIP=downloadable/Screening_Tool_Data.zip
|
GATSBY_FILE_DL_PATH_SCREENING_TOOL_DATA_ZIP=downloadable/Screening_Tool_Data.zip
|
||||||
GATSBY_FILE_DL_PATH_SHAPE_FILE_ZIP=shapefile/usa.zip
|
GATSBY_FILE_DL_PATH_SHAPE_FILE_ZIP=shapefile/usa.zip
|
||||||
|
@ -22,4 +23,4 @@ GATSBY_MAP_TILES_PATH=tiles
|
||||||
# If you want the map to render a MapBox base map (as opposed to the
|
# If you want the map to render a MapBox base map (as opposed to the
|
||||||
# open source one from CartoDB), please create your own API TOKEN from
|
# open source one from CartoDB), please create your own API TOKEN from
|
||||||
# your MapBox account and add the token here:
|
# your MapBox account and add the token here:
|
||||||
# MAPBOX_STYLES_READ_TOKEN=''
|
MAPBOX_STYLES_READ_TOKEN=''
|
|
@ -6,6 +6,7 @@
|
||||||
GATSBY_CDN_TILES_BASE_URL=https://static-data-screeningtool.geoplatform.gov
|
GATSBY_CDN_TILES_BASE_URL=https://static-data-screeningtool.geoplatform.gov
|
||||||
|
|
||||||
GATSBY_DATA_PIPELINE_SCORE_PATH=data-pipeline/data/score
|
GATSBY_DATA_PIPELINE_SCORE_PATH=data-pipeline/data/score
|
||||||
|
GATSBY_DATA_PIPELINE_TRIBAL_PATH=data-pipeline/data/tribal
|
||||||
|
|
||||||
GATSBY_FILE_DL_PATH_SCREENING_TOOL_DATA_ZIP=downloadable/Screening_Tool_Data.zip
|
GATSBY_FILE_DL_PATH_SCREENING_TOOL_DATA_ZIP=downloadable/Screening_Tool_Data.zip
|
||||||
GATSBY_FILE_DL_PATH_SHAPE_FILE_ZIP=shapefile/usa.zip
|
GATSBY_FILE_DL_PATH_SHAPE_FILE_ZIP=shapefile/usa.zip
|
||||||
|
|
|
@ -24,6 +24,7 @@ import mailIcon from '/node_modules/uswds/dist/img/usa-icons/mail_outline.svg';
|
||||||
interface IAreaDetailProps {
|
interface IAreaDetailProps {
|
||||||
properties: constants.J40Properties,
|
properties: constants.J40Properties,
|
||||||
hash: string[],
|
hash: string[],
|
||||||
|
isCensusLayerSelected: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,7 +63,8 @@ export interface ICategory {
|
||||||
isExceed1MoreBurden: boolean | null,
|
isExceed1MoreBurden: boolean | null,
|
||||||
isExceedBothSocioBurdens: boolean | null,
|
isExceedBothSocioBurdens: boolean | null,
|
||||||
}
|
}
|
||||||
const AreaDetail = ({properties, hash}: IAreaDetailProps) => {
|
|
||||||
|
const AreaDetail = ({properties, hash, isCensusLayerSelected}: IAreaDetailProps) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
// console.log the properties of the census that is selected:
|
// console.log the properties of the census that is selected:
|
||||||
|
@ -74,6 +76,7 @@ const AreaDetail = ({properties, hash}: IAreaDetailProps) => {
|
||||||
const countyName = properties[constants.COUNTY_NAME] ? properties[constants.COUNTY_NAME] : "N/A";
|
const countyName = properties[constants.COUNTY_NAME] ? properties[constants.COUNTY_NAME] : "N/A";
|
||||||
const stateName = properties[constants.STATE_NAME] ? properties[constants.STATE_NAME] : "N/A";
|
const stateName = properties[constants.STATE_NAME] ? properties[constants.STATE_NAME] : "N/A";
|
||||||
const sidePanelState = properties[constants.SIDE_PANEL_STATE];
|
const sidePanelState = properties[constants.SIDE_PANEL_STATE];
|
||||||
|
const landAreaName = properties[constants.LAND_AREA_NAME];
|
||||||
|
|
||||||
const isCommunityFocus = score >= constants.SCORE_BOUNDARY_THRESHOLD;
|
const isCommunityFocus = score >= constants.SCORE_BOUNDARY_THRESHOLD;
|
||||||
|
|
||||||
|
@ -590,6 +593,41 @@ const AreaDetail = ({properties, hash}: IAreaDetailProps) => {
|
||||||
{/* Demographics */}
|
{/* Demographics */}
|
||||||
<TractDemographics />
|
<TractDemographics />
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
isCensusLayerSelected ? (
|
||||||
|
<>
|
||||||
|
{/* Census Info */}
|
||||||
|
<ul className={styles.censusRow}>
|
||||||
|
<li>
|
||||||
|
<span className={styles.censusLabel}>
|
||||||
|
{intl.formatMessage(EXPLORE_COPY.SIDE_PANEL_CBG_INFO.CENSUS_BLOCK_GROUP)}
|
||||||
|
</span>
|
||||||
|
<span className={styles.censusText}>{` ${blockGroup}`}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.censusLabel}>
|
||||||
|
{intl.formatMessage(EXPLORE_COPY.SIDE_PANEL_CBG_INFO.COUNTY)}
|
||||||
|
</span>
|
||||||
|
<span className={styles.censusText}>{` ${countyName}`}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.censusLabel}>
|
||||||
|
{properties[constants.SIDE_PANEL_STATE] !== constants.SIDE_PANEL_STATE_VALUES.NATION ?
|
||||||
|
intl.formatMessage(EXPLORE_COPY.SIDE_PANEL_CBG_INFO.TERRITORY) :
|
||||||
|
intl.formatMessage(EXPLORE_COPY.SIDE_PANEL_CBG_INFO.STATE)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span className={styles.censusText}>{` ${stateName}`}</span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={styles.censusLabel}>
|
||||||
|
{intl.formatMessage(EXPLORE_COPY.SIDE_PANEL_CBG_INFO.POPULATION)}
|
||||||
|
</span>
|
||||||
|
<span className={styles.censusText}>{` ${population.toLocaleString()}`}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
{/* Disadvantaged? */}
|
{/* Disadvantaged? */}
|
||||||
<div className={styles.categorization}>
|
<div className={styles.categorization}>
|
||||||
|
|
||||||
|
@ -644,11 +682,23 @@ const AreaDetail = ({properties, hash}: IAreaDetailProps) => {
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<ul className={styles.censusRow}>
|
||||||
|
<li>
|
||||||
|
<span className={styles.censusLabel}>
|
||||||
|
{intl.formatMessage(EXPLORE_COPY.SIDE_PANEL_TRIBAL_INFO.LAND_AREA_NAME)}
|
||||||
|
</span>
|
||||||
|
<span className={styles.censusText}>{` ${landAreaName}`}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
{/* All category accordions in this component */}
|
{/* All category accordions in this component */}
|
||||||
<Accordion multiselectable={true} items={categoryItems} />
|
{isCensusLayerSelected && <Accordion multiselectable={true} items={categoryItems} />}
|
||||||
|
|
||||||
{/* Methodology version */}
|
{/* Methodology version */}
|
||||||
<div className={styles.versionInfo}>
|
<div className={styles.versionInfo}>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {LocalizedComponent} from '../../../test/testHelpers';
|
||||||
|
|
||||||
import * as constants from '../../../data/constants';
|
import * as constants from '../../../data/constants';
|
||||||
|
|
||||||
|
// Todo: Update tests to take into account tribal layer selected
|
||||||
describe('rendering of the AreaDetail', () => {
|
describe('rendering of the AreaDetail', () => {
|
||||||
const properties = {
|
const properties = {
|
||||||
[constants.POVERTY_BELOW_100_PERCENTILE]: .12,
|
[constants.POVERTY_BELOW_100_PERCENTILE]: .12,
|
||||||
|
@ -27,7 +28,7 @@ describe('rendering of the AreaDetail', () => {
|
||||||
it('checks if indicators for NATION is present', () => {
|
it('checks if indicators for NATION is present', () => {
|
||||||
const {asFragment} = render(
|
const {asFragment} = render(
|
||||||
<LocalizedComponent>
|
<LocalizedComponent>
|
||||||
<AreaDetail properties={properties} hash={hash}/>
|
<AreaDetail properties={properties} hash={hash} isCensusLayerSelected={true}/>
|
||||||
</LocalizedComponent>,
|
</LocalizedComponent>,
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
@ -41,7 +42,7 @@ describe('rendering of the AreaDetail', () => {
|
||||||
|
|
||||||
const {asFragment} = render(
|
const {asFragment} = render(
|
||||||
<LocalizedComponent>
|
<LocalizedComponent>
|
||||||
<AreaDetail properties={propertiesPR} hash={hash}/>
|
<AreaDetail properties={propertiesPR} hash={hash} isCensusLayerSelected={true}/>
|
||||||
</LocalizedComponent>,
|
</LocalizedComponent>,
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
@ -59,7 +60,7 @@ describe('rendering of the AreaDetail', () => {
|
||||||
|
|
||||||
const {asFragment} = render(
|
const {asFragment} = render(
|
||||||
<LocalizedComponent>
|
<LocalizedComponent>
|
||||||
<AreaDetail properties={propertiesIA} hash={hash}/>
|
<AreaDetail properties={propertiesIA} hash={hash} isCensusLayerSelected={true}/>
|
||||||
</LocalizedComponent>,
|
</LocalizedComponent>,
|
||||||
);
|
);
|
||||||
expect(asFragment()).toMatchSnapshot();
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
|
|
@ -59,3 +59,13 @@
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 90vh;
|
height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will control the height of the map when the device
|
||||||
|
// width is less than desktop (1024px)
|
||||||
|
.j40Map {
|
||||||
|
@include at-media-max("desktop") {
|
||||||
|
height: 55vh;
|
||||||
|
@include u-margin-top(7); // Allow for tribal toggle to appear above map on screens < 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable valid-jsdoc */
|
/* eslint-disable valid-jsdoc */
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
// External Libs:
|
// External Libs:
|
||||||
import React, {useRef, useState, useMemo} from 'react';
|
import React, {useRef, useState} from 'react';
|
||||||
import {Map, MapboxGeoJSONFeature, LngLatBoundsLike} from 'maplibre-gl';
|
import {Map, MapboxGeoJSONFeature, LngLatBoundsLike} from 'maplibre-gl';
|
||||||
import ReactMapGL, {
|
import ReactMapGL, {
|
||||||
MapEvent,
|
MapEvent,
|
||||||
|
@ -12,7 +12,7 @@ import ReactMapGL, {
|
||||||
Popup,
|
Popup,
|
||||||
FlyToInterpolator,
|
FlyToInterpolator,
|
||||||
FullscreenControl,
|
FullscreenControl,
|
||||||
MapRef, Source, Layer} from 'react-map-gl';
|
MapRef} from 'react-map-gl';
|
||||||
import {useIntl} from 'gatsby-plugin-intl';
|
import {useIntl} from 'gatsby-plugin-intl';
|
||||||
import bbox from '@turf/bbox';
|
import bbox from '@turf/bbox';
|
||||||
import * as d3 from 'd3-ease';
|
import * as d3 from 'd3-ease';
|
||||||
|
@ -27,6 +27,9 @@ import {useFlags} from '../contexts/FlagContext';
|
||||||
import AreaDetail from './AreaDetail';
|
import AreaDetail from './AreaDetail';
|
||||||
import MapInfoPanel from './mapInfoPanel';
|
import MapInfoPanel from './mapInfoPanel';
|
||||||
import MapSearch from './MapSearch';
|
import MapSearch from './MapSearch';
|
||||||
|
import MapTractLayers from './MapTractLayers/MapTractLayers';
|
||||||
|
import MapTribalLayer from './MapTribalLayers/MapTribalLayers';
|
||||||
|
import LayerSelector from './LayerSelector';
|
||||||
import TerritoryFocusControl from './territoryFocusControl';
|
import TerritoryFocusControl from './territoryFocusControl';
|
||||||
import {getOSBaseMap} from '../data/getOSBaseMap';
|
import {getOSBaseMap} from '../data/getOSBaseMap';
|
||||||
|
|
||||||
|
@ -34,10 +37,8 @@ import {getOSBaseMap} from '../data/getOSBaseMap';
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||||
import * as constants from '../data/constants';
|
import * as constants from '../data/constants';
|
||||||
import * as styles from './J40Map.module.scss';
|
import * as styles from './J40Map.module.scss';
|
||||||
import * as COMMON_COPY from '../data/copy/common';
|
|
||||||
import * as EXPLORE_COPY from '../data/copy/explore';
|
import * as EXPLORE_COPY from '../data/copy/explore';
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
Cypress?: object;
|
Cypress?: object;
|
||||||
|
@ -57,55 +58,6 @@ export interface IDetailViewInterface {
|
||||||
properties: constants.J40Properties,
|
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('/');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This the main map component
|
|
||||||
*
|
|
||||||
* @param {IJ40Interface} location
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
const J40Map = ({location}: IJ40Interface) => {
|
const J40Map = ({location}: IJ40Interface) => {
|
||||||
/**
|
/**
|
||||||
* Initializes the zoom, and the map's center point (lat, lng) via the URL hash #{z}/{lat}/{long}
|
* Initializes the zoom, and the map's center point (lat, lng) via the URL hash #{z}/{lat}/{long}
|
||||||
|
@ -134,6 +86,11 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
const [transitionInProgress, setTransitionInProgress] = useState<boolean>(false);
|
const [transitionInProgress, setTransitionInProgress] = useState<boolean>(false);
|
||||||
const [geolocationInProgress, setGeolocationInProgress] = useState<boolean>(false);
|
const [geolocationInProgress, setGeolocationInProgress] = useState<boolean>(false);
|
||||||
const [isMobileMapState, setIsMobileMapState] = useState<boolean>(false);
|
const [isMobileMapState, setIsMobileMapState] = useState<boolean>(false);
|
||||||
|
const [censusSelected, setCensusSelected] = useState<boolean>(true);
|
||||||
|
|
||||||
|
// In order to detect that the layer has been toggled (between census and tribal),
|
||||||
|
// this state variable will hold that information
|
||||||
|
const [layerToggled, setLayerToggled] = useState<boolean>(false);
|
||||||
const {width: windowWidth} = useWindowSize();
|
const {width: windowWidth} = useWindowSize();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -153,7 +110,6 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const selectedFeatureId = (selectedFeature && selectedFeature.id) || '';
|
const selectedFeatureId = (selectedFeature && selectedFeature.id) || '';
|
||||||
const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]);
|
|
||||||
|
|
||||||
const zoomLatLngHash = mapRef.current?.getMap()._hash._getCurrentHash();
|
const zoomLatLngHash = mapRef.current?.getMap()._hash._getCurrentHash();
|
||||||
|
|
||||||
|
@ -219,13 +175,30 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This else clause will fire when the ID is null or empty. This is the case where the map is clicked
|
// This else clause will fire when the ID is null or empty. This is the case where the map is clicked
|
||||||
|
|
||||||
|
setLayerToggled(false);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const feature = event.features && event.features[0];
|
const feature = event.features && event.features[0];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given that Alaska has Points as their data type, we will not zoom into them when
|
||||||
|
* selected. In order to detect if a feature is a Point we will use Regex to determine
|
||||||
|
* the Alaska Point based on it's unique ID:
|
||||||
|
*
|
||||||
|
* E.g. {33FF6457-324C-4643-94E8-D543DD4339E0}
|
||||||
|
*
|
||||||
|
* The regex will test for any numeric, upper-case alpha with hyphens string enclosed
|
||||||
|
* in curly braces.
|
||||||
|
*/
|
||||||
|
const alaskaIDRegex = /\{[0-9,A-Z,-]+\}/g;
|
||||||
|
const isFeatureAlaskaPoint = alaskaIDRegex.test(feature.id);
|
||||||
|
|
||||||
if (feature) {
|
if (feature) {
|
||||||
// Get the current selected feature's bounding box:
|
// Get the current selected feature's bounding box:
|
||||||
const [minLng, minLat, maxLng, maxLat] = bbox(feature);
|
const [minLng, minLat, maxLng, maxLat] = bbox(feature);
|
||||||
|
|
||||||
|
|
||||||
// Set the selectedFeature ID
|
// Set the selectedFeature ID
|
||||||
if (feature.id !== selectedFeatureId) {
|
if (feature.id !== selectedFeatureId) {
|
||||||
setSelectedFeature(feature);
|
setSelectedFeature(feature);
|
||||||
|
@ -233,8 +206,9 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
setSelectedFeature(undefined);
|
setSelectedFeature(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Go to the newly selected feature
|
|
||||||
goToPlace([
|
// Go to the newly selected feature (as long as it's not an Alaska Point)
|
||||||
|
!isFeatureAlaskaPoint && goToPlace([
|
||||||
[minLng, minLat],
|
[minLng, minLat],
|
||||||
[maxLng, maxLat],
|
[maxLng, maxLat],
|
||||||
]);
|
]);
|
||||||
|
@ -376,6 +350,13 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
* Any component declarations outside the <ReactMapGL> component may be susceptible to this bug.
|
* Any component declarations outside the <ReactMapGL> component may be susceptible to this bug.
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
|
{/* This will allow to select between the census tract layer and the tribal lands layer */}
|
||||||
|
<LayerSelector
|
||||||
|
censusSelected={censusSelected}
|
||||||
|
setCensusSelected={setCensusSelected}
|
||||||
|
setLayerToggled={setLayerToggled}
|
||||||
|
/>
|
||||||
|
|
||||||
{/**
|
{/**
|
||||||
* The ReactMapGL component's props are grouped by the API's documentation. The component also has
|
* The ReactMapGL component's props are grouped by the API's documentation. The component also has
|
||||||
* some children.
|
* some children.
|
||||||
|
@ -390,7 +371,7 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
// ****** Map state props: ******
|
// ****** Map state props: ******
|
||||||
// http://visgl.github.io/react-map-gl/docs/api-reference/interactive-map#map-state
|
// http://visgl.github.io/react-map-gl/docs/api-reference/interactive-map#map-state
|
||||||
{...viewport}
|
{...viewport}
|
||||||
mapStyle={process.env.MAPBOX_STYLES_READ_TOKEN ? mapBoxBaseLayer : getOSBaseMap()}
|
mapStyle={process.env.MAPBOX_STYLES_READ_TOKEN ? mapBoxBaseLayer : getOSBaseMap(censusSelected)}
|
||||||
width="100%"
|
width="100%"
|
||||||
// Ajusting this height with a conditional statement will not render the map on staging.
|
// 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.
|
// The reason for this issue is unknown. Consider styling the parent container via SASS.
|
||||||
|
@ -404,7 +385,16 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
minZoom={constants.GLOBAL_MIN_ZOOM}
|
minZoom={constants.GLOBAL_MIN_ZOOM}
|
||||||
dragRotate={false}
|
dragRotate={false}
|
||||||
touchRotate={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,
|
||||||
|
constants.TRIBAL_ALASKA_POINTS_LAYER_ID,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ****** Callback props: ******
|
// ****** Callback props: ******
|
||||||
|
@ -418,98 +408,19 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
ref={mapRef}
|
ref={mapRef}
|
||||||
data-cy={'reactMapGL'}
|
data-cy={'reactMapGL'}
|
||||||
>
|
>
|
||||||
{/**
|
|
||||||
* Load all data sources and layers
|
|
||||||
*
|
|
||||||
* First the low zoom:
|
|
||||||
*/}
|
|
||||||
<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 */}
|
{/* Load either the Tribal layer or Census layer depending on the censusSelected state variable */}
|
||||||
<Layer
|
{
|
||||||
id={constants.LOW_ZOOM_LAYER_ID}
|
censusSelected ?
|
||||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
<MapTractLayers
|
||||||
filter={['>', constants.SCORE_PROPERTY_LOW, constants.SCORE_BOUNDARY_THRESHOLD]}
|
selectedFeature={selectedFeature}
|
||||||
type='fill'
|
selectedFeatureId={selectedFeatureId}
|
||||||
paint={{
|
/> :
|
||||||
'fill-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
<MapTribalLayer
|
||||||
'fill-opacity': constants.LOW_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY}}
|
selectedFeature={selectedFeature}
|
||||||
maxzoom={constants.GLOBAL_MAX_ZOOM_LOW}
|
selectedFeatureId={selectedFeatureId}
|
||||||
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>
|
|
||||||
|
|
||||||
{/* This is the first overlayed row on the map: Search and Geolocation */}
|
{/* This is the first overlayed row on the map: Search and Geolocation */}
|
||||||
<div className={styles.mapHeaderRow}>
|
<div className={styles.mapHeaderRow}>
|
||||||
|
@ -562,7 +473,11 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
onClose={setDetailViewData}
|
onClose={setDetailViewData}
|
||||||
captureScroll={true}
|
captureScroll={true}
|
||||||
>
|
>
|
||||||
<AreaDetail properties={detailViewData.properties} hash={zoomLatLngHash}/>
|
<AreaDetail
|
||||||
|
properties={detailViewData.properties}
|
||||||
|
hash={zoomLatLngHash}
|
||||||
|
isCensusLayerSelected={censusSelected}
|
||||||
|
/>
|
||||||
</Popup>
|
</Popup>
|
||||||
)}
|
)}
|
||||||
{'fs' in flags ? <FullscreenControl className={styles.fullscreenControl}/> :'' }
|
{'fs' in flags ? <FullscreenControl className={styles.fullscreenControl}/> :'' }
|
||||||
|
@ -576,6 +491,8 @@ const J40Map = ({location}: IJ40Interface) => {
|
||||||
featureProperties={detailViewData?.properties}
|
featureProperties={detailViewData?.properties}
|
||||||
selectedFeatureId={selectedFeature?.id}
|
selectedFeatureId={selectedFeature?.id}
|
||||||
hash={zoomLatLngHash}
|
hash={zoomLatLngHash}
|
||||||
|
isCensusLayerSelected={censusSelected}
|
||||||
|
layerToggled={layerToggled}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
@use '../../styles/design-system.scss' as *;
|
||||||
|
|
||||||
|
.layerSelectorContainer {
|
||||||
|
background-color: white;
|
||||||
|
@include u-padding-left(1);
|
||||||
|
@include u-padding-right(1);
|
||||||
|
@include u-padding-top(1);
|
||||||
|
@include u-padding-bottom(1);
|
||||||
|
width: fit-content;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
// styles for mobile-lg (480px) and greater widths,
|
||||||
|
@include at-media('mobile-lg') {
|
||||||
|
position: absolute;
|
||||||
|
top: units(2.5);
|
||||||
|
left: 62%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// styles for less than mobile-lg (480px)
|
||||||
|
position: absolute;
|
||||||
|
top: -5.2rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
12
client/src/components/LayerSelector/LayerSelector.module.scss.d.ts
vendored
Normal file
12
client/src/components/LayerSelector/LayerSelector.module.scss.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
declare namespace LayerSelectorNamespace {
|
||||||
|
export interface ILayerSelectorScss {
|
||||||
|
layerSelectorContainer: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const LayerSelectorScssModule: LayerSelectorNamespace.ILayerSelectorScss & {
|
||||||
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
|
locals: LayerSelectorNamespace.ILayerSelectorScss;
|
||||||
|
};
|
||||||
|
|
||||||
|
export = LayerSelectorScssModule;
|
24
client/src/components/LayerSelector/LayerSelector.test.tsx
Normal file
24
client/src/components/LayerSelector/LayerSelector.test.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import {render} from '@testing-library/react';
|
||||||
|
import {LocalizedComponent} from '../../test/testHelpers';
|
||||||
|
import LayerSelector from './LayerSelector';
|
||||||
|
|
||||||
|
describe('rendering of the LayerSelector', () => {
|
||||||
|
it('checks if component renders census tracts selected', () => {
|
||||||
|
const {asFragment} = render(
|
||||||
|
<LocalizedComponent>
|
||||||
|
<LayerSelector censusSelected={true} setCensusSelected={() => {}} setLayerToggled={() =>{}}/>
|
||||||
|
</LocalizedComponent>,
|
||||||
|
);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('checks if component renders tribal selected', () => {
|
||||||
|
const {asFragment} = render(
|
||||||
|
<LocalizedComponent>
|
||||||
|
<LayerSelector censusSelected={false} setCensusSelected={() => {}} setLayerToggled={()=> {}}/>
|
||||||
|
</LocalizedComponent>,
|
||||||
|
);
|
||||||
|
expect(asFragment()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
74
client/src/components/LayerSelector/LayerSelector.tsx
Normal file
74
client/src/components/LayerSelector/LayerSelector.tsx
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
|
||||||
|
import * as styles from './LayerSelector.module.scss';
|
||||||
|
import * as EXPLORE_COPY from '../../data/copy/explore';
|
||||||
|
|
||||||
|
interface ILayerSelector {
|
||||||
|
censusSelected: boolean,
|
||||||
|
setCensusSelected: Dispatch<boolean>,
|
||||||
|
setLayerToggled: Dispatch<boolean>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const LayerSelector = ({censusSelected, setCensusSelected, setLayerToggled}:ILayerSelector) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At compile-time, the width/height returned by useWindowSize will be X. When the client requests the
|
||||||
|
* app on run-time from CDN, and the app hydrates, reconcilation no longer occurs and the client is forced
|
||||||
|
* to use X.
|
||||||
|
*
|
||||||
|
* To avoid this, we set the text as a state variable. We also create a useEffect that updates
|
||||||
|
* that state whenenver the width changes.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const {width, height} = useWindowSize();
|
||||||
|
const [censusText, setCensusText]= useState(EXPLORE_COPY.MAP.CENSUS_TRACT_LONG);
|
||||||
|
const [tribalText, setTribalText]= useState(EXPLORE_COPY.MAP.TRIBAL_LANDS_LONG);
|
||||||
|
|
||||||
|
useEffect( () => {
|
||||||
|
if (width > height) {
|
||||||
|
setCensusText(EXPLORE_COPY.MAP.CENSUS_TRACT_LONG);
|
||||||
|
setTribalText(EXPLORE_COPY.MAP.TRIBAL_LANDS_LONG);
|
||||||
|
} else {
|
||||||
|
setCensusText(EXPLORE_COPY.MAP.CENSUS_TRACT_SHORT);
|
||||||
|
setTribalText(EXPLORE_COPY.MAP.TRIBAL_LANDS_SHORT);
|
||||||
|
}
|
||||||
|
}, [width]);
|
||||||
|
|
||||||
|
// Anytime the censusSelected state variable changes, set the LayerToggled state
|
||||||
|
// variable
|
||||||
|
useEffect( () => {
|
||||||
|
setLayerToggled(true);
|
||||||
|
}, [censusSelected]);
|
||||||
|
|
||||||
|
|
||||||
|
// Handles toggle of tracts and tribal layer selection
|
||||||
|
const buttonClickHandler = (event) => {
|
||||||
|
if (event.target.id === 'census' && !censusSelected) {
|
||||||
|
setCensusSelected(true);
|
||||||
|
} else if (event.target.id === 'tribal' && censusSelected) {
|
||||||
|
setCensusSelected(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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)}>
|
||||||
|
{intl.formatMessage(censusText)}
|
||||||
|
</Button>
|
||||||
|
<Button id="tribal" type="button" outline={censusSelected} onClick={(e) => buttonClickHandler(e)}>
|
||||||
|
{intl.formatMessage(tribalText)}
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LayerSelector;
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`rendering of the LayerSelector checks if component renders census tracts 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"
|
||||||
|
data-testid="button"
|
||||||
|
id="census"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Census Tracts
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
class="usa-button-group__item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="usa-button usa-button--outline"
|
||||||
|
data-testid="button"
|
||||||
|
id="tribal"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Tribal Lands
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</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>
|
||||||
|
`;
|
3
client/src/components/LayerSelector/index.tsx
Normal file
3
client/src/components/LayerSelector/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import LayerSelector from './LayerSelector';
|
||||||
|
|
||||||
|
export default LayerSelector;
|
187
client/src/components/MapTractLayers/MapTractLayers.tsx
Normal file
187
client/src/components/MapTractLayers/MapTractLayers.tsx
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
import React, {useMemo} from 'react';
|
||||||
|
import {Source, Layer} from 'react-map-gl';
|
||||||
|
import {AnyLayer} from 'mapbox-gl';
|
||||||
|
|
||||||
|
// Contexts:
|
||||||
|
import {useFlags} from '../../contexts/FlagContext';
|
||||||
|
|
||||||
|
import * as constants from '../../data/constants';
|
||||||
|
import * as COMMON_COPY from '../../data/copy/common';
|
||||||
|
|
||||||
|
interface IMapTractLayers {
|
||||||
|
selectedFeatureId: AnyLayer,
|
||||||
|
selectedFeature: AnyLayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component will return the appropriate source and layers for the census layer on the
|
||||||
|
* map.
|
||||||
|
*
|
||||||
|
* There are two use cases here, eg, when the MapBox token is or isn't provided. When the token
|
||||||
|
* is not provided, the open-source map will be rendered. When the open-source map is rendered
|
||||||
|
* only the interactive layers are returned from this component. The reason being is that the
|
||||||
|
* other layers are supplied by he getOSBaseMap function.
|
||||||
|
*
|
||||||
|
* @param {AnyLayer} selectedFeatureId
|
||||||
|
* @param {AnyLayer} selectedFeature
|
||||||
|
* @return {Style}
|
||||||
|
*/
|
||||||
|
const MapTractLayers = ({
|
||||||
|
selectedFeatureId,
|
||||||
|
selectedFeature,
|
||||||
|
}: IMapTractLayers) => {
|
||||||
|
const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]);
|
||||||
|
|
||||||
|
return process.env.MAPBOX_STYLES_READ_TOKEN ? (
|
||||||
|
|
||||||
|
// In this case the MapBox token is found and All source(s)/layer(s) are returned.
|
||||||
|
<>
|
||||||
|
<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 (static) - 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 (static) - 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 (static) - 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 (static) - 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}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Source>
|
||||||
|
</>
|
||||||
|
): (
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this case the MapBox token is NOT found and ONLY interactive source(s)/layer(s) are returned
|
||||||
|
* In this case, the other layers (non-interactive) are provided by getOSBaseMap
|
||||||
|
*/
|
||||||
|
<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 (dynamic) - 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;
|
144
client/src/components/MapTribalLayers/MapTribalLayers.tsx
Normal file
144
client/src/components/MapTribalLayers/MapTribalLayers.tsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import React, {useMemo} from 'react';
|
||||||
|
import {Source, Layer} from 'react-map-gl';
|
||||||
|
import {AnyLayer} from 'mapbox-gl';
|
||||||
|
|
||||||
|
import * as constants from '../../data/constants';
|
||||||
|
|
||||||
|
interface IMapTribalLayers {
|
||||||
|
selectedFeatureId: AnyLayer,
|
||||||
|
selectedFeature: AnyLayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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('/');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component will return the appropriate source and layers for the tribal layer on the
|
||||||
|
* map.
|
||||||
|
*
|
||||||
|
* There are two use cases here, eg, when the MapBox token is or isn't provided. When the token
|
||||||
|
* is not provided, the open-source map will be rendered. When the open-source map is rendered
|
||||||
|
* only the interactive layers are returned from this component. The reason being is that the
|
||||||
|
* other layers are supplied by he getOSBaseMap function.
|
||||||
|
*
|
||||||
|
* @param {AnyLayer} selectedFeatureId
|
||||||
|
* @param {AnyLayer} selectedFeature
|
||||||
|
* @return {Style}
|
||||||
|
*/
|
||||||
|
const MapTribalLayer = ({
|
||||||
|
selectedFeatureId,
|
||||||
|
selectedFeature,
|
||||||
|
}: IMapTribalLayers) => {
|
||||||
|
const tribalSelectionFilter = useMemo(() => ['in', constants.TRIBAL_ID, selectedFeatureId], [selectedFeature]);
|
||||||
|
|
||||||
|
return process.env.MAPBOX_STYLES_READ_TOKEN ? (
|
||||||
|
|
||||||
|
// In this case the MapBox token is found and ALL source(s)/layer(s) are returned.
|
||||||
|
<Source
|
||||||
|
id={constants.TRIBAL_SOURCE_NAME}
|
||||||
|
type="vector"
|
||||||
|
promoteId={constants.TRIBAL_ID}
|
||||||
|
tiles={[tribalURL()]}
|
||||||
|
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||||
|
maxzoom={constants.TRIBAL_MAX_ZOOM}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Tribal layer */}
|
||||||
|
<Layer
|
||||||
|
id={constants.TRIBAL_LAYER_ID}
|
||||||
|
source-layer={constants.TRIBAL_SOURCE_LAYER}
|
||||||
|
type='fill'
|
||||||
|
paint={{
|
||||||
|
'fill-color': constants.PRIORITIZED_FEATURE_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.TRIBAL_SOURCE_LAYER}
|
||||||
|
type='line'
|
||||||
|
paint={{
|
||||||
|
'line-color': constants.FEATURE_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_TRIBAL_FEATURE_BORDER_LAYER_ID}
|
||||||
|
source-layer={constants.TRIBAL_SOURCE_LAYER}
|
||||||
|
filter={tribalSelectionFilter}
|
||||||
|
type='line'
|
||||||
|
paint={{
|
||||||
|
'line-color': constants.SELECTED_FEATURE_BORDER_COLOR,
|
||||||
|
'line-width': constants.SELECTED_FEATURE_BORDER_WIDTH,
|
||||||
|
}}
|
||||||
|
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Alaska layer */}
|
||||||
|
{/* // Todo: limit zoom in amount */}
|
||||||
|
<Layer
|
||||||
|
id={constants.TRIBAL_ALASKA_POINTS_LAYER_ID}
|
||||||
|
source-layer={constants.TRIBAL_SOURCE_LAYER}
|
||||||
|
filter={['==', ['geometry-type'], 'Point']}
|
||||||
|
type='circle'
|
||||||
|
paint={{
|
||||||
|
'circle-radius': constants.TRIBAL_ALASKA_CIRCLE_RADIUS,
|
||||||
|
'circle-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||||
|
}}
|
||||||
|
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||||
|
maxzoom={constants.TRIBAL_MAX_ZOOM}
|
||||||
|
/>
|
||||||
|
</Source>
|
||||||
|
) : (
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In this case the MapBox token is NOT found and ONLY INTERACTIVE source(s)/layer(s) are returned.
|
||||||
|
* In this case, the other layers (non-interactive) are provided by getOSBaseMap
|
||||||
|
*/
|
||||||
|
<Source
|
||||||
|
id={constants.TRIBAL_SOURCE_NAME}
|
||||||
|
type="vector"
|
||||||
|
promoteId={constants.TRIBAL_ID}
|
||||||
|
tiles={[tribalURL()]}
|
||||||
|
minzoom={constants.TRIBAL_MIN_ZOOM}
|
||||||
|
maxzoom={constants.TRIBAL_MAX_ZOOM}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Tribal layer - border styling around the selected feature */}
|
||||||
|
<Layer
|
||||||
|
id={constants.SELECTED_TRIBAL_FEATURE_BORDER_LAYER_ID}
|
||||||
|
source-layer={constants.TRIBAL_SOURCE_LAYER}
|
||||||
|
filter={tribalSelectionFilter}
|
||||||
|
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/MapTribalLayers/index.tsx
Normal file
3
client/src/components/MapTribalLayers/index.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import MapTribalLayers from './MapTribalLayers';
|
||||||
|
|
||||||
|
export default MapTribalLayers;
|
|
@ -7,13 +7,33 @@ interface IMapInfoPanelProps {
|
||||||
featureProperties: { [key:string]: string | number } | undefined,
|
featureProperties: { [key:string]: string | number } | undefined,
|
||||||
selectedFeatureId: string | number | undefined
|
selectedFeatureId: string | number | undefined
|
||||||
hash: string[],
|
hash: string[],
|
||||||
|
isCensusLayerSelected: boolean,
|
||||||
|
layerToggled: boolean, // indicates if census layer or tribal layer has been toggled
|
||||||
}
|
}
|
||||||
|
|
||||||
const MapInfoPanel = ({className, featureProperties, selectedFeatureId, hash}:IMapInfoPanelProps) => {
|
const MapInfoPanel = ({
|
||||||
|
className,
|
||||||
|
featureProperties,
|
||||||
|
selectedFeatureId,
|
||||||
|
hash,
|
||||||
|
isCensusLayerSelected,
|
||||||
|
layerToggled,
|
||||||
|
}:IMapInfoPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={className} >
|
<div className={className} >
|
||||||
{(featureProperties && selectedFeatureId ) ?
|
{/* The tertiary conditional statement below will control the side panel state. Currently
|
||||||
<AreaDetail properties={featureProperties} hash={hash}/> :
|
there are two states, namely showing the AreaDetail or SidePanelInfo. When a feature
|
||||||
|
is selected, on - for example - the census tract layer, and if the Tribal Layer is the selected
|
||||||
|
the Side Panel should revert back to the SidePanelInfo.
|
||||||
|
|
||||||
|
A new boolean called layerToggle captures that a layer has been selected and to render
|
||||||
|
the SidePanelInfo component */}
|
||||||
|
{(featureProperties && selectedFeatureId && !layerToggled) ?
|
||||||
|
<AreaDetail
|
||||||
|
properties={featureProperties}
|
||||||
|
hash={hash}
|
||||||
|
isCensusLayerSelected={isCensusLayerSelected}
|
||||||
|
/> :
|
||||||
<SidePanelInfo />
|
<SidePanelInfo />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,6 +25,10 @@ export type J40Properties = { [key: string]: any };
|
||||||
|
|
||||||
// ****** SIDE PANEL BACKEND SIGNALS ***********
|
// ****** SIDE PANEL BACKEND SIGNALS ***********
|
||||||
|
|
||||||
|
// Tribal signals
|
||||||
|
export const TRIBAL_ID = 'tribalId';
|
||||||
|
export const LAND_AREA_NAME = 'landAreaName';
|
||||||
|
|
||||||
// Set the threshold percentile used by most indicators in the side panel
|
// Set the threshold percentile used by most indicators in the side panel
|
||||||
export const DEFAULT_THRESHOLD_PERCENTILE = 90;
|
export const DEFAULT_THRESHOLD_PERCENTILE = 90;
|
||||||
|
|
||||||
|
@ -187,15 +191,20 @@ export const ISLAND_AREA_LOW_HS_EDU = 'IALHE';
|
||||||
export const BASE_MAP_SOURCE_NAME = 'base-map-source-name';
|
export const BASE_MAP_SOURCE_NAME = 'base-map-source-name';
|
||||||
export const HIGH_ZOOM_SOURCE_NAME = 'high-zoom-source-name';
|
export const HIGH_ZOOM_SOURCE_NAME = 'high-zoom-source-name';
|
||||||
export const LOW_ZOOM_SOURCE_NAME = 'low-zoom-source-name';
|
export const LOW_ZOOM_SOURCE_NAME = 'low-zoom-source-name';
|
||||||
|
export const TRIBAL_SOURCE_NAME = 'tribal-source-name';
|
||||||
|
|
||||||
// Layer ID constants
|
// Layer ID constants
|
||||||
export const SCORE_SOURCE_LAYER = 'blocks'; // The name of the layer within the tiles that contains the score
|
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 BASE_MAP_LAYER_ID = 'base-map-layer-id';
|
||||||
export const HIGH_ZOOM_LAYER_ID = 'high-zoom-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 PRIORITIZED_HIGH_ZOOM_LAYER_ID = 'prioritized-high-zoom-layer-id';
|
||||||
export const LOW_ZOOM_LAYER_ID = 'low-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 FEATURE_BORDER_LAYER_ID = 'feature-border-layer-id';
|
||||||
export const SELECTED_FEATURE_BORDER_LAYER_ID = 'selected-feature-border-layer-id';
|
export const SELECTED_FEATURE_BORDER_LAYER_ID = 'selected-feature-border-layer-id';
|
||||||
|
export const TRIBAL_LAYER_ID = 'tribal-layer-id';
|
||||||
|
export const SELECTED_TRIBAL_FEATURE_BORDER_LAYER_ID = 'selected-feature-tribal-border-layer-id';
|
||||||
|
export const TRIBAL_ALASKA_POINTS_LAYER_ID = 'tribal-alaska-points-layer-id';
|
||||||
|
|
||||||
// Used in layer filters:
|
// Used in layer filters:
|
||||||
export const SCORE_PROPERTY_LOW = 'M_SCORE';
|
export const SCORE_PROPERTY_LOW = 'M_SCORE';
|
||||||
|
@ -213,21 +222,31 @@ export const GLOBAL_MAX_ZOOM_HIGH = 11;
|
||||||
|
|
||||||
export const GLOBAL_MIN_ZOOM_FEATURE_BORDER = 5;
|
export const GLOBAL_MIN_ZOOM_FEATURE_BORDER = 5;
|
||||||
export const GLOBAL_MAX_ZOOM_FEATURE_BORDER = 22;
|
export const GLOBAL_MAX_ZOOM_FEATURE_BORDER = 22;
|
||||||
|
export const TRIBAL_MIN_ZOOM = 3;
|
||||||
|
export const TRIBAL_MAX_ZOOM = 22;
|
||||||
|
|
||||||
// Opacity
|
// Opacity
|
||||||
export const FEATURE_BORDER_OPACITY = 0.5;
|
export const FEATURE_BORDER_OPACITY = 0.5;
|
||||||
export const HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY = 0.3;
|
export const HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY = 0.3;
|
||||||
export const LOW_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY = 0.6;
|
export const LOW_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY = 0.6;
|
||||||
export const NON_PRIORITIZED_FEATURE_FILL_OPACITY = 0;
|
export const NON_PRIORITIZED_FEATURE_FILL_OPACITY = 0;
|
||||||
|
export const TRIBAL_FEATURE_FILL_OPACITY = 0.3;
|
||||||
|
|
||||||
// Colors
|
// Colors
|
||||||
export const FEATURE_BORDER_COLOR = '#4EA5CF';
|
export const FEATURE_BORDER_COLOR = '#4EA5CF';
|
||||||
export const SELECTED_FEATURE_BORDER_COLOR = '#1A4480';
|
export const SELECTED_FEATURE_BORDER_COLOR = '#1A4480';
|
||||||
export const PRIORITIZED_FEATURE_FILL_COLOR = '#768FB3';
|
export const PRIORITIZED_FEATURE_FILL_COLOR = '#768FB3';
|
||||||
|
|
||||||
|
export const TRIBAL_BORDER_COLOR = '##4EA5CF';
|
||||||
|
export const SELECTED_TRIBAL_BORDER_COLOR = '#1A4480';
|
||||||
|
export const TRIBAL_FILL_COLOR = '#768FB3';
|
||||||
|
export const TRIBAL_ALASKA_CIRCLE_FILL_COLOR = '#768FB3';
|
||||||
|
export const TRIBAL_ALASKA_CIRCLE_RADIUS = 5;
|
||||||
|
|
||||||
// Widths
|
// Widths
|
||||||
export const FEATURE_BORDER_WIDTH = 0.8;
|
export const FEATURE_BORDER_WIDTH = 0.8;
|
||||||
export const SELECTED_FEATURE_BORDER_WIDTH = 5.0;
|
export const SELECTED_FEATURE_BORDER_WIDTH = 5.0;
|
||||||
|
export const ALAKSA_POINTS_STROKE_WIDTH = 1.0;
|
||||||
|
|
||||||
// Bounds - these bounds can be obtained by using the getCurrentMapBoundingBox() function in the map
|
// Bounds - these bounds can be obtained by using the getCurrentMapBoundingBox() function in the map
|
||||||
export const GLOBAL_MAX_BOUNDS: LngLatBoundsLike = [
|
export const GLOBAL_MAX_BOUNDS: LngLatBoundsLike = [
|
||||||
|
|
|
@ -184,6 +184,26 @@ export const MAP = defineMessages({
|
||||||
// defaultMessage: 'Geolocation locked',
|
// defaultMessage: 'Geolocation locked',
|
||||||
// description: 'On the explore the map page, on the map, this is the message above the gelocation icon that geolocation is locked.',
|
// description: 'On the explore the map page, on the map, this is the message above the gelocation icon that geolocation is locked.',
|
||||||
// },
|
// },
|
||||||
|
CENSUS_TRACT_LONG: {
|
||||||
|
id: 'explore.map.page.map.layer.selector.tracts.long',
|
||||||
|
defaultMessage: 'Census Tracts',
|
||||||
|
description: 'On the explore the map page, on the map, the full name indicating Census Tracts',
|
||||||
|
},
|
||||||
|
CENSUS_TRACT_SHORT: {
|
||||||
|
id: 'explore.map.page.map.layer.selector.tracts.short',
|
||||||
|
defaultMessage: 'Tracts',
|
||||||
|
description: 'On the explore the map page, on the map, the short name indicating Census Tracts',
|
||||||
|
},
|
||||||
|
TRIBAL_LANDS_LONG: {
|
||||||
|
id: 'explore.map.page.map.layer.selector.tribal.long',
|
||||||
|
defaultMessage: 'Tribal Lands',
|
||||||
|
description: 'On the explore the map page, on the map, the full name indicating Tribal Lands',
|
||||||
|
},
|
||||||
|
TRIBAL_LANDS_SHORT: {
|
||||||
|
id: 'explore.map.page.map.layer.selector.tracts.short',
|
||||||
|
defaultMessage: 'Tribal',
|
||||||
|
description: 'On the explore the map page, on the map, the short name indicating Tribal Lands',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -311,6 +331,15 @@ export const SIDE_PANEL_CBG_INFO = defineMessages({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SIDE_PANEL_TRIBAL_INFO = defineMessages({
|
||||||
|
LAND_AREA_NAME: {
|
||||||
|
id: 'explore.map.page.side.panel.tribalInfo.landAreaName',
|
||||||
|
defaultMessage: 'Land Area Name:',
|
||||||
|
description: `Navigate to the explore the map page. Click on Tribal Lands, when the map is in view,
|
||||||
|
click on the map. The side panel will show the land area name of the feature selected`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const COMMUNITY = {
|
export const COMMUNITY = {
|
||||||
OF_FOCUS: <FormattedMessage
|
OF_FOCUS: <FormattedMessage
|
||||||
id={'explore.map.page.side.panel.community.of.focus'}
|
id={'explore.map.page.side.panel.community.of.focus'}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import {Style} from 'maplibre-gl';
|
import {Style} from 'maplibre-gl';
|
||||||
import * as constants from '../data/constants';
|
|
||||||
import {featureURLForTilesetName} from '../components/J40Map';
|
|
||||||
|
|
||||||
// *********** BASE MAP SOURCES ***************
|
import {featureURLForTilesetName} from '../components/MapTractLayers/MapTractLayers';
|
||||||
|
import {tribalURL} from '../components/MapTribalLayers/MapTribalLayers';
|
||||||
|
|
||||||
|
import * as constants from '../data/constants';
|
||||||
|
|
||||||
|
// *********** OPEN SOURCE BASE MAP CONSTANTS ***************
|
||||||
const imageSuffix = constants.isMobile ? '' : '@2x';
|
const imageSuffix = constants.isMobile ? '' : '@2x';
|
||||||
|
|
||||||
// Original "light" Base layer
|
// Original "light" Base layer
|
||||||
|
@ -23,10 +26,26 @@ const cartoLightBaseLayer = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Utility function to get OpenSource base maps that are in accordance to JSON spec of MapBox
|
// *********** OPEN SOURCE STATIC MAP STYLES ***************
|
||||||
// https://docs.mapbox.com/mapbox-gl-js/style-spec/
|
/**
|
||||||
export const getOSBaseMap = () : Style => {
|
* This function will be called when there is no MapBox token found. This function will
|
||||||
return {
|
* return the open source base map along with styles for the chosen source. We currently
|
||||||
|
* have two sources, either the census tracts or the tribal layer.
|
||||||
|
* *
|
||||||
|
* This function returns a Style in accordance to JSON spec of MapBox
|
||||||
|
* https://docs.mapbox.com/mapbox-gl-js/style-spec/
|
||||||
|
*
|
||||||
|
* @param {boolean} censusSelected
|
||||||
|
* @return {Style}
|
||||||
|
*/
|
||||||
|
export const getOSBaseMap = (
|
||||||
|
censusSelected: boolean,
|
||||||
|
) : Style => {
|
||||||
|
return !censusSelected ? {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tribal Source
|
||||||
|
*/
|
||||||
'version': 8,
|
'version': 8,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,6 +63,111 @@ export const getOSBaseMap = () : Style => {
|
||||||
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
'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,
|
||||||
|
},
|
||||||
|
|
||||||
|
// The labels source:
|
||||||
|
'labels': {
|
||||||
|
'type': 'raster',
|
||||||
|
'tiles': cartoLightBaseLayer.labelsOnly,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||||
|
'fill-opacity': constants.HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||||
|
},
|
||||||
|
'minzoom': constants.TRIBAL_MIN_ZOOM,
|
||||||
|
'maxzoom': constants.TRIBAL_MAX_ZOOM,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tribal layer - controls the border between features
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
'id': constants.FEATURE_BORDER_LAYER_ID,
|
||||||
|
'source': constants.TRIBAL_SOURCE_NAME,
|
||||||
|
'source-layer': constants.TRIBAL_SOURCE_LAYER,
|
||||||
|
'type': 'line',
|
||||||
|
'paint': {
|
||||||
|
'line-color': constants.FEATURE_BORDER_COLOR,
|
||||||
|
'line-width': constants.FEATURE_BORDER_WIDTH,
|
||||||
|
'line-opacity': constants.FEATURE_BORDER_OPACITY},
|
||||||
|
'minzoom': constants.TRIBAL_MIN_ZOOM,
|
||||||
|
'maxzoom': constants.TRIBAL_MAX_ZOOM,
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alaska layer
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
'id': constants.TRIBAL_ALASKA_POINTS_LAYER_ID,
|
||||||
|
'source': constants.TRIBAL_SOURCE_NAME,
|
||||||
|
'source-layer': constants.TRIBAL_SOURCE_LAYER,
|
||||||
|
'type': 'circle',
|
||||||
|
'filter': ['==', ['geometry-type'], 'Point'],
|
||||||
|
'paint': {
|
||||||
|
'circle-radius': constants.TRIBAL_ALASKA_CIRCLE_RADIUS,
|
||||||
|
'circle-color': constants.PRIORITIZED_FEATURE_FILL_COLOR,
|
||||||
|
'circle-opacity': constants.HIGH_ZOOM_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||||
|
'circle-stroke-color': constants.FEATURE_BORDER_COLOR,
|
||||||
|
'circle-stroke-width': constants.ALAKSA_POINTS_STROKE_WIDTH,
|
||||||
|
'circle-stroke-opacity': constants.FEATURE_BORDER_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:
|
// The High zoom source:
|
||||||
[constants.HIGH_ZOOM_SOURCE_NAME]: {
|
[constants.HIGH_ZOOM_SOURCE_NAME]: {
|
||||||
// It is only shown at high zoom levels to avoid performance issues at lower zooms
|
// It is only shown at high zoom levels to avoid performance issues at lower zooms
|
||||||
|
@ -92,55 +216,19 @@ export const getOSBaseMap = () : Style => {
|
||||||
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
// A layer for labels only
|
||||||
* High zoom layer - non-prioritized features only
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
'id': constants.HIGH_ZOOM_LAYER_ID,
|
'id': 'labels-only-layer',
|
||||||
'source': constants.HIGH_ZOOM_SOURCE_NAME,
|
'source': 'labels',
|
||||||
'source-layer': constants.SCORE_SOURCE_LAYER,
|
'type': 'raster',
|
||||||
/**
|
'layout': {
|
||||||
* This shows features where the high score < score boundary threshold.
|
'visibility': 'visible',
|
||||||
* In other words, this filter out prioritized features
|
|
||||||
*/
|
|
||||||
'filter': ['all',
|
|
||||||
['<', 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,
|
'minzoom': constants.GLOBAL_MIN_ZOOM,
|
||||||
|
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
// Low zoom layer (static) - prioritized features only
|
||||||
* High zoom layer - prioritized features only
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
'id': constants.PRIORITIZED_HIGH_ZOOM_LAYER_ID,
|
|
||||||
'source': constants.HIGH_ZOOM_SOURCE_NAME,
|
|
||||||
'source-layer': constants.SCORE_SOURCE_LAYER,
|
|
||||||
/**
|
|
||||||
* This shows features where the high score > score boundary threshold.
|
|
||||||
* In other words, this filter out non-prioritized features
|
|
||||||
*/
|
|
||||||
'filter': ['all',
|
|
||||||
['>', 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,
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Low zoom layer - prioritized features only
|
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
'id': constants.LOW_ZOOM_LAYER_ID,
|
'id': constants.LOW_ZOOM_LAYER_ID,
|
||||||
'source': constants.LOW_ZOOM_SOURCE_NAME,
|
'source': constants.LOW_ZOOM_SOURCE_NAME,
|
||||||
|
@ -162,16 +250,60 @@ export const getOSBaseMap = () : Style => {
|
||||||
'maxzoom': constants.GLOBAL_MAX_ZOOM_LOW,
|
'maxzoom': constants.GLOBAL_MAX_ZOOM_LOW,
|
||||||
},
|
},
|
||||||
|
|
||||||
// A layer for labels only
|
// High zoom layer (static) - non-prioritized features only
|
||||||
{
|
{
|
||||||
'id': 'labels-only-layer',
|
'id': constants.HIGH_ZOOM_LAYER_ID,
|
||||||
'source': 'labels',
|
'source': constants.HIGH_ZOOM_SOURCE_NAME,
|
||||||
'type': 'raster',
|
'source-layer': constants.SCORE_SOURCE_LAYER,
|
||||||
'layout': {
|
/**
|
||||||
'visibility': 'visible',
|
* This shows features where the high score < score boundary threshold.
|
||||||
|
* In other words, this filter out prioritized features
|
||||||
|
*/
|
||||||
|
'filter': ['all',
|
||||||
|
['<', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD],
|
||||||
|
],
|
||||||
|
|
||||||
|
'type': 'fill',
|
||||||
|
'paint': {
|
||||||
|
'fill-opacity': constants.NON_PRIORITIZED_FEATURE_FILL_OPACITY,
|
||||||
},
|
},
|
||||||
'minzoom': constants.GLOBAL_MIN_ZOOM,
|
'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
|
||||||
'maxzoom': constants.GLOBAL_MAX_ZOOM,
|
},
|
||||||
|
|
||||||
|
// High zoom layer (static) - prioritized features only
|
||||||
|
{
|
||||||
|
'id': constants.PRIORITIZED_HIGH_ZOOM_LAYER_ID,
|
||||||
|
'source': constants.HIGH_ZOOM_SOURCE_NAME,
|
||||||
|
'source-layer': constants.SCORE_SOURCE_LAYER,
|
||||||
|
/**
|
||||||
|
* This shows features where the high score > score boundary threshold.
|
||||||
|
* In other words, this filter out non-prioritized features
|
||||||
|
*/
|
||||||
|
'filter': ['all',
|
||||||
|
['>', 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 (static) - controls the border between features
|
||||||
|
{
|
||||||
|
'id': constants.FEATURE_BORDER_LAYER_ID,
|
||||||
|
'source': constants.HIGH_ZOOM_SOURCE_NAME,
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
'minzoom': constants.GLOBAL_MIN_ZOOM_FEATURE_BORDER,
|
||||||
|
'maxzoom': constants.GLOBAL_MAX_ZOOM_FEATURE_BORDER,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -351,6 +351,22 @@
|
||||||
"defaultMessage": "Finding location...",
|
"defaultMessage": "Finding location...",
|
||||||
"description": "On the explore the map page, on the map, this is the message above the gelocation icon that geolocation is locating."
|
"description": "On the explore the map page, on the map, this is the message above the gelocation icon that geolocation is locating."
|
||||||
},
|
},
|
||||||
|
"explore.map.page.map.layer.selector.tracts.long": {
|
||||||
|
"defaultMessage": "Census Tracts",
|
||||||
|
"description": "On the explore the map page, on the map, the full name indicating Census Tracts"
|
||||||
|
},
|
||||||
|
"explore.map.page.map.layer.selector.tracts.short": {
|
||||||
|
"defaultMessage": "Tracts",
|
||||||
|
"description": "On the explore the map page, on the map, the short name indicating Census Tracts"
|
||||||
|
},
|
||||||
|
"explore.map.page.map.layer.selector.tribal.long": {
|
||||||
|
"defaultMessage": "Tribal Lands",
|
||||||
|
"description": "On the explore the map page, on the map, the full name indicating Tribal Lands"
|
||||||
|
},
|
||||||
|
"explore.map.page.map.layer.selector.tribal.short": {
|
||||||
|
"defaultMessage": "Tribal",
|
||||||
|
"description": "On the explore the map page, on the map, the short name indicating Tribal Lands"
|
||||||
|
},
|
||||||
"explore.map.page.map.search.placeholder.mobile.text": {
|
"explore.map.page.map.search.placeholder.mobile.text": {
|
||||||
"defaultMessage": "Search locations",
|
"defaultMessage": "Search locations",
|
||||||
"description": "On the explore the map page, on the map, the placeholder text for search"
|
"description": "On the explore the map page, on the map, the placeholder text for search"
|
||||||
|
@ -823,6 +839,10 @@
|
||||||
"defaultMessage": "AND",
|
"defaultMessage": "AND",
|
||||||
"description": "Navigate to the explore the map page. When the map is in view, click on the map. Click on a category to expand. This is the AND spacer around thresholds."
|
"description": "Navigate to the explore the map page. When the map is in view, click on the map. Click on a category to expand. This is the AND spacer around thresholds."
|
||||||
},
|
},
|
||||||
|
"explore.map.page.side.panel.tribalInfo.landAreaName": {
|
||||||
|
"defaultMessage": "Land Area Name:",
|
||||||
|
"description": "Navigate to the explore the map page. Click on Tribal Lands, when the map is in view, \n click on the map. The side panel will show the land area name of the feature selected"
|
||||||
|
},
|
||||||
"explore.map.page.side.panel.version.title": {
|
"explore.map.page.side.panel.version.title": {
|
||||||
"defaultMessage": "Methodology version {version}",
|
"defaultMessage": "Methodology version {version}",
|
||||||
"description": "Navigate to the explore the map page. When the map is in view, click on the map. The side panel will show the methodology version number"
|
"description": "Navigate to the explore the map page. When the map is in view, click on the map. The side panel will show the methodology version number"
|
||||||
|
|
Loading…
Add table
Reference in a new issue