Fix territory shortcuts when census tract is selected (#1082)

* Refactor map click event architecture

- combine territory map clickHandlers
- centers AS on the map

* Center US on the map

- make the east and west coast both viewable
- make clicking on the 48, show the same zoom/lat/long as initial map
- centers Hawaii on map

* Update link to map performance

* Explicitly show links as the links return a 403

* Removes link and spells link out
This commit is contained in:
Vim 2021-12-28 18:30:22 -05:00 committed by GitHub
parent beb0eea5cc
commit 356e16950f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 223 additions and 167 deletions

View file

@ -9,53 +9,53 @@ describe('Does the map zoom and adjust to lat/long correctly?', () => {
cy.get('.mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in').click({force: true});
cy.url().should('include', '#4');
});
// it('should show the correct lat/lng coordinates in the URL',
// {
// retries: {
// runMode: 3,
// openMode: 3,
// },
// defaultCommandTimeout: 4000,
// execTimeout: 10000,
// taskTimeout: 10000,
// pageLoadTimeout: 10000,
// requestTimeout: 5000,
// responseTimeout: 10000,
// },
// () => {
// cy.getMap().then((map) => {
// cy.panTo(map, [-77.9, 35.04]);
// cy.url().should('include', '#4/35.04/-77.9');
// });
// });
it('should show the correct lat/lng coordinates in the URL',
{
retries: {
runMode: 3,
openMode: 3,
},
defaultCommandTimeout: 4000,
execTimeout: 10000,
taskTimeout: 10000,
pageLoadTimeout: 10000,
requestTimeout: 5000,
responseTimeout: 10000,
},
() => {
cy.getMap().then((map) => {
cy.panTo(map, [-77.9, 35.04]);
cy.url().should('include', '#4/35.04/-77.9');
});
});
// This test hangs intermittently (30% of the time) need to investigate why
// it('allows user to specify alternative starting URL',
// {
// retries: {
// runMode: 3,
// openMode: 3,
// },
// defaultCommandTimeout: 4000,
// execTimeout: 10000,
// taskTimeout: 10000,
// pageLoadTimeout: 10000,
// requestTimeout: 5000,
// responseTimeout: 10000,
// },
// () => {
// 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);
// });
// });
it('allows user to specify alternative starting URL',
{
retries: {
runMode: 3,
openMode: 3,
},
defaultCommandTimeout: 4000,
execTimeout: 10000,
taskTimeout: 10000,
pageLoadTimeout: 10000,
requestTimeout: 5000,
responseTimeout: 10000,
},
() => {
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);
});
});
});

View file

