mirror of
https://github.com/DOI-DO/j40-cejst-2.git
synced 2025-02-22 17:44:20 -08:00
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
This commit is contained in:
parent
36f43b2d44
commit
a2ba9236ab
6 changed files with 78 additions and 87 deletions
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ViewportProps>({
|
||||
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<MapboxGeoJSONFeature>();
|
||||
|
@ -63,6 +67,9 @@ const J40Map = () => {
|
|||
const mapRef = useRef<MapRef>(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<HTMLButtonElement>) => {
|
||||
event.stopPropagation();
|
||||
const buttonID = event.target && (event.target as HTMLElement).id;
|
||||
|
||||
switch (buttonID) {
|
||||
|
@ -200,6 +190,39 @@ const J40Map = () => {
|
|||
ref={mapRef}
|
||||
data-cy={'reactMapGL'}
|
||||
>
|
||||
<Source
|
||||
id={constants.HIGH_SCORE_SOURCE_NAME}
|
||||
type="vector"
|
||||
promoteId={constants.GEOID_PROPERTY}
|
||||
tiles={[constants.FEATURE_TILE_HIGH_ZOOM_URL]}
|
||||
maxzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
minzoom={constants.GLOBAL_MAX_ZOOM_HIGH}
|
||||
>
|
||||
<Layer
|
||||
id={constants.CURRENTLY_SELECTED_FEATURE_HIGHLIGHT_LAYER_NAME}
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
type='line'
|
||||
paint={{
|
||||
'line-color': constants.DEFAULT_OUTLINE_COLOR,
|
||||
'line-width': constants.CURRENTLY_SELECTED_FEATURE_LAYER_WIDTH,
|
||||
'line-opacity': constants.CURRENTLY_SELECTED_FEATURE_LAYER_OPACITY,
|
||||
}}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGHLIGHT}
|
||||
maxzoom={constants.GLOBAL_MAX_ZOOM_HIGHLIGHT}
|
||||
/>
|
||||
|
||||
<Layer
|
||||
id={constants.BLOCK_GROUP_BOUNDARY_LAYER_NAME}
|
||||
type='line'
|
||||
source-layer={constants.SCORE_SOURCE_LAYER}
|
||||
paint={{
|
||||
'line-color': constants.BORDER_HIGHLIGHT_COLOR,
|
||||
'line-width': constants.HIGHLIGHT_BORDER_WIDTH,
|
||||
}}
|
||||
filter={filter}
|
||||
minzoom={constants.GLOBAL_MIN_ZOOM_HIGH}
|
||||
/>
|
||||
</Source>
|
||||
{('fs' in flags && detailViewData && !transitionInProgress) && (
|
||||
<Popup
|
||||
className={styles.j40Popup}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
import * as React from 'react';
|
||||
import J40Map from './J40Map';
|
||||
import MapLegend from '../components/mapLegend';
|
||||
|
||||
const MapWrapper = () => {
|
||||
return (
|
||||
<section>
|
||||
<h2>Explore the Tool</h2>
|
||||
<J40Map />
|
||||
<MapLegend />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default MapWrapper;
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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) => {
|
|||
<DownloadPacket />
|
||||
</div>
|
||||
</section>
|
||||
<MapWrapper />
|
||||
<h2>Explore the Tool</h2>
|
||||
<J40Map location={location}/>
|
||||
<MapLegend />
|
||||
<HowYouCanHelp />
|
||||
</main>
|
||||
</Layout>
|
||||
|
|
Loading…
Add table
Reference in a new issue