Search by tract needs to highlight a tract 100% of the time

This commit is contained in:
Carlos Felix 2024-12-16 11:44:53 -05:00 committed by Carlos Felix
commit aa53d519a5
2 changed files with 33 additions and 52 deletions

View file

@ -91,6 +91,7 @@ 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 [selectTractId, setSelectTractId] = useState<string | undefined>(undefined);
const {width: windowWidth} = useWindowSize(); const {width: windowWidth} = useWindowSize();
/** /**
@ -123,11 +124,7 @@ const J40Map = ({location}: IJ40Interface) => {
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) { setSelectedFeature(feature);
setSelectedFeature(feature);
} else {
setSelectedFeature(undefined);
}
// Go to the newly selected feature (as long as it's not an Alaska Point) // Go to the newly selected feature (as long as it's not an Alaska Point)
goToPlace([ goToPlace([
@ -250,7 +247,7 @@ const J40Map = ({location}: IJ40Interface) => {
* @param {LngLatBoundsLike} bounds * @param {LngLatBoundsLike} bounds
* @param {boolean} isTerritory * @param {boolean} isTerritory
*/ */
const goToPlace = (bounds: LngLatBoundsLike, isTerritory = false) => { const goToPlace = (bounds: LngLatBoundsLike, isTerritory = false, selectTractId: string | undefined = undefined) => {
const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!}); const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!});
const {longitude, latitude, zoom} = newViewPort.fitBounds( const {longitude, latitude, zoom} = newViewPort.fitBounds(
bounds as [[number, number], [number, number]], { bounds as [[number, number], [number, number]], {
@ -281,6 +278,9 @@ const J40Map = ({location}: IJ40Interface) => {
transitionInterpolator: new FlyToInterpolator(), transitionInterpolator: new FlyToInterpolator(),
transitionEasing: d3.easeCubic, transitionEasing: d3.easeCubic,
}); });
// Set the tract ID to be selected if any.
setSelectTractId(selectTractId);
}; };
const onTransitionStart = () => { const onTransitionStart = () => {
@ -289,6 +289,25 @@ const J40Map = ({location}: IJ40Interface) => {
const onTransitionEnd = () => { const onTransitionEnd = () => {
setTransitionInProgress(false); setTransitionInProgress(false);
/*
If there is a tract ID to be selected then do so once the map has finished moving.
Note that setting the viewpoint to move the map as done in this component does not
trigger a moveend or idle event like when using flyTo or easeTo.
*/
if (selectTractId) {
// Search for features in the map that have the tract ID.
const geoidSearchResults = mapRef.current?.getMap()
.querySourceFeatures(constants.HIGH_ZOOM_SOURCE_NAME, {
sourceLayer: constants.SCORE_SOURCE_LAYER,
validate: true,
filter: ['==', constants.GEOID_PROPERTY, selectTractId],
});
if (geoidSearchResults && geoidSearchResults.length > 0) {
selectFeatureOnMap(geoidSearchResults[0]);
}
setSelectTractId(undefined);
}
}; };
const onGeolocate = () => { const onGeolocate = () => {
@ -393,8 +412,7 @@ const J40Map = ({location}: IJ40Interface) => {
{/* 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}>
<MapSearch goToPlace={goToPlace} mapRef={mapRef} selectFeatureOnMap={selectFeatureOnMap} <MapSearch goToPlace={goToPlace}/>
selectedFeatureId={selectedFeatureId}/>
{/* Geolocate Icon */} {/* Geolocate Icon */}
<div className={styles.geolocateBox}> <div className={styles.geolocateBox}>

View file

@ -4,8 +4,6 @@ import {LngLatBoundsLike} from 'maplibre-gl';
import {useIntl} from 'gatsby-plugin-intl'; import {useIntl} from 'gatsby-plugin-intl';
import {Search} from '@trussworks/react-uswds'; import {Search} from '@trussworks/react-uswds';
import {useWindowSize} from 'react-use'; import {useWindowSize} from 'react-use';
import {RefObject} from 'react';
import {MapRef} from 'react-map-gl';
import * as JsSearch from 'js-search'; import * as JsSearch from 'js-search';
import * as constants from '../../data/constants'; import * as constants from '../../data/constants';
@ -15,10 +13,7 @@ import * as styles from './MapSearch.module.scss';
import * as EXPLORE_COPY from '../../data/copy/explore'; import * as EXPLORE_COPY from '../../data/copy/explore';
interface IMapSearch { interface IMapSearch {
goToPlace(bounds: LngLatBoundsLike):void; goToPlace(bounds: LngLatBoundsLike, isTerritory: boolean, selectTractId: string | undefined):void;
mapRef:RefObject<MapRef>;
selectFeatureOnMap: (feature: any) => void;
selectedFeatureId: string;
} }
interface ISearchTractRecord { interface ISearchTractRecord {
@ -27,7 +22,7 @@ interface ISearchTractRecord {
INTPTLON10: string; INTPTLON10: string;
} }
const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IMapSearch) => { const MapSearch = ({goToPlace}:IMapSearch) => {
// State to hold if the search results are empty or not: // State to hold if the search results are empty or not:
const [isSearchResultsNull, setIsSearchResultsNull] = useState(false); const [isSearchResultsNull, setIsSearchResultsNull] = useState(false);
const intl = useIntl(); const intl = useIntl();
@ -85,33 +80,15 @@ const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IM
const searchForTract = async (tract: string) => { const searchForTract = async (tract: string) => {
// We create a bounding box just to get the tract in the view box. // We create a bounding box just to get the tract in the view box.
// The size is not important. // The size is not important.
const BOUNDING_BOX_SIZE_DD = 0.1; const BOUNDING_BOX_SIZE_DD = 0.2;
/**
* Wait for the map to be done loading and moving.
* @param {function()} callback the callback to run after the map is ready
*/
const waitforMap = (callback: () => void): void => {
const isMapReady = !!mapRef.current &&
mapRef.current.getMap().isStyleLoaded() &&
mapRef.current.getMap().isSourceLoaded(constants.HIGH_ZOOM_SOURCE_NAME);
if (isMapReady) {
callback();
} else {
setTimeout(() => waitforMap(callback), 200);
}
};
// Convert 10 digit tracts to 11. // Convert 10 digit tracts to 11.
const searchTerm = tract.length == 10 ? '0' + tract : tract; const normalizedTractId = tract.length == 10 ? '0' + tract : tract;
// If the search is for the same tract then do nothing.
if (selectedFeatureId == searchTerm) return;
setIsSearchResultsNull(true); setIsSearchResultsNull(true);
if (tractSearch) { if (tractSearch) {
const result = tractSearch.search(searchTerm); const result = tractSearch.search(normalizedTractId);
if (result.length > 0) { if (result.length > 0) {
const searchTractRecord = result[0] as ISearchTractRecord; const searchTractRecord = result[0] as ISearchTractRecord;
const lat = Number(searchTractRecord.INTPTLAT10); const lat = Number(searchTractRecord.INTPTLAT10);
@ -126,21 +103,7 @@ const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IM
setIsSearchResultsNull(false); setIsSearchResultsNull(false);
// Now move the map and select the tract. // Now move the map and select the tract.
goToPlace([[Number(longMin), Number(latMin)], [Number(longMax), Number(latMax)]]); goToPlace([[Number(longMin), Number(latMin)], [Number(longMax), Number(latMax)]], false, normalizedTractId);
waitforMap(() => {
// Set up a one-shot event handler to fire when the flyTo arrives at its destination. Once the
// tract is in view of the map. mpRef.current will always be valid here...
mapRef.current?.getMap().once('idle', () => {
const geoidSearchResults = mapRef.current?.getMap().querySourceFeatures(constants.HIGH_ZOOM_SOURCE_NAME, {
sourceLayer: constants.SCORE_SOURCE_LAYER,
validate: true,
filter: ['==', constants.GEOID_PROPERTY, searchTerm],
});
if (geoidSearchResults && geoidSearchResults.length > 0) {
selectFeatureOnMap(geoidSearchResults[0]);
}
});
});
} }
} }
}; };
@ -173,7 +136,7 @@ const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IM
if (searchResults && searchResults.length > 0) { if (searchResults && searchResults.length > 0) {
setIsSearchResultsNull(false); setIsSearchResultsNull(false);
const [latMin, latMax, longMin, longMax] = searchResults[0].boundingbox; const [latMin, latMax, longMin, longMax] = searchResults[0].boundingbox;
goToPlace([[Number(longMin), Number(latMin)], [Number(longMax), Number(latMax)]]); goToPlace([[Number(longMin), Number(latMin)], [Number(longMax), Number(latMax)]], false, undefined);
} else { } else {
setIsSearchResultsNull(true); setIsSearchResultsNull(true);
} }