From a2ba9236ab4cc1a97912502053e59636ab8ce24e Mon Sep 17 00:00:00 2001 From: Nat Hillard <72811320+NatHillardUSDS@users.noreply.github.com> Date: Tue, 27 Jul 2021 18:21:47 -0400 Subject: [PATCH] Clicking territory focus button sometimes focuses on a different CBG, not the actual territory (#412) * Fixing #410 - Clicking a CBG region, then another, then hitting territory button results in random CBG being selected * Part of fix for #410 - refactoring selection logic to remove setFeatureState call. * Renaming layers for clarity and adding constants * Using constant for layer identifier * Fixes #409 - Loading a URL with lat/lng/zoom specified occasionally does not work . We now parse the URL on page before initializing the map. Also adds tests for this --- client/cypress/e2e/latlngurl.spec.js | 14 +++++ client/src/components/J40Map.tsx | 81 ++++++++++++++++++---------- client/src/components/mapWrapper.tsx | 16 ------ client/src/data/constants.tsx | 8 ++- client/src/data/mapStyle.tsx | 39 -------------- client/src/pages/cejst.tsx | 7 ++- 6 files changed, 78 insertions(+), 87 deletions(-) delete mode 100644 client/src/components/mapWrapper.tsx diff --git a/client/cypress/e2e/latlngurl.spec.js b/client/cypress/e2e/latlngurl.spec.js index debfd18f..d7fce3a7 100644 --- a/client/cypress/e2e/latlngurl.spec.js +++ b/client/cypress/e2e/latlngurl.spec.js @@ -15,5 +15,19 @@ describe('LatLng Test', () => { cy.url().should('include', '#4/35.04/-77.9'); }); }); + it('allows user to specify alternative starting URL', () => { + const [expectedZoom, expectedLat, expectedLng] = [12.05, 41.40965, -75.65978]; + const expectedURL = `http://localhost:8000/en/cejst/#${expectedZoom}/${expectedLat}/${expectedLng}`; + cy.visit(expectedURL); + cy.getMap().then((map) => { + cy.waitForMapIdle(map); + cy.url().should('equal', expectedURL); + const actualZoom = map.getZoom(); + const actualCenter = map.getCenter(); + expect(actualCenter.lat).to.eq(expectedLat); + expect(actualCenter.lng).to.eq(expectedLng); + expect(actualZoom).to.eq(expectedZoom); + }); + }); }); diff --git a/client/src/components/J40Map.tsx b/client/src/components/J40Map.tsx index ab4da961..cf538069 100644 --- a/client/src/components/J40Map.tsx +++ b/client/src/components/J40Map.tsx @@ -1,6 +1,6 @@ /* eslint-disable no-unused-vars */ // External Libs: -import React, {MouseEvent, useRef, useState} from 'react'; +import React, {MouseEvent, useRef, useState, useMemo} from 'react'; import {Map, MapboxGeoJSONFeature, LngLatBoundsLike} from 'maplibre-gl'; import ReactMapGL, { MapEvent, @@ -11,7 +11,7 @@ import ReactMapGL, { Popup, FlyToInterpolator, FullscreenControl, - MapRef} from 'react-map-gl'; + MapRef, Source, Layer} from 'react-map-gl'; import bbox from '@turf/bbox'; import * as d3 from 'd3-ease'; import {isMobile} from 'react-device-detect'; @@ -38,6 +38,10 @@ declare global { } } +interface IJ40Interface { + location: Location; +}; + export interface IDetailViewInterface { latitude: number @@ -46,12 +50,12 @@ export interface IDetailViewInterface { properties: constants.J40Properties, }; - -const J40Map = () => { +const J40Map = ({location}: IJ40Interface) => { + const [zoom, lat, lng] = location.hash.slice(1).split('/'); const [viewport, setViewport] = useState({ - latitude: constants.DEFAULT_CENTER[0], - longitude: constants.DEFAULT_CENTER[1], - zoom: constants.GLOBAL_MIN_ZOOM, + latitude: lat && parseFloat(lat) || constants.DEFAULT_CENTER[0], + longitude: lng && parseFloat(lng) || constants.DEFAULT_CENTER[1], + zoom: zoom && parseFloat(zoom) || constants.GLOBAL_MIN_ZOOM, }); const [selectedFeature, setSelectedFeature] = useState(); @@ -63,6 +67,9 @@ const J40Map = () => { const mapRef = useRef(null); const flags = useFlags(); + const selectedFeatureId = (selectedFeature && selectedFeature.id) || ''; + const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]); + const onClick = (event: MapEvent) => { const feature = event.features && event.features[0]; if (feature) { @@ -77,12 +84,11 @@ const J40Map = () => { padding: 40, }, ); - - // If we've selected a new feature, set 'selected' to false - if (selectedFeature && feature.id !== selectedFeature.id) { - setMapSelected(selectedFeature, false); + if (feature.id !== selectedFeatureId) { + setSelectedFeature(feature); + } else { + setSelectedFeature(undefined); } - setMapSelected(feature, true); const popupInfo = { longitude: longitude, latitude: latitude, @@ -124,24 +130,8 @@ const J40Map = () => { }); }; - - const setMapSelected = (feature:MapboxGeoJSONFeature, isSelected:boolean) : void => { - // The below can be confirmed during debug with: - // mapRef.current.getFeatureState({"id":feature.id, "source":feature.source, "sourceLayer":feature.sourceLayer}) - mapRef.current && mapRef.current.getMap().setFeatureState({ - source: feature.source, - sourceLayer: feature.sourceLayer, - id: feature.id, - }, {[constants.SELECTED_PROPERTY]: isSelected}); - if (isSelected) { - setSelectedFeature(feature); - } else { - setSelectedFeature(undefined); - } - }; - - const onClickTerritoryFocusButton = (event: MouseEvent) => { + event.stopPropagation(); const buttonID = event.target && (event.target as HTMLElement).id; switch (buttonID) { @@ -200,6 +190,39 @@ const J40Map = () => { ref={mapRef} data-cy={'reactMapGL'} > + + + + + {('fs' in flags && detailViewData && !transitionInProgress) && ( { - return ( -
-

Explore the Tool

- - -
- ); -}; - - -export default MapWrapper; diff --git a/client/src/data/constants.tsx b/client/src/data/constants.tsx index 98b9a444..15eab058 100644 --- a/client/src/data/constants.tsx +++ b/client/src/data/constants.tsx @@ -23,12 +23,16 @@ export const HIGH_SCORE_LAYER_NAME = 'score-high-layer'; export const LOW_SCORE_SOURCE_NAME = 'score-low'; export const LOW_SCORE_LAYER_NAME = 'score-low-layer'; export const SELECTED_PROPERTY = 'selected'; +export const CURRENTLY_SELECTED_FEATURE_HIGHLIGHT_LAYER_NAME = 'currently-selected-feature-highlight-layer'; +export const BLOCK_GROUP_BOUNDARY_LAYER_NAME = 'block-group-boundary-layer'; + + +// Properties export const POVERTY_PROPERTY_PERCENTILE = 'Poverty (Less than 200% of federal poverty line) (percentile)'; export const HOUSING_BURDEN_PROPERTY_PERCENTILE = 'Housing burden (percent) (percentile)'; export const LINGUISTIC_ISOLATION_PROPERTY_PERCENTILE = 'Linguistic isolation (percent) (percentile)'; export const UNEMPLOYMENT_PROPERTY_PERCENTILE = 'Unemployed civilians (percent) (percentile)'; export const TOTAL_POPULATION = 'Total population'; - export const EDUCATION_PROPERTY_PERCENTILE = 'Percent individuals age 25 or over ' + 'with less than high school degree (percentile)'; @@ -99,9 +103,11 @@ export const MIN_COLOR = '#FFFFFF'; export const MED_COLOR = '#D1DAE6'; export const MAX_COLOR = '#768FB3'; export const BORDER_HIGHLIGHT_COLOR = '#00BDE3'; +export const CURRENTLY_SELECTED_FEATURE_LAYER_OPACITY = 0.5; // Widths export const HIGHLIGHT_BORDER_WIDTH = 5.0; +export const CURRENTLY_SELECTED_FEATURE_LAYER_WIDTH = 0.8; // Score boundaries export const SCORE_BOUNDARY_LOW = 0.0; diff --git a/client/src/data/mapStyle.tsx b/client/src/data/mapStyle.tsx index c08c0916..381e81f7 100644 --- a/client/src/data/mapStyle.tsx +++ b/client/src/data/mapStyle.tsx @@ -169,45 +169,6 @@ export const makeMapStyle = (flagContainer: FlagContainer) : Style => { 'maxzoom': constants.GLOBAL_MAX_ZOOM_LOW, }, { - // "Score-highlights" represents the border - // around given tiles that appears at higher zooms - 'id': 'score-highlights-layer', - 'source': constants.HIGH_SCORE_SOURCE_NAME, - 'source-layer': constants.SCORE_SOURCE_LAYER, - 'type': 'line', - 'layout': { - 'visibility': 'visible', - 'line-join': 'round', - 'line-cap': 'round', - }, - 'paint': { - 'line-color': constants.DEFAULT_OUTLINE_COLOR, - 'line-width': 0.8, - 'line-opacity': 0.5, - }, - 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGHLIGHT, - 'maxzoom': constants.GLOBAL_MAX_ZOOM_HIGHLIGHT, - }, - { - // "score-border-highlight" is used to highlight - // the currently-selected feature - 'id': 'score-border-highlight-layer', - 'type': 'line', - 'source': constants.HIGH_SCORE_SOURCE_NAME, - 'source-layer': constants.SCORE_SOURCE_LAYER, - 'layout': {}, - 'paint': { - 'line-color': constants.BORDER_HIGHLIGHT_COLOR, - 'line-width': [ - 'case', - ['boolean', ['feature-state', constants.SELECTED_PROPERTY], false], - constants.HIGHLIGHT_BORDER_WIDTH, - 0, - ], - }, - 'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH, - }, - { // We put labels last to ensure prominence 'id': 'labels-only-layer', 'type': 'raster', diff --git a/client/src/pages/cejst.tsx b/client/src/pages/cejst.tsx index 234c3765..fc561bc8 100644 --- a/client/src/pages/cejst.tsx +++ b/client/src/pages/cejst.tsx @@ -1,8 +1,9 @@ import React from 'react'; import Layout from '../components/layout'; -import MapWrapper from '../components/mapWrapper'; +import J40Map from '../components/J40Map'; import HowYouCanHelp from '../components/HowYouCanHelp'; import DownloadPacket from '../components/downloadPacket'; +import MapLegend from '../components/mapLegend'; import * as styles from './cejst.module.scss'; interface IMapPageProps { @@ -51,7 +52,9 @@ const CEJSTPage = ({location}: IMapPageProps) => { - +

Explore the Tool

+ +