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 [geolocationInProgress, setGeolocationInProgress] = useState<boolean>(false);
const [isMobileMapState, setIsMobileMapState] = useState<boolean>(false);
const [selectTractId, setSelectTractId] = useState<string | undefined>(undefined);
const {width: windowWidth} = useWindowSize();
/**
@ -123,11 +124,7 @@ const J40Map = ({location}: IJ40Interface) => {
const [minLng, minLat, maxLng, maxLat] = bbox(feature);
// Set the selectedFeature ID
if (feature.id !== selectedFeatureId) {
setSelectedFeature(feature);
} else {
setSelectedFeature(undefined);
}
setSelectedFeature(feature);
// Go to the newly selected feature (as long as it's not an Alaska Point)
goToPlace([
@ -250,7 +247,7 @@ const J40Map = ({location}: IJ40Interface) => {
* @param {LngLatBoundsLike} bounds
* @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 {longitude, latitude, zoom} = newViewPort.fitBounds(
bounds as [[number, number], [number, number]], {
@ -281,6 +278,9 @@ const J40Map = ({location}: IJ40Interface) => {
transitionInterpolator: new FlyToInterpolator(),
transitionEasing: d3.easeCubic,
});
// Set the tract ID to be selected if any.
setSelectTractId(selectTractId);
};
const onTransitionStart = () => {
@ -289,6 +289,25 @@ const J40Map = ({location}: IJ40Interface) => {
const onTransitionEnd = () => {
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 = () => {
@ -393,8 +412,7 @@ const J40Map = ({location}: IJ40Interface) => {
{/* This is the first overlayed row on the map: Search and Geolocation */}
<div className={styles.mapHeaderRow}>
<MapSearch goToPlace={goToPlace} mapRef={mapRef} selectFeatureOnMap={selectFeatureOnMap}
selectedFeatureId={selectedFeatureId}/>
<MapSearch goToPlace={goToPlace}/>
{/* Geolocate Icon */}
<div className={styles.geolocateBox}>

View file

@ -4,8 +4,6 @@ import {LngLatBoundsLike} from 'maplibre-gl';
import {useIntl} from 'gatsby-plugin-intl';
import {Search} from '@trussworks/react-uswds';
import {useWindowSize} from 'react-use';
import {RefObject} from 'react';
import {MapRef} from 'react-map-gl';
import * as JsSearch from 'js-search';
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';
interface IMapSearch {
goToPlace(bounds: LngLatBoundsLike):void;
mapRef:RefObject<MapRef>;
selectFeatureOnMap: (feature: any) => void;
selectedFeatureId: string;
goToPlace(bounds: LngLatBoundsLike, isTerritory: boolean, selectTractId: string | undefined):void;
}
interface ISearchTractRecord {
@ -27,7 +22,7 @@ interface ISearchTractRecord {
INTPTLON10: string;
}
const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IMapSearch) => {
const MapSearch = ({goToPlace}:IMapSearch) => {
// State to hold if the search results are empty or not:
const [isSearchResultsNull, setIsSearchResultsNull] = useState(false);
const intl = useIntl();
@ -85,33 +80,15 @@ const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IM
const searchForTract = async (tract: string) => {
// We create a bounding box just to get the tract in the view box.
// The size is not important.
const BOUNDING_BOX_SIZE_DD = 0.1;
/**
* 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);
}
};
const BOUNDING_BOX_SIZE_DD = 0.2;
// Convert 10 digit tracts to 11.
const searchTerm = tract.length == 10 ? '0' + tract : tract;
// If the search is for the same tract then do nothing.
if (selectedFeatureId == searchTerm) return;
const normalizedTractId = tract.length == 10 ? '0' + tract : tract;
setIsSearchResultsNull(true);
if (tractSearch) {
const result = tractSearch.search(searchTerm);
const result = tractSearch.search(normalizedTractId);
if (result.length > 0) {
const searchTractRecord = result[0] as ISearchTractRecord;
const lat = Number(searchTractRecord.INTPTLAT10);
@ -126,21 +103,7 @@ const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IM
setIsSearchResultsNull(false);
// Now move the map and select the tract.
goToPlace([[Number(longMin), Number(latMin)], [Number(longMax), Number(latMax)]]);
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]);
}
});
});
goToPlace([[Number(longMin), Number(latMin)], [Number(longMax), Number(latMax)]], false, normalizedTractId);
}
}
};
@ -173,7 +136,7 @@ const MapSearch = ({goToPlace, mapRef, selectFeatureOnMap, selectedFeatureId}:IM
if (searchResults && searchResults.length > 0) {
setIsSearchResultsNull(false);
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 {
setIsSearchResultsNull(true);
}