diff --git a/client/src/components/J40Map.tsx b/client/src/components/J40Map.tsx index 884fe246..c0f10ba3 100644 --- a/client/src/components/J40Map.tsx +++ b/client/src/components/J40Map.tsx @@ -136,6 +136,7 @@ const J40Map = ({location}: IJ40Interface) => { const filter = useMemo(() => ['in', constants.GEOID_PROPERTY, selectedFeatureId], [selectedFeature]); const zoomLatLngHash = mapRef.current?.getMap()._hash._getCurrentHash(); + /** * This function will return the bounding box of the current map. Comment in when needed. * { @@ -169,28 +170,28 @@ const J40Map = ({location}: IJ40Interface) => { switch (buttonID) { case '48': - goToPlace(constants.LOWER_48_BOUNDS); + goToPlace(constants.LOWER_48_BOUNDS, true); break; case 'AK': - goToPlace(constants.ALASKA_BOUNDS); + goToPlace(constants.ALASKA_BOUNDS, true); break; case 'HI': - goToPlace(constants.HAWAII_BOUNDS); + goToPlace(constants.HAWAII_BOUNDS, true); break; case 'PR': - goToPlace(constants.PUERTO_RICO_BOUNDS); + goToPlace(constants.PUERTO_RICO_BOUNDS, true); break; case 'GU': - goToPlace(constants.GUAM_BOUNDS); + goToPlace(constants.GUAM_BOUNDS, true); break; case 'AS': - goToPlace(constants.AMERICAN_SAMOA_BOUNDS); + goToPlace(constants.AMERICAN_SAMOA_BOUNDS, true); break; case 'MP': - goToPlace(constants.MARIANA_ISLAND_BOUNDS); + goToPlace(constants.MARIANA_ISLAND_BOUNDS, true); break; case 'VI': - goToPlace(constants.US_VIRGIN_ISLANDS_BOUNDS); + goToPlace(constants.US_VIRGIN_ISLANDS_BOUNDS, true); break; default: @@ -200,10 +201,32 @@ const J40Map = ({location}: IJ40Interface) => { // This else clause will fire when the ID is null or empty. This is the case where the map is clicked // @ts-ignore const feature = event.features && event.features[0]; - console.log(feature); + if (feature) { + // Get the current selected feature's bounding box: const [minLng, minLat, maxLng, maxLat] = bbox(feature); + + // Set the selectedFeature ID + if (feature.id !== selectedFeatureId) { + setSelectedFeature(feature); + } else { + setSelectedFeature(undefined); + } + + // Go to the newly selected feature + goToPlace([ + [minLng, minLat], + [maxLng, maxLat], + ]); + + + /** + * The following logic is used for the popup for the fullscreen feature + */ + // Create a new viewport using the current viewport dimnesions: const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!}); + + // Fit the viewport to the new bounds and return a long, lat and zoom: const {longitude, latitude, zoom} = newViewPort.fitBounds( [ [minLng, minLat], @@ -213,22 +236,21 @@ const J40Map = ({location}: IJ40Interface) => { padding: 40, }, ); - if (feature.id !== selectedFeatureId) { - setSelectedFeature(feature); - } else { - setSelectedFeature(undefined); - } + + // Save the popupInfo const popupInfo = { longitude: longitude, latitude: latitude, zoom: zoom, properties: feature.properties, }; - goToPlace([ - [minLng, minLat], - [maxLng, maxLat], - ]); + + // Update the DetailedView state variable with the new popupInfo object: setDetailViewData(popupInfo); + + /** + * End Fullscreen feature specific logic + */ } } }; @@ -242,17 +264,44 @@ const J40Map = ({location}: IJ40Interface) => { }; - const goToPlace = (bounds: LngLatBoundsLike ) => { - const {longitude, latitude, zoom} = new WebMercatorViewport({height: viewport.height!, width: viewport.width!}) - .fitBounds(bounds as [[number, number], [number, number]], { - padding: 20, - offset: [0, -100], - }); + /** + * This function will move the map (with easing) to the given lat/long bounds. + * + * When a user clicks on a tracts vs a territory button, the zoom level returned by the fitBounds + * function differ. Given that we want to handle the zoom differently depending on these two cases, we + * introduce a boolean, isTerritory that will allow the zoom level to be set depending on what the user + * is interacting with, namely a tract vs a territory button. + * + * @param {LngLatBoundsLike} bounds + * @param {boolean} isTerritory + */ + const goToPlace = (bounds: LngLatBoundsLike, isTerritory = false ) => { + const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!}); + const {longitude, latitude, zoom} = newViewPort.fitBounds( + bounds as [[number, number], [number, number]], { + // padding: 200, // removing padding and offset in favor of a zoom offset below + // offset: [0, -100], + }); + + /** + * When some tracts are selected, they end up too far zoomed in, causing some census tracts to + * only show a portion of the tract in the viewport. We reduce the zoom level by 1 to allow + * more space around the selected tract. + * + * Given that the high zoom tiles only go to zoom level 5, if the corrected zoom level (zoom - 1) is + * less than MIN_ZOOM_FEATURE_BORDER, then we floor the zoom to MIN_ZOOM_FEATURE_BORDER + .1 (which + * is 5.1 as of this comment) + */ + // eslint-disable-next-line max-len + const featureSelectionZoomLevel = (zoom - 1) < constants.GLOBAL_MIN_ZOOM_FEATURE_BORDER + .1 ? + constants.GLOBAL_MIN_ZOOM_FEATURE_BORDER : + zoom - 1; + setViewport({ ...viewport, longitude, latitude, - zoom, + zoom: isTerritory ? zoom : featureSelectionZoomLevel, transitionDuration: 1000, transitionInterpolator: new FlyToInterpolator(), transitionEasing: d3.easeCubic, @@ -434,7 +483,26 @@ const J40Map = ({location}: IJ40Interface) => { /> - {/* Enable fullscreen behind a feature flag */} + {/* This will add the navigation controls of the zoom in and zoom out buttons */} + { windowWidth > constants.USWDS_BREAKPOINTS.MOBILE_LG && } + + {/* This will show shortcut buttons to pan/zoom to US territories */} + + + {/* This places Geolocation behind a feature flag */} + {'gl' in flags ? : ''} + {geolocationInProgress ?
Geolocation in progress...
: ''} + + {/* Enable fullscreen pop-up behind a feature flag */} {('fs' in flags && detailViewData && !transitionInProgress) && ( { )} - - {/* This will add the navigation controls of the zoom in and zoom out buttons */} - { windowWidth > constants.USWDS_BREAKPOINTS.MOBILE_LG && } - - {/* This places Geolocation behind a feature flag */} - {'gl' in flags ? : ''} - {geolocationInProgress ?
Geolocation in progress...
: ''} - - {/* This will show shortcut buttons to pan/zoom to US territories */} - - {'fs' in flags ? :'' } diff --git a/client/src/data/constants.tsx b/client/src/data/constants.tsx index e24fe463..09f38ba4 100644 --- a/client/src/data/constants.tsx +++ b/client/src/data/constants.tsx @@ -204,11 +204,14 @@ export const SCORE_PROPERTY_HIGH = 'SM_PFS'; // Zoom export const GLOBAL_MIN_ZOOM = 3; export const GLOBAL_MAX_ZOOM = 22; + export const GLOBAL_MIN_ZOOM_LOW = 3; -export const GLOBAL_MAX_ZOOM_LOW = 7; -export const GLOBAL_MIN_ZOOM_HIGH = 7; +export const GLOBAL_MAX_ZOOM_LOW = 5; + +export const GLOBAL_MIN_ZOOM_HIGH = 5; export const GLOBAL_MAX_ZOOM_HIGH = 11; -export const GLOBAL_MIN_ZOOM_FEATURE_BORDER = 7; + +export const GLOBAL_MIN_ZOOM_FEATURE_BORDER = 5; export const GLOBAL_MAX_ZOOM_FEATURE_BORDER = 22; // Opacity diff --git a/data/data-pipeline/data_pipeline/ipython/TractArea.ipynb b/data/data-pipeline/data_pipeline/ipython/TractArea.ipynb new file mode 100644 index 00000000..d4b678c1 --- /dev/null +++ b/data/data-pipeline/data_pipeline/ipython/TractArea.ipynb @@ -0,0 +1,418 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 54, + "id": "df048f08", + "metadata": {}, + "outputs": [], + "source": [ + "import geopandas as gpd\n", + "import pathlib" + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "62366f7d", + "metadata": {}, + "outputs": [], + "source": [ + "lowJson = pathlib.Path() / 'usa-low.json'\n", + "assert lowJson.exists()\n", + "highJson = pathlib.Path() / 'usa-high.json'\n", + "assert highJson.exists()" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "4077ed78", + "metadata": {}, + "outputs": [], + "source": [ + "gdf = gpd.read_file(highJson)" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "d4abfc64", + "metadata": {}, + "outputs": [], + "source": [ + "gdf['area'] = gdf.apply(lambda row : gpd.GeoSeries(row['geometry']).area, axis = 1)" + ] + }, + { + "cell_type": "markdown", + "id": "5077d9ef", + "metadata": {}, + "source": [ + "Add `zlfc` = *zoom level full containment*, This field will indicate the maximum zoom level the user can go up to while still keeping the entire tract in view. Below, we sample a few tracts to get an idea of the relationship between zoom level and area" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "a1234574", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
GEOID10SFCFareazlfc
984602185000200AlaskaNorth Slope Borough53.3237024.45
993702290000100AlaskaYukon-Koyukuk Census Area21.6531545.50
985702188000100AlaskaNorthwest Arctic Borough21.1881595.50
993502290000200AlaskaYukon-Koyukuk Census Area20.7447705.38
993402290000300AlaskaYukon-Koyukuk Census Area17.1408260.00
993602290000400AlaskaYukon-Koyukuk Census Area14.6874485.77
989302180000100AlaskaNome Census Area13.3778170.00
984702164000100AlaskaLake and Peninsula Borough13.0616445.33
991802261000100AlaskaValdez-Cordova Census Area11.1188350.00
994502050000100AlaskaBethel Census Area10.9518880.00
984102270000100AlaskaWade Hampton Census Area8.7718060.00
983902240000100AlaskaSoutheast Fairbanks Census Area8.6136900.00
984302070000100AlaskaDillingham Census Area8.5753070.00
994702050000300AlaskaBethel Census Area8.4080400.00
989902170000101AlaskaMatanuska-Susitna Borough6.4804440.00
994402068000100AlaskaDenali Borough5.9972360.00
983602013000100AlaskaAleutians East Borough5.4877260.00
992102122000100AlaskaKenai Peninsula Borough4.8318316.10
985102150000100AlaskaKodiak Island Borough4.6640090.00
985002105000300AlaskaHoonah-Angoon Census Area4.3057160.00
983802016000100AlaskaAleutians West Census Area4.0535200.00
991702282000100AlaskaYakutat City and Borough3.9261820.00
992002261000300AlaskaValdez-Cordova Census Area3.2854820.00
984002240000400AlaskaSoutheast Fairbanks Census Area3.2339610.00
991902261000200AlaskaValdez-Cordova Census Area3.1563170.00
1035441045970900OregonMalheur County2.7317190.00
988802198000100AlaskaPrince of Wales-Hyder Census Area2.6062860.00
1021241025960200OregonHarney County2.5689437.08
984402185000300AlaskaNorth Slope Borough2.4631650.00
985802130000100AlaskaKetchikan Gateway Borough2.4400510.00
\n", + "
" + ], + "text/plain": [ + " GEOID10 SF CF area zlfc\n", + "9846 02185000200 Alaska North Slope Borough 53.323702 4.45\n", + "9937 02290000100 Alaska Yukon-Koyukuk Census Area 21.653154 5.50\n", + "9857 02188000100 Alaska Northwest Arctic Borough 21.188159 5.50\n", + "9935 02290000200 Alaska Yukon-Koyukuk Census Area 20.744770 5.38\n", + "9934 02290000300 Alaska Yukon-Koyukuk Census Area 17.140826 0.00\n", + "9936 02290000400 Alaska Yukon-Koyukuk Census Area 14.687448 5.77\n", + "9893 02180000100 Alaska Nome Census Area 13.377817 0.00\n", + "9847 02164000100 Alaska Lake and Peninsula Borough 13.061644 5.33\n", + "9918 02261000100 Alaska Valdez-Cordova Census Area 11.118835 0.00\n", + "9945 02050000100 Alaska Bethel Census Area 10.951888 0.00\n", + "9841 02270000100 Alaska Wade Hampton Census Area 8.771806 0.00\n", + "9839 02240000100 Alaska Southeast Fairbanks Census Area 8.613690 0.00\n", + "9843 02070000100 Alaska Dillingham Census Area 8.575307 0.00\n", + "9947 02050000300 Alaska Bethel Census Area 8.408040 0.00\n", + "9899 02170000101 Alaska Matanuska-Susitna Borough 6.480444 0.00\n", + "9944 02068000100 Alaska Denali Borough 5.997236 0.00\n", + "9836 02013000100 Alaska Aleutians East Borough 5.487726 0.00\n", + "9921 02122000100 Alaska Kenai Peninsula Borough 4.831831 6.10\n", + "9851 02150000100 Alaska Kodiak Island Borough 4.664009 0.00\n", + "9850 02105000300 Alaska Hoonah-Angoon Census Area 4.305716 0.00\n", + "9838 02016000100 Alaska Aleutians West Census Area 4.053520 0.00\n", + "9917 02282000100 Alaska Yakutat City and Borough 3.926182 0.00\n", + "9920 02261000300 Alaska Valdez-Cordova Census Area 3.285482 0.00\n", + "9840 02240000400 Alaska Southeast Fairbanks Census Area 3.233961 0.00\n", + "9919 02261000200 Alaska Valdez-Cordova Census Area 3.156317 0.00\n", + "10354 41045970900 Oregon Malheur County 2.731719 0.00\n", + "9888 02198000100 Alaska Prince of Wales-Hyder Census Area 2.606286 0.00\n", + "10212 41025960200 Oregon Harney County 2.568943 7.08\n", + "9844 02185000300 Alaska North Slope Borough 2.463165 0.00\n", + "9858 02130000100 Alaska Ketchikan Gateway Borough 2.440051 0.00" + ] + }, + "execution_count": 89, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf['zlfc'] = 0\n", + "gdf.at[9846, 'zlfc'] = 4.45\n", + "gdf.at[10212, 'zlfc'] = 7.08\n", + "gdf.at[9937, 'zlfc'] = 5.5\n", + "gdf.at[9857, 'zlfc'] = 5.5\n", + "gdf.at[9935, 'zlfc'] = 5.38\n", + "gdf.at[9936, 'zlfc'] = 5.77\n", + "gdf.at[9921, 'zlfc'] = 6.1\n", + "gdf.at[9847, 'zlfc'] = 5.33\n", + "gdf_short = gdf[[\"GEOID10\", \"SF\", \"CF\", \"area\", \"zlfc\"]]\n", + "gdf_short_sorted = gdf_short.sort_values(by='area', ascending=False);\n", + "gdf_short_sorted.head(30)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5930de0e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}