diff --git a/client/.env.development b/client/.env.development index 67707973..2ebda307 100644 --- a/client/.env.development +++ b/client/.env.development @@ -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=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_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 # open source one from CartoDB), please create your own API TOKEN from # your MapBox account and add the token here: -# MAPBOX_STYLES_READ_TOKEN='' +MAPBOX_STYLES_READ_TOKEN='' \ No newline at end of file diff --git a/client/.env.production b/client/.env.production index 196be237..93712b45 100644 --- a/client/.env.production +++ b/client/.env.production @@ -6,6 +6,7 @@ GATSBY_CDN_TILES_BASE_URL=https://static-data-screeningtool.geoplatform.gov 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_SHAPE_FILE_ZIP=shapefile/usa.zip diff --git a/client/src/components/AreaDetail/AreaDetail.tsx b/client/src/components/AreaDetail/AreaDetail.tsx index 48d61346..2aeaa337 100644 --- a/client/src/components/AreaDetail/AreaDetail.tsx +++ b/client/src/components/AreaDetail/AreaDetail.tsx @@ -24,6 +24,7 @@ import mailIcon from '/node_modules/uswds/dist/img/usa-icons/mail_outline.svg'; interface IAreaDetailProps { properties: constants.J40Properties, hash: string[], + isCensusLayerSelected: boolean, } /** @@ -62,7 +63,8 @@ export interface ICategory { isExceed1MoreBurden: boolean | null, isExceedBothSocioBurdens: boolean | null, } -const AreaDetail = ({properties, hash}: IAreaDetailProps) => { + +const AreaDetail = ({properties, hash, isCensusLayerSelected}: IAreaDetailProps) => { const intl = useIntl(); // 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 stateName = properties[constants.STATE_NAME] ? properties[constants.STATE_NAME] : "N/A"; const sidePanelState = properties[constants.SIDE_PANEL_STATE]; + const landAreaName = properties[constants.LAND_AREA_NAME]; const isCommunityFocus = score >= constants.SCORE_BOUNDARY_THRESHOLD; @@ -590,65 +593,112 @@ const AreaDetail = ({properties, hash}: IAreaDetailProps) => { {/* Demographics */} - {/* Disadvantaged? */} -
- {/* Questions asking if disadvantaged? */} -
- {EXPLORE_COPY.COMMUNITY.IS_FOCUS} -
+ { + isCensusLayerSelected ? ( + <> + {/* Census Info */} + - {/* YES with Dot or NO with no Dot */} -
- {isCommunityFocus ? - <> -

{EXPLORE_COPY.COMMUNITY.OF_FOCUS}

- - : -

{EXPLORE_COPY.COMMUNITY.NOT_OF_FOCUS}

- } -
+ {/* Disadvantaged? */} +
- {/* Number of categories exceeded */} -
- {EXPLORE_COPY.numberOfCategoriesExceeded(properties[constants.COUNT_OF_CATEGORIES_DISADV])} -
- - {/* Number of thresholds exceeded */} - {/*
- {EXPLORE_COPY.numberOfThresholdsExceeded(properties[constants.TOTAL_NUMBER_OF_DISADVANTAGE_INDICATORS])} -
*/} - {/* Send Feedback button */} - - - -
+ {/* YES with Dot or NO with no Dot */} +
+ {isCommunityFocus ? + <> +

{EXPLORE_COPY.COMMUNITY.OF_FOCUS}

+ + : +

{EXPLORE_COPY.COMMUNITY.NOT_OF_FOCUS}

+ } +
+ + {/* Number of categories exceeded */} +
+ {EXPLORE_COPY.numberOfCategoriesExceeded(properties[constants.COUNT_OF_CATEGORIES_DISADV])} +
+ + {/* Number of thresholds exceeded */} + {/*
+ {EXPLORE_COPY.numberOfThresholdsExceeded(properties[constants.TOTAL_NUMBER_OF_DISADVANTAGE_INDICATORS])} +
*/} + {/* Send Feedback button */} + + + +
+ + ) : ( + + ) + } + {/* All category accordions in this component */} - + {isCensusLayerSelected && } {/* Methodology version */}
diff --git a/client/src/components/AreaDetail/tests/__snapshots__/areaDetail.test.tsx.snap b/client/src/components/AreaDetail/tests/__snapshots__/areaDetail.test.tsx.snap index f4d8bbbf..3714f9b5 100644 --- a/client/src/components/AreaDetail/tests/__snapshots__/areaDetail.test.tsx.snap +++ b/client/src/components/AreaDetail/tests/__snapshots__/areaDetail.test.tsx.snap @@ -210,11 +210,11 @@ exports[`rendering of the AreaDetail checks if indicators for ISLAND AREAS are p
@@ -727,11 +727,11 @@ exports[`rendering of the AreaDetail checks if indicators for NATION is present @@ -2619,11 +2619,11 @@ exports[`rendering of the AreaDetail checks if indicators for PUERTO RICO are pr diff --git a/client/src/components/AreaDetail/tests/areaDetail.test.tsx b/client/src/components/AreaDetail/tests/areaDetail.test.tsx index 1ffff386..232c8bf4 100644 --- a/client/src/components/AreaDetail/tests/areaDetail.test.tsx +++ b/client/src/components/AreaDetail/tests/areaDetail.test.tsx @@ -5,6 +5,7 @@ import {LocalizedComponent} from '../../../test/testHelpers'; import * as constants from '../../../data/constants'; +// Todo: Update tests to take into account tribal layer selected describe('rendering of the AreaDetail', () => { const properties = { [constants.POVERTY_BELOW_100_PERCENTILE]: .12, @@ -27,7 +28,7 @@ describe('rendering of the AreaDetail', () => { it('checks if indicators for NATION is present', () => { const {asFragment} = render( - + , ); expect(asFragment()).toMatchSnapshot(); @@ -41,7 +42,7 @@ describe('rendering of the AreaDetail', () => { const {asFragment} = render( - + , ); expect(asFragment()).toMatchSnapshot(); @@ -59,7 +60,7 @@ describe('rendering of the AreaDetail', () => { const {asFragment} = render( - + , ); expect(asFragment()).toMatchSnapshot(); diff --git a/client/src/components/J40Map.module.scss b/client/src/components/J40Map.module.scss index f1d83333..58f597c1 100644 --- a/client/src/components/J40Map.module.scss +++ b/client/src/components/J40Map.module.scss @@ -59,3 +59,13 @@ overflow-y: auto; 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 + } + +} diff --git a/client/src/components/J40Map.tsx b/client/src/components/J40Map.tsx index f6089259..363bb199 100644 --- a/client/src/components/J40Map.tsx +++ b/client/src/components/J40Map.tsx @@ -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 {useIntl} from 'gatsby-plugin-intl'; import bbox from '@turf/bbox'; import * as d3 from 'd3-ease'; @@ -27,6 +27,9 @@ import {useFlags} from '../contexts/FlagContext'; import AreaDetail from './AreaDetail'; import MapInfoPanel from './mapInfoPanel'; import MapSearch from './MapSearch'; +import MapTractLayers from './MapTractLayers/MapTractLayers'; +import MapTribalLayer from './MapTribalLayers/MapTribalLayers'; +import LayerSelector from './LayerSelector'; import TerritoryFocusControl from './territoryFocusControl'; import {getOSBaseMap} from '../data/getOSBaseMap'; @@ -34,10 +37,8 @@ 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 * as EXPLORE_COPY from '../data/copy/explore'; - declare global { interface Window { Cypress?: object; @@ -57,55 +58,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('/'); - } -}; - -/** - * This the main map component - * - * @param {IJ40Interface} location - * @returns {ReactElement} - */ const J40Map = ({location}: IJ40Interface) => { /** * 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(false); const [geolocationInProgress, setGeolocationInProgress] = useState(false); const [isMobileMapState, setIsMobileMapState] = useState(false); + const [censusSelected, setCensusSelected] = useState(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(false); const {width: windowWidth} = useWindowSize(); /** @@ -153,7 +110,6 @@ const J40Map = ({location}: IJ40Interface) => { const intl = useIntl(); const selectedFeatureId = (selectedFeature && selectedFeature.id) || ''; - const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]); const zoomLatLngHash = mapRef.current?.getMap()._hash._getCurrentHash(); @@ -219,13 +175,30 @@ const J40Map = ({location}: IJ40Interface) => { } } else { // 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 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) { // Get the current selected feature's bounding box: const [minLng, minLat, maxLng, maxLat] = bbox(feature); + // Set the selectedFeature ID if (feature.id !== selectedFeatureId) { setSelectedFeature(feature); @@ -233,8 +206,9 @@ const J40Map = ({location}: IJ40Interface) => { 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], [maxLng, maxLat], ]); @@ -298,7 +272,7 @@ const J40Map = ({location}: IJ40Interface) => { * @param {LngLatBoundsLike} bounds * @param {boolean} isTerritory */ - const goToPlace = (bounds: LngLatBoundsLike, isTerritory = false ) => { + const goToPlace = (bounds: LngLatBoundsLike, isTerritory = false) => { const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!}); const {longitude, latitude, zoom} = newViewPort.fitBounds( bounds as [[number, number], [number, number]], { @@ -376,6 +350,13 @@ const J40Map = ({location}: IJ40Interface) => { * Any component declarations outside the component may be susceptible to this bug. */} + {/* This will allow to select between the census tract layer and the tribal lands layer */} + + {/** * The ReactMapGL component's props are grouped by the API's documentation. The component also has * some children. @@ -390,7 +371,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. @@ -404,7 +385,16 @@ 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, + constants.TRIBAL_ALASKA_POINTS_LAYER_ID, + ] + } // ****** Callback props: ****** @@ -418,98 +408,19 @@ const J40Map = ({location}: IJ40Interface) => { ref={mapRef} data-cy={'reactMapGL'} > - {/** - * Load all data sources and layers - * - * First the low zoom: - */} - - {/* Low zoom layer - prioritized features only */} - ', 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} - /> - - - {/** - * The high zoom source - */} - - - {/* High zoom layer - non-prioritized features only */} - - - {/* High zoom layer - prioritized features only */} - ', 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 */} - - - {/* High zoom layer - border styling around the selected feature */} - - + {/* Load either the Tribal layer or Census layer depending on the censusSelected state variable */} + { + censusSelected ? + : + + } {/* This is the first overlayed row on the map: Search and Geolocation */}
@@ -562,7 +473,11 @@ const J40Map = ({location}: IJ40Interface) => { onClose={setDetailViewData} captureScroll={true} > - + )} {'fs' in flags ? :'' } @@ -576,6 +491,8 @@ const J40Map = ({location}: IJ40Interface) => { featureProperties={detailViewData?.properties} selectedFeatureId={selectedFeature?.id} hash={zoomLatLngHash} + isCensusLayerSelected={censusSelected} + layerToggled={layerToggled} /> diff --git a/client/src/components/LayerSelector/LayerSelector.module.scss b/client/src/components/LayerSelector/LayerSelector.module.scss new file mode 100644 index 00000000..bb491638 --- /dev/null +++ b/client/src/components/LayerSelector/LayerSelector.module.scss @@ -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; + +} + + + + + + + \ No newline at end of file diff --git a/client/src/components/LayerSelector/LayerSelector.module.scss.d.ts b/client/src/components/LayerSelector/LayerSelector.module.scss.d.ts new file mode 100644 index 00000000..f8c32767 --- /dev/null +++ b/client/src/components/LayerSelector/LayerSelector.module.scss.d.ts @@ -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; diff --git a/client/src/components/LayerSelector/LayerSelector.test.tsx b/client/src/components/LayerSelector/LayerSelector.test.tsx new file mode 100644 index 00000000..76667a90 --- /dev/null +++ b/client/src/components/LayerSelector/LayerSelector.test.tsx @@ -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( + + {}} setLayerToggled={() =>{}}/> + , + ); + expect(asFragment()).toMatchSnapshot(); + }); + + it('checks if component renders tribal selected', () => { + const {asFragment} = render( + + {}} setLayerToggled={()=> {}}/> + , + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/client/src/components/LayerSelector/LayerSelector.tsx b/client/src/components/LayerSelector/LayerSelector.tsx new file mode 100644 index 00000000..638fcacc --- /dev/null +++ b/client/src/components/LayerSelector/LayerSelector.tsx @@ -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, + setLayerToggled: Dispatch, +} + +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 ( +
+ {/* // Todo: set i18n here */} + + + + + +
+ ); +}; + +export default LayerSelector; diff --git a/client/src/components/LayerSelector/__snapshots__/LayerSelector.test.tsx.snap b/client/src/components/LayerSelector/__snapshots__/LayerSelector.test.tsx.snap new file mode 100644 index 00000000..55de09d0 --- /dev/null +++ b/client/src/components/LayerSelector/__snapshots__/LayerSelector.test.tsx.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`rendering of the LayerSelector checks if component renders census tracts selected 1`] = ` + +
+ +
    +
  • + +
  • +
  • + +
  • +
+
+
+`; + +exports[`rendering of the LayerSelector checks if component renders tribal selected 1`] = ` + +
+ +
    +
  • + +
  • +
  • + +
  • +
+
+
+`; diff --git a/client/src/components/LayerSelector/index.tsx b/client/src/components/LayerSelector/index.tsx new file mode 100644 index 00000000..61f2645c --- /dev/null +++ b/client/src/components/LayerSelector/index.tsx @@ -0,0 +1,3 @@ +import LayerSelector from './LayerSelector'; + +export default LayerSelector; diff --git a/client/src/components/MapTractLayers/MapTractLayers.tsx b/client/src/components/MapTractLayers/MapTractLayers.tsx new file mode 100644 index 00000000..93c96d5a --- /dev/null +++ b/client/src/components/MapTractLayers/MapTractLayers.tsx @@ -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. + <> + + + {/* Low zoom layer (static) - prioritized features only */} + ', 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} + /> + + + {/* The high zoom source */} + + + {/* High zoom layer (static) - non-prioritized features only */} + + + {/* High zoom layer (static) - prioritized features only */} + ', 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 */} + + + + + ): ( + + /** + * 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 + */ + + + {/* High zoom layer (dynamic) - border styling around the selected feature */} + + + ); +}; + +export default MapTractLayers; diff --git a/client/src/components/MapTractLayers/index.tsx b/client/src/components/MapTractLayers/index.tsx new file mode 100644 index 00000000..5721cd84 --- /dev/null +++ b/client/src/components/MapTractLayers/index.tsx @@ -0,0 +1,3 @@ +import MapTractLayers from './MapTractLayers'; + +export default MapTractLayers; diff --git a/client/src/components/MapTribalLayers/MapTribalLayers.tsx b/client/src/components/MapTribalLayers/MapTribalLayers.tsx new file mode 100644 index 00000000..8a4db968 --- /dev/null +++ b/client/src/components/MapTribalLayers/MapTribalLayers.tsx @@ -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. + + + {/* Tribal layer */} + + + {/* Tribal layer - controls the border between features */} + + + {/* Tribal layer - border styling around the selected feature */} + + + {/* Alaska layer */} + {/* // Todo: limit zoom in amount */} + + + ) : ( + + /** + * 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 + */ + + + {/* Tribal layer - border styling around the selected feature */} + + + ); +}; + +export default MapTribalLayer; diff --git a/client/src/components/MapTribalLayers/index.tsx b/client/src/components/MapTribalLayers/index.tsx new file mode 100644 index 00000000..9d484931 --- /dev/null +++ b/client/src/components/MapTribalLayers/index.tsx @@ -0,0 +1,3 @@ +import MapTribalLayers from './MapTribalLayers'; + +export default MapTribalLayers; diff --git a/client/src/components/mapInfoPanel.tsx b/client/src/components/mapInfoPanel.tsx index 1a454448..c0ec0687 100644 --- a/client/src/components/mapInfoPanel.tsx +++ b/client/src/components/mapInfoPanel.tsx @@ -7,13 +7,33 @@ interface IMapInfoPanelProps { featureProperties: { [key:string]: string | number } | undefined, selectedFeatureId: string | number | undefined 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 (
- {(featureProperties && selectedFeatureId ) ? - : + {/* The tertiary conditional statement below will control the side panel state. Currently + 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) ? + : }
diff --git a/client/src/data/constants.tsx b/client/src/data/constants.tsx index 09f38ba4..25bb5fa5 100644 --- a/client/src/data/constants.tsx +++ b/client/src/data/constants.tsx @@ -25,6 +25,10 @@ export type J40Properties = { [key: string]: any }; // ****** 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 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 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'; +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: 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_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 = '##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 export const FEATURE_BORDER_WIDTH = 0.8; 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 export const GLOBAL_MAX_BOUNDS: LngLatBoundsLike = [ diff --git a/client/src/data/copy/explore.tsx b/client/src/data/copy/explore.tsx index 3f56d83b..94f7801a 100644 --- a/client/src/data/copy/explore.tsx +++ b/client/src/data/copy/explore.tsx @@ -184,6 +184,26 @@ export const MAP = defineMessages({ // 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.', // }, + 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 = { OF_FOCUS: { - return { +// *********** OPEN SOURCE STATIC MAP STYLES *************** +/** + * This function will be called when there is no MapBox token found. This function will + * 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, /** @@ -44,6 +63,111 @@ 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, + }, + + // 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: [constants.HIGH_ZOOM_SOURCE_NAME]: { // 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, }, - /** - * High zoom layer - non-prioritized features only - */ + // A layer for labels only { - 'id': constants.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 prioritized features - */ - 'filter': ['all', - ['<', constants.SCORE_PROPERTY_HIGH, constants.SCORE_BOUNDARY_THRESHOLD], - ], - - 'type': 'fill', - 'paint': { - 'fill-opacity': constants.NON_PRIORITIZED_FEATURE_FILL_OPACITY, + 'id': 'labels-only-layer', + 'source': 'labels', + 'type': 'raster', + 'layout': { + 'visibility': 'visible', }, - 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH, + 'minzoom': constants.GLOBAL_MIN_ZOOM, + 'maxzoom': constants.GLOBAL_MAX_ZOOM, }, - /** - * 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 - */ + // Low zoom layer (static) - prioritized features only { 'id': constants.LOW_ZOOM_LAYER_ID, 'source': constants.LOW_ZOOM_SOURCE_NAME, @@ -162,16 +250,60 @@ export const getOSBaseMap = () : Style => { 'maxzoom': constants.GLOBAL_MAX_ZOOM_LOW, }, - // A layer for labels only + // High zoom layer (static) - non-prioritized features only { - 'id': 'labels-only-layer', - 'source': 'labels', - 'type': 'raster', - 'layout': { - 'visibility': 'visible', + 'id': constants.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 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, - 'maxzoom': constants.GLOBAL_MAX_ZOOM, + 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH, + }, + + // 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, }, ], }; diff --git a/client/src/intl/en.json b/client/src/intl/en.json index d86db2f3..513db923 100644 --- a/client/src/intl/en.json +++ b/client/src/intl/en.json @@ -351,6 +351,22 @@ "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." }, + "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": { "defaultMessage": "Search locations", "description": "On the explore the map page, on the map, the placeholder text for search" @@ -823,6 +839,10 @@ "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." }, + "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": { "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"