From a2d530422aed6dbcf0ae83049b37267e952cfdd4 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 08:57:06 -0500 Subject: [PATCH 1/9] Add convenience FIPS enum --- src/night_light/utils/fips.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/night_light/utils/fips.py diff --git a/src/night_light/utils/fips.py b/src/night_light/utils/fips.py new file mode 100644 index 0000000..c096d17 --- /dev/null +++ b/src/night_light/utils/fips.py @@ -0,0 +1,55 @@ +from enum import Enum + + +class StateFIPS(Enum): + ALABAMA = "01" + ALASKA = "02" + ARIZONA = "04" + ARKANSAS = "05" + CALIFORNIA = "06" + COLORADO = "08" + CONNECTICUT = "09" + DELAWARE = "10" + DISTRICT_OF_COLUMBIA = "11" + FLORIDA = "12" + GEORGIA = "13" + HAWAII = "15" + IDAHO = "16" + ILLINOIS = "17" + INDIANA = "18" + IOWA = "19" + KANSAS = "20" + KENTUCKY = "21" + LOUISIANA = "22" + MAINE = "23" + MARYLAND = "24" + MASSACHUSETTS = "25" + MICHIGAN = "26" + MINNESOTA = "27" + MISSISSIPPI = "28" + MISSOURI = "29" + MONTANA = "30" + NEBRASKA = "31" + NEVADA = "32" + NEW_HAMPSHIRE = "33" + NEW_JERSEY = "34" + NEW_MEXICO = "35" + NEW_YORK = "36" + NORTH_CAROLINA = "37" + NORTH_DAKOTA = "38" + OHIO = "39" + OKLAHOMA = "40" + OREGON = "41" + PENNSYLVANIA = "42" + RHODE_ISLAND = "44" + SOUTH_CAROLINA = "45" + SOUTH_DAKOTA = "46" + TENNESSEE = "47" + TEXAS = "48" + UTAH = "49" + VERMONT = "50" + VIRGINIA = "51" + WASHINGTON = "53" + WEST_VIRGINIA = "54" + WISCONSIN = "55" + WYOMING = "56" From bce274929447c8c40c1030504698fd699faf1125 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 08:57:21 -0500 Subject: [PATCH 2/9] Add partial for Choropleth layer --- src/night_light/utils/mapping.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/night_light/utils/mapping.py b/src/night_light/utils/mapping.py index 23e3828..d1a61e4 100644 --- a/src/night_light/utils/mapping.py +++ b/src/night_light/utils/mapping.py @@ -16,6 +16,14 @@ """, ) +Choropleth = partial( + folium.Choropleth, + fill_color="YlOrRd", + fill_opacity=0.7, + line_opacity=0.2, + line_weight=0.1, +) + LAYER_STYLE_DICT = { "fillColor": "cyan", "color": "black", From 83fcbad5a906608cfde62f2d1522f841be4edbf1 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:20:18 -0500 Subject: [PATCH 3/9] Add explanation and reference docs for population density --- docs/source/explanations/datasets.rst | 32 +++++++++++++++++-- docs/source/references/index.rst | 5 +-- .../source/references/socioeconomic/index.rst | 7 ++++ .../references/socioeconomic/population.rst | 13 ++++++++ 4 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 docs/source/references/socioeconomic/index.rst create mode 100644 docs/source/references/socioeconomic/population.rst diff --git a/docs/source/explanations/datasets.rst b/docs/source/explanations/datasets.rst index 3d190ff..0a1159f 100644 --- a/docs/source/explanations/datasets.rst +++ b/docs/source/explanations/datasets.rst @@ -5,13 +5,39 @@ Since creating a synthesized dataset based on multiple datasets is a critical pa Crosswalk Dataset -***************** +----------------- The crosswalk dataset should contain polygons that represent the boundaries of the crosswalks. The dataset ideally should accurately reflect the real world, but there is an implicit understanding that the dataset may not be perfect since most cities do not have a comprehensive dataset of crosswalks. The dataset should be in a format that can be easily read by the software, such as GeoJSON. In the testing module, you'll find the tests for fetching the crosswalk dataset and mapping it for the city of Boston. We've chosen Boston for its relative ease of access to various datasets and physical proximity to the team at Olin College of Engineering. UMass Amherst has been developing a dataset of all crosswalks in Massachusetts using computer vision model (YOLOv8) and aerial imagery. The dataset is not perfect, but it is a good starting point for our project, and has the potential to be applicable for states that also do not have a thorough catalog of their crosswalk assets. The dataset can be viewed at the `following link `_. Traffic Dataset -*************** +--------------- -Since our project is focused on pedestrian safety at nighttime on crosswalks, we need a dataset that contains information about the volume of traffic. MassDOT provides a convenient dataset that includes average annual daily traffic (AADT) counts for most roads in Massachusetts. The counts will be used to inform the risk of a pedestrian being hit by a car at a given crosswalk. The dataset can be viewed at the `following link `_. \ No newline at end of file +Since our project is focused on pedestrian safety at nighttime on crosswalks, we need a dataset that contains information about the volume of traffic. MassDOT provides a convenient dataset that includes average annual daily traffic (AADT) counts for most roads in Massachusetts. The counts will be used to inform the risk of a pedestrian being hit by a car at a given crosswalk. The dataset can be viewed at the `following link `_. + +Population Density Dataset +-------------------------- + +The population density data is sourced from the American Community Survey (ACS) 5-year estimates, which provide reliable demographic information on U.S. populations. The ACS data is integrated with geographic data from census tract boundaries to compute the area of each tract and derive population density values. + +Density Calculation Methodology +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +1. **Geographic Area Calculation**: + Each census tract is represented by a polygon geometry, and its area is calculated in square kilometers. To ensure accuracy, geometries are reprojected to a projected coordinate system (EPSG:3857) before calculating areas. + +2. **Population Segments**: + The dataset includes specific population segments based on age and disability + status: + + - **Total Population**: Total number of individuals residing within each tract. + - **Senior Population (65+)**: The population aged 65 years and older. + - **Youth Population (0-17)**: The population aged 17 years and younger. + - **Disabled Population**: Individuals identified as having a disability. + +3. **Density Calculation**: + Population density for each segment is calculated by dividing the total + population or segment-specific population by the area of each tract in + square kilometers. This results in values representing the density of each + population segment (e.g., seniors per kmĀ²). diff --git a/docs/source/references/index.rst b/docs/source/references/index.rst index 7c6dce0..48ab650 100644 --- a/docs/source/references/index.rst +++ b/docs/source/references/index.rst @@ -1,9 +1,10 @@ References -========================= +========== .. toctree:: :maxdepth: 2 :caption: Contents: - utils/index \ No newline at end of file + utils/index + socioeconomic/index \ No newline at end of file diff --git a/docs/source/references/socioeconomic/index.rst b/docs/source/references/socioeconomic/index.rst new file mode 100644 index 0000000..d1613de --- /dev/null +++ b/docs/source/references/socioeconomic/index.rst @@ -0,0 +1,7 @@ +Socioeconomic Module +==================== + +.. toctree:: + :maxdepth: 1 + + population \ No newline at end of file diff --git a/docs/source/references/socioeconomic/population.rst b/docs/source/references/socioeconomic/population.rst new file mode 100644 index 0000000..bb901ed --- /dev/null +++ b/docs/source/references/socioeconomic/population.rst @@ -0,0 +1,13 @@ +Population Density +================== + +.. automodule:: night_light.socioeconomic.population + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: night_light.socioeconomic.population_density + :members: + :undoc-members: + :show-inheritance: From 6141ddc26b46ec8b7bf2c595e8654fc8a811c900 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:21:10 -0500 Subject: [PATCH 4/9] Add utility function for fetching shapes data --- src/night_light/utils/shapes.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/night_light/utils/shapes.py diff --git a/src/night_light/utils/shapes.py b/src/night_light/utils/shapes.py new file mode 100644 index 0000000..43861b9 --- /dev/null +++ b/src/night_light/utils/shapes.py @@ -0,0 +1,14 @@ +from pygris import tracts +from pygris import places + +from night_light.utils.fips import StateFIPS + + +def get_tract_shapes(state: StateFIPS, year: int = 2021): + """Get tract shapes data""" + return tracts(state=state.value, cb=True, cache=True, year=year) + + +def get_place_shapes(state: StateFIPS, year: int = 2021): + """Get place shapes data""" + return places(state=state.value, cb=True, cache=True, year=year) From 94c3e359b8d8f292d26e7f166addac9df5cb2710 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:21:33 -0500 Subject: [PATCH 5/9] Add population fetching function --- src/night_light/socioeconomic/population.py | 150 ++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/night_light/socioeconomic/population.py diff --git a/src/night_light/socioeconomic/population.py b/src/night_light/socioeconomic/population.py new file mode 100644 index 0000000..b97367d --- /dev/null +++ b/src/night_light/socioeconomic/population.py @@ -0,0 +1,150 @@ +from pygris import tracts +from pygris.data import get_census +from night_light.utils.fips import StateFIPS + +mass_tracts = tracts(state="MA", cb=True, cache=True, year=2021) + + +def get_population( + year: int = 2021, state: StateFIPS = StateFIPS.MASSACHUSETTS, **kwargs +): + """ + Fetch population data for a given year and state using the ACS 5-year survey. + + This function retrieves population data for a specific state and year from the + American Community Survey (ACS) 5-year dataset. It allows for additional filtering + by specific population segments, including seniors, youths, and disabled + individuals, if specified in the keyword arguments. + + Args: + year (int, optional): The year for which to fetch the data. Defaults to 2021. + state (StateFIPS, optional): The state for which to fetch the data, using FIPS + code Enum. Defaults to StateFIPS.MASSACHUSETTS. + + Keyword Args: + seniors (bool, optional): If True, fetches the senior population (ages 65+). + youths (bool, optional): If True, fetches the youth population (ages 0-17). + disabled (bool, optional): If True, fetches the disabled population. + + Returns: + pd.DataFrame: A DataFrame containing the population data by census tract, + with columns for: + - "GEOID" + - "total_population" + - "senior_population" (if seniors=True) + - "youth_population" (if youths=True) + - "disabled_population" (if disabled=True) + """ + seniors = kwargs.pop("seniors", False) + youths = kwargs.pop("youths", False) + disabled = kwargs.pop("disabled", False) + variables = ["B01003_001E"] + if seniors: + variables.extend( + [ + "B01001_020E", + "B01001_021E", + "B01001_022E", + "B01001_023E", + "B01001_024E", + "B01001_025E", + "B01001_044E", + "B01001_045E", + "B01001_046E", + "B01001_047E", + "B01001_048E", + "B01001_049E", + ] + ) + if youths: + variables.extend( + [ + "B01001_003E", + "B01001_004E", + "B01001_005E", + "B01001_006E", + "B01001_027E", + "B01001_028E", + "B01001_029E", + "B01001_030E", + ] + ) + + if disabled: + variables.append("B18101_001E") + + df = get_census( + year=year, + variables=variables, + params={ + "for": "tract:*", + "in": f"state:{state.value}", + }, + dataset="acs/acs5", + return_geoid=True, + guess_dtypes=True, + ) + + df["total_population"] = df["B01003_001E"] + df.drop(columns=["B01003_001E"], inplace=True) + if seniors: + df["senior_population"] = ( + df["B01001_020E"] + + df["B01001_021E"] + + df["B01001_022E"] + + df["B01001_023E"] + + df["B01001_024E"] + + df["B01001_025E"] + + df["B01001_044E"] + + df["B01001_045E"] + + df["B01001_046E"] + + df["B01001_047E"] + + df["B01001_048E"] + + df["B01001_049E"] + ) + df.drop( + columns=[ + "B01001_020E", + "B01001_021E", + "B01001_022E", + "B01001_023E", + "B01001_024E", + "B01001_025E", + "B01001_044E", + "B01001_045E", + "B01001_046E", + "B01001_047E", + "B01001_048E", + "B01001_049E", + ], + inplace=True, + ) + if youths: + df["youth_population"] = ( + df["B01001_003E"] + + df["B01001_004E"] + + df["B01001_005E"] + + df["B01001_006E"] + + df["B01001_027E"] + + df["B01001_028E"] + + df["B01001_029E"] + + df["B01001_030E"] + ) + df.drop( + columns=[ + "B01001_003E", + "B01001_004E", + "B01001_005E", + "B01001_006E", + "B01001_027E", + "B01001_028E", + "B01001_029E", + "B01001_030E", + ], + inplace=True, + ) + if disabled: + df["disabled_population"] = df["B18101_001E"] + df.drop(columns=["B18101_001E"], inplace=True) + + return df From a6db92ee8abe31e6c1b0c325d08ce71d48299079 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:21:39 -0500 Subject: [PATCH 6/9] Add population density fetching function --- .../socioeconomic/population_density.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/night_light/socioeconomic/population_density.py diff --git a/src/night_light/socioeconomic/population_density.py b/src/night_light/socioeconomic/population_density.py new file mode 100644 index 0000000..9c48858 --- /dev/null +++ b/src/night_light/socioeconomic/population_density.py @@ -0,0 +1,66 @@ +from night_light.socioeconomic.population import get_population +from night_light.utils.fips import StateFIPS +from night_light.utils.shapes import get_tract_shapes + + +def get_population_density( + year: int = 2021, state: StateFIPS = StateFIPS.MASSACHUSETTS, **kwargs +): + """ + Calculate population density using ACS 5-year survey data. + + This function retrieves population data and geographic shapes for census tracts + within the specified state, calculates the area of each tract, and computes + population density. Additional segments of the population (seniors, youths, and + disabled individuals) can be included if specified, providing corresponding density + values. + + Args: + year (int, optional): The year for which to fetch the population data. Defaults + to 2021. + state (StateFIPS, optional): The state for which to calculate population + density, specified using FIPS code Enum. Defaults to + `StateFIPS.MASSACHUSETTS`. + + Keyword Args: + seniors (bool, optional): If True, includes senior population density (ages + 65+). + youths (bool, optional): If True, includes youth population density (ages 0-17). + disabled (bool, optional): If True, includes disabled population density. + + Returns: + gpd.GeoDataFrame: A GeoDataFrame containing population density data by census + tract, with columns for: + - "GEOID" + - "area_km2" + - "total_population_density" + - "senior_population_density" (if seniors=True) + - "youth_population_density" (if youths=True) + - "disabled_population_density" (if disabled=True) + """ + df_pop = get_population(year, state, **kwargs) + tract_shapes = get_tract_shapes(state) + tract_shapes.to_crs("EPSG:3857", inplace=True) + tract_shapes["area_km2"] = tract_shapes.area / 1e6 + + df_pop_density = tract_shapes.merge(df_pop, how="inner", on="GEOID") + if kwargs.get("seniors"): + df_pop_density["senior_population_density"] = ( + df_pop_density["senior_population"] / tract_shapes["area_km2"] + ) + + if kwargs.get("youths"): + df_pop_density["youth_population_density"] = ( + df_pop_density["youth_population"] / tract_shapes["area_km2"] + ) + + if kwargs.get("disabled"): + df_pop_density["disabled_population_density"] = ( + df_pop_density["disabled_population"] / tract_shapes["area_km2"] + ) + + df_pop_density["total_population_density"] = ( + df_pop_density["total_population"] / tract_shapes["area_km2"] + ) + + return df_pop_density.to_crs("EPSG:4326") From 2490e344a165ca9a6daf8a2c27f2754a1ab2efca Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:28:46 -0500 Subject: [PATCH 7/9] Add tests for querying and saving population density data --- tests/test_population_density_query.py | 97 ++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/test_population_density_query.py diff --git a/tests/test_population_density_query.py b/tests/test_population_density_query.py new file mode 100644 index 0000000..4d54a04 --- /dev/null +++ b/tests/test_population_density_query.py @@ -0,0 +1,97 @@ +import os + +from night_light.socioeconomic.population_density import get_population_density +from night_light.utils import query_geojson +from night_light.utils.fips import StateFIPS + + +def test_population_density_query(): + """Test querying population density data.""" + data = get_population_density(year=2021, state=StateFIPS.MASSACHUSETTS) + assert not data.empty + assert "GEOID" in data.columns + assert "area_km2" in data.columns + assert "total_population_density" in data.columns + assert "senior_population_density" not in data.columns + assert "youth_population_density" not in data.columns + assert "disabled_population_density" not in data.columns + + +def test_senior_population_density_query(): + """Test querying senior population density data.""" + data = get_population_density( + year=2021, state=StateFIPS.MASSACHUSETTS, seniors=True + ) + assert not data.empty + assert "GEOID" in data.columns + assert "area_km2" in data.columns + assert "total_population_density" in data.columns + assert "senior_population_density" in data.columns + assert "youth_population_density" not in data.columns + assert "disabled_population_density" not in data.columns + + +def test_youth_population_density_query(): + """Test querying youth population density data.""" + data = get_population_density(year=2021, state=StateFIPS.MASSACHUSETTS, youths=True) + assert not data.empty + assert "GEOID" in data.columns + assert "area_km2" in data.columns + assert "total_population_density" in data.columns + assert "senior_population_density" not in data.columns + assert "youth_population_density" in data.columns + assert "disabled_population_density" not in data.columns + + +def test_disabled_population_density_query(): + """Test querying disabled population density data.""" + data = get_population_density( + year=2021, state=StateFIPS.MASSACHUSETTS, disabled=True + ) + assert not data.empty + assert "GEOID" in data.columns + assert "area_km2" in data.columns + assert "total_population_density" in data.columns + assert "senior_population_density" not in data.columns + assert "youth_population_density" not in data.columns + assert "disabled_population_density" in data.columns + + +def test_all_population_density_query(): + """Test querying all population density data.""" + data = get_population_density( + year=2021, + state=StateFIPS.MASSACHUSETTS, + seniors=True, + youths=True, + disabled=True, + ) + assert not data.empty + assert "GEOID" in data.columns + assert "area_km2" in data.columns + assert "total_population_density" in data.columns + assert "senior_population_density" in data.columns + assert "youth_population_density" in data.columns + assert "disabled_population_density" in data.columns + + +def test_all_population_density_geojson(): + """Test saving all population density to a GeoJSON file""" + data = get_population_density( + year=2021, + state=StateFIPS.MASSACHUSETTS, + seniors=True, + youths=True, + disabled=True, + ) + geojson_filename = "test_all_population_density.geojson" + query_geojson.save_geojson(data, geojson_filename) + saved_gdf = query_geojson.gpd.read_file(geojson_filename) + + assert data.crs == saved_gdf.crs + assert set(data.columns) == set(saved_gdf.columns) + assert data.index.equals(saved_gdf.index) + assert data.shape == saved_gdf.shape + + os.remove(geojson_filename) + assert not os.path.exists(geojson_filename) From 65968d099f92abaebbaba3653882cb3fcea2172a Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:29:17 -0500 Subject: [PATCH 8/9] Add tests for mapping population density as choropleth --- tests/test_population_density_mapping.py | 212 +++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 tests/test_population_density_mapping.py diff --git a/tests/test_population_density_mapping.py b/tests/test_population_density_mapping.py new file mode 100644 index 0000000..488f001 --- /dev/null +++ b/tests/test_population_density_mapping.py @@ -0,0 +1,212 @@ +import os +import time + +import folium + +from night_light.socioeconomic.population_density import get_population_density +from night_light.utils import create_folium_map, open_html_file, Tooltip +from night_light.utils.fips import StateFIPS +from night_light.utils.mapping import Choropleth + + +def test_population_density_choropleth(): + """Test creating a population density choropleth map.""" + map_filename = "test_population_density_map.html" + population_density_data = get_population_density( + year=2021, state=StateFIPS.MASSACHUSETTS + ) + + density_layer = Choropleth( + geo_data=population_density_data, + name="Population Density Choropleth", + data=population_density_data, + columns=["GEOID", "total_population_density"], + key_on="feature.properties.GEOID", + ) + + tooltip_layer = folium.GeoJson( + population_density_data, + name="Population Density Tooltips", + style_function=lambda x: {"fillColor": "transparent", "color": "transparent"}, + tooltip=Tooltip( + fields=["NAMELSAD", "total_population_density"], + aliases=["Tract Name", "Density"], + ), + ) + + create_folium_map( + layers=[density_layer, tooltip_layer], + center=[42.4072, -71.3824], + zoom_start=9, + map_filename=map_filename, + ) + + assert os.path.exists(map_filename) + open_html_file(map_filename) + time.sleep(1) + os.remove(map_filename) + assert not os.path.exists(map_filename) + + +def test_senior_population_density_choropleth(): + map_filename = "test_senior_population_density_map.html" + population_density_data = get_population_density( + year=2021, state=StateFIPS.MASSACHUSETTS, seniors=True + ) + + density_layer = Choropleth( + geo_data=population_density_data, + name="Senior Population Density Choropleth", + data=population_density_data, + columns=["GEOID", "senior_population_density"], + key_on="feature.properties.GEOID", + ) + + tooltip_layer = folium.GeoJson( + population_density_data, + name="Senior Population Density Tooltips", + style_function=lambda x: {"fillColor": "transparent", "color": "transparent"}, + tooltip=Tooltip( + fields=["NAMELSAD", "senior_population_density"], + aliases=["Tract Name", "Density"], + ), + ) + + create_folium_map( + layers=[density_layer, tooltip_layer], + center=[42.4072, -71.3824], + zoom_start=9, + map_filename=map_filename, + ) + + assert os.path.exists(map_filename) + open_html_file(map_filename) + time.sleep(1) + os.remove(map_filename) + assert not os.path.exists(map_filename) + + +def test_youth_population_density_choropleth(): + """Test creating a youth population density choropleth map.""" + map_filename = "test_youth_population_density_map.html" + population_density_data = get_population_density( + year=2021, state=StateFIPS.MASSACHUSETTS, youths=True + ) + + density_layer = Choropleth( + geo_data=population_density_data, + name="Youth Population Density Choropleth", + data=population_density_data, + columns=["GEOID", "youth_population_density"], + key_on="feature.properties.GEOID", + ) + + tooltip_layer = folium.GeoJson( + population_density_data, + name="Youth Population Density Tooltips", + style_function=lambda x: {"fillColor": "transparent", "color": "transparent"}, + tooltip=Tooltip( + fields=["NAMELSAD", "youth_population_density"], + aliases=["Tract Name", "Density"], + ), + ) + + create_folium_map( + layers=[density_layer, tooltip_layer], + center=[42.4072, -71.3824], + zoom_start=9, + map_filename=map_filename, + ) + + assert os.path.exists(map_filename) + open_html_file(map_filename) + time.sleep(1) + os.remove(map_filename) + assert not os.path.exists(map_filename) + + +def test_disabled_population_density_choropleth(): + """Test creating a disabled population density choropleth map.""" + map_filename = "test_disabled_population_density_map.html" + population_density_data = get_population_density( + year=2021, state=StateFIPS.MASSACHUSETTS, disabled=True + ) + + density_layer = Choropleth( + geo_data=population_density_data, + name="Disabled Population Density Choropleth", + data=population_density_data, + columns=["GEOID", "disabled_population_density"], + key_on="feature.properties.GEOID", + ) + + tooltip_layer = folium.GeoJson( + population_density_data, + name="Disabled Population Density Tooltips", + style_function=lambda x: {"fillColor": "transparent", "color": "transparent"}, + tooltip=Tooltip( + fields=["NAMELSAD", "disabled_population_density"], + aliases=["Tract Name", "Density"], + ), + ) + + create_folium_map( + layers=[density_layer, tooltip_layer], + center=[42.4072, -71.3824], + zoom_start=9, + map_filename=map_filename, + ) + + assert os.path.exists(map_filename) + open_html_file(map_filename) + time.sleep(1) + os.remove(map_filename) + assert not os.path.exists(map_filename) + + +def test_vulnerable_population_density_choropleth(): + """Test creating a vulnerable population density choropleth map.""" + map_filename = "test_vulnerable_population_density_map.html" + population_density_data = get_population_density( + year=2021, + state=StateFIPS.MASSACHUSETTS, + seniors=True, + youths=True, + disabled=True, + ) + population_density_data["vulnerable_population_density"] = ( + population_density_data["senior_population_density"] + + population_density_data["youth_population_density"] + + population_density_data["disabled_population_density"] + ) + density_layer = Choropleth( + geo_data=population_density_data, + name="Vulnerable Population Density Choropleth", + data=population_density_data, + columns=["GEOID", "vulnerable_population_density"], + key_on="feature.properties.GEOID", + legend_name="Vulnerable Population Density", + ) + + tooltip_layer = folium.GeoJson( + population_density_data, + name="Vulnerable Population Density Tooltips", + style_function=lambda x: {"fillColor": "transparent", "color": "transparent"}, + tooltip=Tooltip( + fields=["NAMELSAD", "vulnerable_population_density"], + aliases=["Tract Name", "Density"], + ), + ) + + create_folium_map( + layers=[density_layer, tooltip_layer], + center=[42.4072, -71.3824], + zoom_start=9, + map_filename=map_filename, + ) + + assert os.path.exists(map_filename) + open_html_file(map_filename) + time.sleep(1) + os.remove(map_filename) + assert not os.path.exists(map_filename) From 3407c29342c38f5a0f105c344b472388492130d8 Mon Sep 17 00:00:00 2001 From: Daeyoung Kim Date: Tue, 12 Nov 2024 09:29:40 -0500 Subject: [PATCH 9/9] Add `pygris` to requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b3125..1adeed0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ requests~=2.32.3 geopandas~=1.0.1 folium~=0.18.0 pytest~=8.3.3 -sphinx~=8.1.3 \ No newline at end of file +sphinx~=8.1.3 +pygris~=0.1.6 \ No newline at end of file