@ -1,32 +1,48 @@
// / <reference types="Cypress" />
describe('Will it zoom into territories correctly?', () => {
beforeEach(() => {
cy.viewport('macbook-13');
cy.visit('http://localhost:8000/en/cejst');
});
// The below values all assume a 13-inch MB as set in viewport above.
// Values will be different for different screens
// Removing z as each tests has variance on it's value
const tests = {
'Lower 48': '/38.07/-95.87',
// Todo: Understand what causes these two to hang intermittently ticket #579
// 'Puerto Rico': '/18.2/-66.583',
// 'Alaska': '/63.28/-162.39',
// 'Hawaii': '5.35/20.574/-161.438',
};
for (const [territory, xy] of Object.entries(tests)) {
it(`Can focus on ${territory} `, () => {
cy.getMap().then((map) => {
cy.get(`[aria-label="Focus on ${territory}"]`).click();
cy.waitForMapIdle(map);
cy.log(cy.url());
cy.url().should('include', xy);
describe('Will it zoom into territories correctly?',
{
retries: {
runMode: 3,
openMode: 3,
},
defaultCommandTimeout: 4000,
execTimeout: 10000,
taskTimeout: 10000,
pageLoadTimeout: 10000,
requestTimeout: 5000,
responseTimeout: 10000,
},
() => {
beforeEach(() => {
cy.viewport('macbook-13');
cy.visit('http://localhost:8000/en/cejst');
});
// The below values all assume a 13-inch MB as set in viewport above.
// Values will be different for different screens
// Removing z as each tests has variance on it's value
const tests = {
'Lower 48': '3/33.47/-97.5',
// Todo: Understand what causes these two to hang intermittently ticket #579
// 'Puerto Rico': '7.71/18.2/-66.583',
// 'Alaska': '3/63.28/-162.39',
// 'American Samoa': '6.55/-13.804/-171.117',
// 'Commonwealth of Northern Mariana Islands': '5.98/16.901/145.472',
// 'Hawaii': '5.73/20.657/-157.697',
};
for (let i=0; i<1; i++) {
for (const [territory, zxy] of Object.entries(tests)) {
it(`Can focus on ${territory} `, () => {
cy.getMap().then((map) => {
cy.get(`[aria-label="Focus on ${territory}"]`).click();
cy.waitForMapIdle(map);
cy.log(cy.url());
cy.url().should('include', zxy);
});
});
};
}
});
};
});

View file

@ -28,6 +28,7 @@ export interface indicatorInfo {
const AreaDetail = ({properties}:IAreaDetailProps) => {
const intl = useIntl();
// console.log the properties of the census that is selected:
console.log("Area Detail properies: ", properties);
const score = properties[constants.SCORE_PROPERTY_HIGH] ? properties[constants.SCORE_PROPERTY_HIGH] as number : 0;
@ -38,10 +39,6 @@ const AreaDetail = ({properties}:IAreaDetailProps) => {
const isCommunityFocus = score >= constants.SCORE_BOUNDARY_PRIORITIZED;
// const sidePanelFeedbackHref = `
// mailto:screeningtool.feedback@usds.gov?subject=Feedback on Census Tract: ${blockGroup}
// `;
// Define each indicator in the side panel with constants from copy file (for intl)
// Indicators are grouped by category
const expAgLoss:indicatorInfo = {
@ -382,8 +379,9 @@ const AreaDetail = ({properties}:IAreaDetailProps) => {
totalCount: constants.TOTAL_NUMBER_OF_INDICATORS,
}}/>
</div>
{/* eslint-disable-next-line max-len */}
{/* <a className={styles.feedbackLink} href={sidePanelFeedbackHref}>{EXPLORE_COPY.COMMUNITY.SEND_FEEDBACK}</a> */}
{/* <a className={styles.feedbackLink} href={sidePanelFeedbackHref}>
{EXPLORE_COPY.COMMUNITY.SEND_FEEDBACK}
</a> */}
</div>

View file

@ -1,3 +1,4 @@
/* eslint-disable valid-jsdoc */
/* eslint-disable no-unused-vars */
// External Libs:
import React, {useRef, useState, useMemo} from 'react';
@ -55,12 +56,23 @@ export interface IDetailViewInterface {
const J40Map = ({location}: IJ40Interface) => {
// Hash portion of URL is of the form #zoom/lat/lng
/**
* Initializes the zoom, and the map's center point (lat, lng) via the URL hash #{z}/{lat}/{long}
* where:
* z = zoom
* lat = map center's latitude
* long = map center's longitude
*/
const [zoom, lat, lng] = location.hash.slice(1).split('/');
/**
* If the URL has no #{z}/{lat}/{long} specified in the hash, then set the map's intial viewport state
* to use constants. This is so that we can load URLs with certain zoom/lat/long specified:
*/
const [viewport, setViewport] = useState<ViewportProps>({
latitude: lat && parseFloat(lat) || constants.DEFAULT_CENTER[0],
longitude: lng && parseFloat(lng) || constants.DEFAULT_CENTER[1],
zoom: zoom && parseFloat(zoom) || constants.GLOBAL_MIN_ZOOM,
latitude: lat && parseFloat(lat) ? parseFloat(lat) : constants.DEFAULT_CENTER[0],
longitude: lng && parseFloat(lng) ? parseFloat(lng) : constants.DEFAULT_CENTER[1],
zoom: zoom && parseFloat(zoom) ? parseFloat(zoom) : constants.GLOBAL_MIN_ZOOM,
});
const [selectedFeature, setSelectedFeature] = useState<MapboxGeoJSONFeature>();
@ -76,37 +88,102 @@ const J40Map = ({location}: IJ40Interface) => {
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) {
const [minLng, minLat, maxLng, maxLat] = bbox(feature);
const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!});
const {longitude, latitude, zoom} = newViewPort.fitBounds(
[
[minLng, minLat],
[maxLng, maxLat],
],
{
padding: 40,
},
);
if (feature.id !== selectedFeatureId) {
setSelectedFeature(feature);
console.log(feature.properties);
} else {
setSelectedFeature(undefined);
/**
* This function will return the bounding box of the current map. Comment in when needed.
* {
* _ne: {lng:number, lat:number}
* _sw: {lng:number, lat:number}
* }
* @returns {LngLatBounds}
*/
const getCurrentMapBoundingBox = () => {
return mapRef.current ? console.log('mapRef getBounds(): ', mapRef.current.getMap().getBounds()) : null;
};
/**
* This onClick event handler will listen and handle clicks on the map. It will listen for clicks on the
* territory controls and it will listen to clicks on the map.
*
* It will NOT listen to clicks into the search field or the zoom controls. These clickHandlers are
* captured in their own respective components.
*/
const onClick = (event: MapEvent | React.MouseEvent<HTMLButtonElement>) => {
// Stop all propagation / bubbling / capturing
event.preventDefault();
event.stopPropagation();
getCurrentMapBoundingBox();
// Check if the click is for territories. Given the territories component's design, it can be
// guaranteed that each territory control will have an id. We use this ID to determine
// if the click is coming from a territory control
if (event.target && (event.target as HTMLElement).id) {
const buttonID = event.target && (event.target as HTMLElement).id;
switch (buttonID) {
case '48':
goToPlace(constants.LOWER_48_BOUNDS);
break;
case 'AK':
goToPlace(constants.ALASKA_BOUNDS);
break;
case 'HI':
goToPlace(constants.HAWAII_BOUNDS);
break;
case 'PR':
goToPlace(constants.PUERTO_RICO_BOUNDS);
break;
case 'GU':
goToPlace(constants.GUAM_BOUNDS);
break;
case 'AS':
goToPlace(constants.AMERICAN_SAMOA_BOUNDS);
break;
case 'MP':
goToPlace(constants.MARIANA_ISLAND_BOUNDS);
break;
case 'VI':
goToPlace(constants.US_VIRGIN_ISLANDS_BOUNDS);
break;
default:
break;
}
} else {
// This else clause will fire when the ID is null or empty. This is the case where the map is clicked
const feature = event.features && event.features[0];
console.log(feature);
if (feature) {
const [minLng, minLat, maxLng, maxLat] = bbox(feature);
const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!});
const {longitude, latitude, zoom} = newViewPort.fitBounds(
[
[minLng, minLat],
[maxLng, maxLat],
],
{
padding: 40,
},
);
if (feature.id !== selectedFeatureId) {
setSelectedFeature(feature);
} else {
setSelectedFeature(undefined);
}
const popupInfo = {
longitude: longitude,
latitude: latitude,
zoom: zoom,
properties: feature.properties,
};
goToPlace([
[minLng, minLat],
[maxLng, maxLat],
]);
setDetailViewData(popupInfo);
}
const popupInfo = {
longitude: longitude,
latitude: latitude,
zoom: zoom,
properties: feature.properties,
};
goToPlace([
[minLng, minLat],
[maxLng, maxLat],
]);
setDetailViewData(popupInfo);
}
};
@ -251,7 +328,7 @@ const J40Map = ({location}: IJ40Interface) => {
onClick={onClickGeolocate}
/> : ''}
{geolocationInProgress ? <div>Geolocation in progress...</div> : ''}
<TerritoryFocusControl goToPlace={goToPlace}/>
<TerritoryFocusControl onClick={onClick}/>
{'fs' in flags ? <FullscreenControl className={styles.fullscreenControl}/> :'' }
</ReactMapGL>

View file

@ -1,54 +1,18 @@
import {useIntl} from 'gatsby-plugin-intl';
import React from 'react';
import {LngLatBoundsLike} from 'mapbox-gl';
import {MapEvent} from 'react-map-gl';
import * as styles from './territoryFocusControl.module.scss';
import * as EXPLORE_COPY from '../data/copy/explore';
import * as constants from '../data/constants';
interface ITerritoryFocusControl {
goToPlace(bounds: LngLatBoundsLike): void;
onClick(event: MapEvent | React.MouseEvent<HTMLButtonElement>):void;
}
const TerritoryFocusControl = ({goToPlace}: ITerritoryFocusControl) => {
const TerritoryFocusControl = ({onClick}: ITerritoryFocusControl) => {
const intl = useIntl();
const onClickTerritoryFocusButton = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
const buttonID = event.target && (event.target as HTMLElement).id;
switch (buttonID) {
case '48':
goToPlace(constants.LOWER_48_BOUNDS);
break;
case 'AK':
goToPlace(constants.ALASKA_BOUNDS);
break;
case 'HI':
goToPlace(constants.HAWAII_BOUNDS);
break;
case 'PR':
goToPlace(constants.PUERTO_RICO_BOUNDS);
break;
case 'GU':
goToPlace(constants.GUAM_BOUNDS);
break;
case 'AS':
goToPlace(constants.AMERICAN_SAMOA_BOUNDS);
break;
case 'MP':
goToPlace(constants.MARIANA_ISLAND_BOUNDS);
break;
case 'VI':
goToPlace(constants.US_VIRGIN_ISLANDS_BOUNDS);
break;
default:
break;
}
};
const territories = [
{
short: intl.formatMessage(EXPLORE_COPY.MAP.LOWER48_SHORT),
@ -102,7 +66,8 @@ const TerritoryFocusControl = ({goToPlace}: ITerritoryFocusControl) => {
<button
id={territory.short}
key={territory.short}
onClick={(e) => onClickTerritoryFocusButton(e)}
// onClickCapture={(e) => onClickTerritoryFocusButton(e)}
onClickCapture={(e) => onClick(e)}
className={'mapboxgl-ctrl-icon ' + territoriesIconClassName[index]}
aria-label={intl.formatMessage(
{

View file

@ -128,15 +128,15 @@ export const GLOBAL_MAX_ZOOM_HIGHLIGHT = 22;
export const GLOBAL_MIN_ZOOM_HIGH = 7;
export const GLOBAL_MAX_ZOOM_HIGH = 11;
// Bounds
// Bounds - these bounds can be obtained by using the getCurrentMapBoundingBox() function in the map
export const GLOBAL_MAX_BOUNDS: LngLatBoundsLike = [
[-180.118306, 5.499550],
[-65.0, 83.162102],
];
export const LOWER_48_BOUNDS: LngLatBoundsLike = [
[-124.7844079, 24.7433195],
[-66.9513812, 49.3457868],
[-134.943542, 1.301806],
[-60.060729, 57.050462],
];
export const ALASKA_BOUNDS: LngLatBoundsLike = [
@ -145,8 +145,8 @@ export const ALASKA_BOUNDS: LngLatBoundsLike = [
];
export const HAWAII_BOUNDS: LngLatBoundsLike = [
[-168.118306, 18.748115],
[-154.757881, 22.378413],
[-161.174534, 17.652170],
[-154.218940, 23.603623],
];
export const PUERTO_RICO_BOUNDS: LngLatBoundsLike = [
@ -165,8 +165,8 @@ export const MARIANA_ISLAND_BOUNDS: LngLatBoundsLike = [
];
export const AMERICAN_SAMOA_BOUNDS: LngLatBoundsLike = [
[-171.089874, -14.548699],
[-168.1433, -11.046934],
[-172.589874, -15.548699],
[-169.6433, -12.046934],
];
export const US_VIRGIN_ISLANDS_BOUNDS: LngLatBoundsLike = [
@ -174,7 +174,7 @@ export const US_VIRGIN_ISLANDS_BOUNDS: LngLatBoundsLike = [
[-64.2704123, 18.7495796],
];
export const DEFAULT_CENTER = [32.4687126, -86.502136];
export const DEFAULT_CENTER = [33.4687126, -97.502136];
// Opacity
export const DEFAULT_LAYER_OPACITY = 0.6;

View file

@ -128,7 +128,7 @@ export const MAP = defineMessages({
},
AS_LONG: {
id: 'map.territoryFocus.american.samoa.long',
defaultMessage: 'American Somoa',
defaultMessage: 'American Samoa',
description: 'The full name indicating the bounds of American Somoa',
},
MP_SHORT: {

View file

@ -58,8 +58,8 @@ We provide more detail on these factors below.
![GIS Feature set](./0002-files/GIS_Features.png)
- **Popularity** : OpenLayers is second only to Leaflet in the number of Github [stars](https://github.com/openlayers/openlayers/stargazers) it has received, close to 8000
- **Performance** :
- The below chart comes from an September 2020 [study](https://doi.org/10.3390/ijgi9100563). The purpose of this study was to compare OpenLayers to Mapbox-GL-JS and Leaflet (both raster and vector tile variants) as the potential basis for a Life Quality Index for 55,000+ census radius jurisdictions in Argentina.
![Execution Time](./0002-files/ExecutionTime.png) ([Source](https://doi.org/10.3390/ijgi9100563))
- The below chart comes from an September 2020 study (www dot mdpi dot com/2220-9964/9/10/563). The purpose of this study was to compare OpenLayers to Mapbox-GL-JS and Leaflet (both raster and vector tile variants) as the potential basis for a Life Quality Index for 55,000+ census radius jurisdictions in Argentina.
![Execution Time](./0002-files/ExecutionTime.png) (Source (www dot mdpi dot com/2220-9964/9/10/563))
In this chart, the two letters following the library name are for basemap layer and feature layer. Further, "R" is "raster" and "V" is vector, and lower numbers indicate faster load times. "OpenLayersRR" and "OpenLayersRV", (signifying a raster base layer and vector feature layer), performed quite well across all device types compared to other libraries.
- We also performed local testing using puppeteer and web performance APIs, tested against a choropleth map of the cenus block groups, which represents a likely usecase for us. The results were as follows:
![Choropleth Map Performance](./0002-files/ChoroplethMapPerformance.png) ([Source](./0002-files/Maryland.csv))
@ -70,7 +70,7 @@ We provide more detail on these factors below.
- OL+MB did not file the `tiledidload` event and thus there was not a separate measure for style loaded
- Apparent performance was different from measured/reported overall, particularly when it comes to zoom performance. This is an area to dig into further and measured at a later time to understand better.
- **Data Usage** : The same study above also analyzed the amount of data usage for each of the libraries under investigation, and the result was the below chart (Lower values are better). OpenLayers overall performed quite well
![Data Usage](./0002-files/NetworkTraffic.png) ([Source](https://doi.org/10.3390/ijgi9100563))
![Data Usage](./0002-files/NetworkTraffic.png) (source (www dot mdpi dot com/2220-9964/9/10/563))
### Negative Consequences