Project Assignment 3

Pratheeksha Nayak

Identifying hotspots of vulnerability during hurricane storm-surge inundation (Python implementation)

Part 2: For Citizens

  • This part adopts a citizen-centric approach, aiming to empower individuals with actionable information in the event of a storm.
  • Lets users input a specific point location and identifies the category of storm that would affect that area.
  • Also shows the pharmacies within a one-mile radius, highlighting those that would be inundated by storm surge waters.
  • This information can be vital for individuals needing access to essential services like pharmacies during emergencies.

Data Sources

  1. Pharmacy location in DC:
  • Source: Open Data DC portal ((https://opendata.dc.gov/)).
  • This data contains shapefile with locations of licensed pharmacies in DC.
In [1]:
# Import required libraries

from geopandas import read_file, gpd
import folium
from shapely.geometry import Point
from termcolor import colored  
In [2]:
# gpd.__version__
# !pip install geopandas==0.10.2
# !pip install folium==0.16.0
# !pip install shapely==1.8.1
# !pip install numpy==1.23.5
In [3]:
# Surge Areas Data
input_file = './Data/StormSurgeRiskAreas/Storm_Surge_Risk_Areas.shp'
surge = gpd.read_file(input_file)
surge['HES_ZONE'].replace(99, 5, inplace=True)

Pharmacy Locations Data

In [4]:
input_file = './Data/Pharmacy_Locations/Pharmacy_Locations.shp'
pharma = gpd.read_file(input_file)
pharma.tail()
Out[4]:
NAME LICENSE_NU ADDRESS ADDRESSLIN CITY STATE ZIPCODE PHONE WARD SSL ... SE_ANNO_CA GLOBALID CREATOR CREATED EDITOR EDITED OBJECTID ANC GIS_LAST_M geometry
142 CVS/PHARMACY #2104 RX1000401 5013 CONNECTICUT AVE NW None Washington DC 20008 401-765-1500 Ward 3 1986 0008 ... None {239311D5-3B07-2747-E063-782F520A1BEB} None None None None 5415 ANC 3F 2024-10-03 POINT (-8579339.244 4715280.755)
143 NAI SATURN EASTERN LLC DBA SAFEWAY PHARMACY #4832 RX0000082 5545 CONNECTICUT AVE NW None Washington DC 20015 208-395-4303 Ward 3 1867 0092 ... None {239311D5-3B08-2747-E063-782F520A1BEB} None None None None 5416 ANC 3/4G 2024-10-03 POINT (-8579939.698 4716602.197)
144 AVITA PHARMACY 1071 RX2300176 600 PENNSYLVANIA AVENUE, SE SUITE LOWER LEVEL A Washington DC 20003 202-978-1565 Ward 6 0873 0112 ... None {239311D5-3B09-2747-E063-782F520A1BEB} None None None None 5417 ANC 6B 2024-10-03 POINT (-8571345.692 4705309.692)
145 CVS PHARMACY #10685 RX0000100 675 K STREET NW None Washington DC 20001 401-765-1500 Ward 6 0451 0033 ... None {239311D5-3B0A-2747-E063-782F520A1BEB} None None None None 5418 ANC 6E 2024-10-03 POINT (-8573972.893 4707756.592)
146 GENOA HEALTHCARE LLC RX1800122 801 PENNSYLVANIA AVE SE SUITE 120 Washington DC 20003 763-330-5827 Ward 6 0925 0026 ... None {239311D5-3B0B-2747-E063-782F520A1BEB} None None None None 5419 ANC 6B 2024-10-03 POINT (-8570992.933 4705018.681)

5 rows × 24 columns

In [5]:
# Reproject
print(pharma.crs)
pharma = pharma.to_crs(4326)
epsg:3857

Locations of all pharmacies in DC

Click on the map to select point location for further analysis.

In [6]:
center_lat = pharma.geometry.y.mean()
center_lon = pharma.geometry.x.mean()
m = folium.Map(location=[center_lat, center_lon], tiles="CartoDB positron", zoom_start=12)
pharma.explore(m=m, name="pharmacy locations")
m.add_child(folium.ClickForLatLng(format_str='"[" + lat + "," + lng + "]"', alert=True))
m
Out[6]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [7]:
def get_category_surge(category):
    surge_category = surge[surge['HES_ZONE']<=category]
    return surge_category

def get_category_pharma(category):
    pharma_category = gpd.clip(pharma, get_category_surge(category))
    return pharma_category
In [8]:
def create_buffer(clicked_point):
    # Convert the clicked point into a GeoSeries
    click_geom = gpd.GeoSeries([Point(clicked_point[1], clicked_point[0])], crs="EPSG:4326")

    # Find category of storm that would affect the given location
    for idx, row in surge.iterrows():
        category = row['HES_ZONE']
        if click_geom.geometry[0].within(row['geometry']):
            print(colored(f"Your location is affected by storm category {category} or above!", "red", attrs=["bold"]))
            break
    else:
        print(colored("Your location is not affected by storm surge", "green", attrs=["bold"]))

    # Plot user-input location    
    click_geom.explore(m=m, name="selected point", marker_kwds=dict(radius=4), style_kwds={"color":"black", "fillColor":"black"})
    
    # Reproject to UTM for accurate distance-based buffering
    click_geom_utm = click_geom.to_crs(pharma.estimate_utm_crs())
    
    # Create a buffer of one mile (m)
    buffer = click_geom_utm.buffer(1609)  # one mile buffer
    
    # Reproject back to EPSG:4326
    buffer_wgs84 = buffer.to_crs(epsg=4326)
    
    # Plot buffer
    buffer_wgs84.explore(m=m, name="buffer", style_kwds={"fill":True, "fillOpacity":0.3})
    
    # Find points from pharma within the buffer
    points_in_buffer = pharma[pharma.geometry.within(buffer_wgs84.iloc[0])]
    
    # Find points that are inundated
    pharma_category = get_category_pharma(category)        
    buffer_points_in_category = points_in_buffer[points_in_buffer['OBJECTID'].isin(pharma_category['OBJECTID'])]

    # Plot points in buffer in green and inundated pharmacies in red
    if len(points_in_buffer)>0:
        points_in_buffer.explore(m=m, name="pharmacies in buffer zone", style_kwds={"color":"green", "weight":3, "fillColor":"green"})
        if len(buffer_points_in_category)>0:
            buffer_points_in_category.explore(m=m, name="inundated pharmacies", style_kwds={"color":"red", "weight":3, "fillColor":"red"})
            #Plot surge inundated area for the category of storm for given location 
            surge_category = get_category_surge(category)
            surge_category.explore(m=m, name="storm surge areas", 
                                   style_kwds=dict(color="black", weight=0.5, opacity=0.3, fillColor='yellow', fillOpacity=0.3))
    
    # Print message if no points found
    else:
        print('No pharmacies in buffer zone, Expand search zone')
        
    return points_in_buffer, category, m

Example 1

In [9]:
point = [38.878740,-77.020340]
pts, cat, m = create_buffer(point)
folium.LayerControl().add_to(m)
m
Your location is affected by storm category 5 or above!
Out[9]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Example 2

In [10]:
m = folium.Map(location=[center_lat, center_lon], tiles="CartoDB positron", zoom_start=12)

point = [38.939918,-77.041626]
pts, cat, m = create_buffer(point)
folium.LayerControl().add_to(m)
m
Your location is not affected by storm surge
Out[10]:
Make this Notebook Trusted to load map: File -> Trust Notebook
  • Example 1 shows a point inundated by category 5 storm: For the point location given (black) by the user, the category of storm that would impact the location is provided (see message on top). The pharmacies within a 1 mile radius are marked in green. Of these, the pharmacies which would be inundated by the same category of storm are marked in red. The results are validated by overplotting the surge inundated area for the category (yellow).
  • Example 2 shows a point that is not inundated in any of the storm categories. The pharmacies within a 1 mile radius are marked in green.
In [ ]: