mirror of
https://github.com/dylanbenzi/hurricane_normalization_app.git
synced 2026-07-29 21:01:27 +00:00
536 lines
16 KiB
R
536 lines
16 KiB
R
library(bslib)
|
||
|
||
download_card <- function(
|
||
title,
|
||
description,
|
||
data_coverage,
|
||
last_updated,
|
||
data_id
|
||
) {
|
||
return(
|
||
card(
|
||
fill = F,
|
||
layout_columns(
|
||
col_widths = c(10, 2),
|
||
strong(title),
|
||
actionButton(
|
||
class = "btn-primary btn-sm",
|
||
icon = icon("download"),
|
||
paste("download", data_id, sep = "-"),
|
||
"Download"
|
||
)
|
||
),
|
||
p(description),
|
||
div(
|
||
class = "d-flex",
|
||
div(
|
||
class = "d-flex me-4",
|
||
bs_icon("database-fill", class = "text-muted me-1"),
|
||
h6(data_coverage)
|
||
),
|
||
div(
|
||
class = "d-flex me-4",
|
||
bs_icon("calendar-event-fill", class = "text-muted me-1"),
|
||
h6(last_updated)
|
||
)
|
||
)
|
||
)
|
||
)
|
||
}
|
||
|
||
# cost norm, economic factors, fatalities, svis, rmw county weights, landfalls
|
||
download_cards <- list(
|
||
download_card(
|
||
"Cyclone Cost Normalization",
|
||
"Normalized economic damage estimates for US landfalling hurricanes 1900-2023 using updated MMH/MMP methodology.",
|
||
"1900-2023",
|
||
"8/03/2025",
|
||
"cost_norm"
|
||
),
|
||
download_card(
|
||
"Economic Factors",
|
||
"Yearly economic factors for GDP deflator and Current-Cost Net Stock of Fixed Assets and Consumer Durable Goods (CCNFACDG) used in the MMH/MMP cost normalization methodology.",
|
||
"1900-2024",
|
||
"-/-/2025",
|
||
"usa_econ"
|
||
),
|
||
download_card(
|
||
"Tracked Landfalls",
|
||
"Landfalls with identifiers, RMW county weights, and time/location data for all storms normalized using the MMH/MMP methodology.",
|
||
"1900-2024",
|
||
"3/15/2026",
|
||
"landfalls"
|
||
),
|
||
download_card(
|
||
"Cyclone Fatalities",
|
||
"All known direct fatalities with peril categories from tropical cyclones",
|
||
"1963-2024",
|
||
"3/31/2026",
|
||
"fatalities"
|
||
),
|
||
download_card(
|
||
"Social Vulnerability Index",
|
||
"County/tract level data representing the vulnerability of people in that region",
|
||
"X-X",
|
||
"...",
|
||
"svi"
|
||
),
|
||
download_card(
|
||
"Additional Data",
|
||
"We can add more here later",
|
||
"X-X",
|
||
"...",
|
||
"additional"
|
||
)
|
||
)
|
||
|
||
ui <- page_navbar(
|
||
id = "main_navbar",
|
||
fillable = TRUE,
|
||
title = "Tropical Cyclone Database",
|
||
theme = bs_theme(
|
||
bootswatch = "sandstone"
|
||
),
|
||
nav_panel(
|
||
"Home",
|
||
layout_columns(
|
||
col_widths = c(6, 6),
|
||
card(
|
||
HTML(
|
||
'
|
||
<h4>Welcome to the Hurricane Cost Normalization Web App</h4>
|
||
|
||
<p>Our platform provides access to normalized hurricane damage data spanning from 1900 to 2024, allowing researchers, policymakers, insurance professionals, and the public to better understand how hurricane costs have changed over time.</p>
|
||
|
||
<h6>Our Data</h6>
|
||
|
||
<p>The core datasets used in this app are based on research by Muller et al. (2025) published in <i>Bulletin of the American Meteorlogical Society</i>. This study updates and refines hurricane damage normalization methodologies to provide a more accurate picture of how historical hurricanes would impact today\'s society.</p>
|
||
|
||
<ul>
|
||
<li><b>MMP24:</b> The Muller-Mooney Population (2024) normalization with RMW weighting on affected population.</li>
|
||
|
||
<li><b>MMH24:</b> The Muller-Mooney Housing (2024) normalization with RMW weighting on affected housing units.</li>
|
||
</ul>
|
||
|
||
<h6>Methodology Innovations</h6>
|
||
|
||
<p>Our platform incorporates several methodological innovations over previously used cost normalization formulas:</p>
|
||
|
||
<ul>
|
||
<li><b>Radius of Maximum Wind (RMW) Data:</b> Using landfalling RMWs to identify impacted coastal counties.</li>
|
||
|
||
<li><b>RMW Affected Area Weighting:</b> Determining affected population and housing unit figures based on RMW.</li>
|
||
|
||
<li><b>Expanded Storm Coverage:</b> Including over 200 storms analyzed with interactive data.</li>
|
||
|
||
<li><b>Up-to-date Data:</b> Using the latest population, housing unit, and economic data through 2024.</li>
|
||
</ul>
|
||
'
|
||
)
|
||
),
|
||
layout_columns(
|
||
col_widths = 12,
|
||
card(
|
||
HTML(
|
||
'
|
||
<h4>How to Use This App</h4>
|
||
|
||
<h6>1. Find a Storm — Storm Selector</h6>
|
||
<p>Browse and filter the full storm database. Use the search bar to find a storm by name, or narrow results by year range, available data types, and normalized cost or fatality thresholds. Click a row to preview storm statistics, then press <b>Load Storm</b> to explore it in detail.</p>
|
||
|
||
<h6>2. Explore Storm Data — Storm Explorer</h6>
|
||
<p>Once a storm is loaded, four analysis tabs become available:</p>
|
||
<ul>
|
||
<li><b>Track:</b> Interactive map of the storm\'s full HURDAT2 track with meteorological observations and a time-series chart of windspeed and pressure.</li>
|
||
|
||
<li><b>Cost Normalization:</b> View MMH24 and MMP24 normalized damage indices and losses over time for each landfall. Toggle between index and dollar values, linear and log scale, and individual landfalls.</li>
|
||
|
||
<li><b>Human Factors:</b> Animated county-level map showing population and housing unit growth in the affected area from the storm year through 2024.</li>
|
||
|
||
<li><b>Fatality Map:</b> State-level direct fatality breakdown by cause, with a category pie chart and state-by-cause heatmap.</li>
|
||
</ul>
|
||
|
||
<h6>Data Availability</h6>
|
||
<p>All storms include HURDAT2 track data. Cost normalization and fatality data are available for a subset of storms, indicated by the <span class="badge bg-success">Cost</span> and <span class="badge bg-danger">Fatality</span> badges in the Storm Selector.</p>
|
||
'
|
||
)
|
||
),
|
||
card(
|
||
"Contact Us",
|
||
card_body(
|
||
"CONTACT US INFO"
|
||
)
|
||
)
|
||
),
|
||
)
|
||
),
|
||
nav_panel(
|
||
"Storm Selector",
|
||
layout_columns(
|
||
col_widths = c(8, 4),
|
||
card(
|
||
div(
|
||
class = "px-1 pt-1",
|
||
layout_columns(
|
||
col_widths = c(6, 6),
|
||
textInput(
|
||
"table_search",
|
||
"Storm Search",
|
||
placeholder = "Search storm name...",
|
||
width = "100%"
|
||
),
|
||
div(
|
||
tags$label(class = "form-label", "Data Availability"),
|
||
layout_columns(
|
||
col_widths = c(4, 4, 4),
|
||
tags$div(
|
||
class = "btn-group w-100",
|
||
role = "group",
|
||
tags$button(
|
||
class = "btn btn-primary w-100",
|
||
style = "pointer-events: none;",
|
||
"HURDAT2"
|
||
)
|
||
),
|
||
checkboxGroupButtons(
|
||
"filter_cost",
|
||
label = NULL,
|
||
choices = "Cost",
|
||
selected = character(0),
|
||
status = "outline-success",
|
||
justified = TRUE,
|
||
width = "100%"
|
||
),
|
||
checkboxGroupButtons(
|
||
"filter_fatality",
|
||
label = NULL,
|
||
choices = "Fatality",
|
||
selected = character(0),
|
||
status = "outline-danger",
|
||
justified = TRUE,
|
||
width = "100%"
|
||
)
|
||
)
|
||
)
|
||
),
|
||
layout_columns(
|
||
col_widths = c(6, 6),
|
||
sliderInput(
|
||
"dt_year_filter",
|
||
"Year",
|
||
min = 1900,
|
||
max = 2024,
|
||
value = c(1900, 2024),
|
||
sep = "",
|
||
step = 1,
|
||
ticks = FALSE,
|
||
width = "100%"
|
||
),
|
||
numericRangeInput(
|
||
"dt_deaths_filter",
|
||
"Direct Deaths",
|
||
value = c(0, dt_death_max),
|
||
min = 0,
|
||
separator = "–",
|
||
width = "100%"
|
||
)
|
||
),
|
||
layout_columns(
|
||
col_widths = c(6, 6),
|
||
numericRangeInput(
|
||
"dt_mmh_filter",
|
||
"MMH ($)",
|
||
value = c(0, round(dt_mmh_max, digits = 0)),
|
||
min = 0,
|
||
separator = "–",
|
||
width = "100%"
|
||
),
|
||
numericRangeInput(
|
||
"dt_mmp_filter",
|
||
"MMP ($)",
|
||
value = c(0, round(dt_mmp_max, digits = 0)),
|
||
min = 0,
|
||
separator = "–",
|
||
width = "100%"
|
||
)
|
||
)
|
||
),
|
||
hr(class = "m-0"),
|
||
DTOutput("storm_coverage_table")
|
||
),
|
||
layout_columns(
|
||
col_widths = 12,
|
||
card(
|
||
height = 300,
|
||
div(
|
||
h5(strong(textOutput("dt_storm_name_year"))),
|
||
uiOutput("dt_storm_badges")
|
||
),
|
||
layout_columns(
|
||
col_widths = c(6, 6),
|
||
card(
|
||
class = "bg-light",
|
||
card_body(
|
||
class = "p-1",
|
||
div(
|
||
div(tags$small(class = "text-muted", "Track Observations")),
|
||
h6(strong(textOutput("dt_track_observations")))
|
||
)
|
||
)
|
||
),
|
||
card(
|
||
class = "bg-light",
|
||
card_body(
|
||
class = "p-1",
|
||
div(
|
||
div(tags$small(class = "text-muted", "Max Category")),
|
||
h6(strong(textOutput("dt_max_category")))
|
||
)
|
||
)
|
||
),
|
||
card(
|
||
class = "bg-light",
|
||
card_body(
|
||
class = "p-1",
|
||
div(
|
||
div(tags$small(class = "text-muted", "Max Windspeed")),
|
||
h6(strong(textOutput("dt_max_windspeed")))
|
||
)
|
||
)
|
||
),
|
||
card(
|
||
class = "bg-light",
|
||
card_body(
|
||
class = "p-1",
|
||
div(
|
||
div(tags$small(class = "text-muted", "Min Pressure")),
|
||
h6(strong(textOutput("dt_min_pressure")))
|
||
)
|
||
)
|
||
)
|
||
),
|
||
actionButton(class = "btn-primary", "load_storm", "Load Storm")
|
||
),
|
||
card(
|
||
height = 700,
|
||
card_body(
|
||
class = "p-0",
|
||
leafletOutput("all_storms_map", height = "100%")
|
||
)
|
||
)
|
||
)
|
||
)
|
||
),
|
||
nav_panel(
|
||
title = "Storm Explorer",
|
||
navset_card_underline(
|
||
nav_item(strong(textOutput("selected_storm_name_year"))),
|
||
nav_panel(
|
||
"Track",
|
||
layout_columns(
|
||
col_widths = 12,
|
||
layout_column_wrap(
|
||
card(
|
||
full_screen = TRUE,
|
||
card_body(
|
||
class = "p-0",
|
||
leafletOutput("track_map", height = "100%")
|
||
)
|
||
),
|
||
layout_column_wrap(
|
||
width = 1,
|
||
card(
|
||
card_body(
|
||
DTOutput("track_data", fill = TRUE)
|
||
)
|
||
),
|
||
card(
|
||
dygraphOutput("track_met_chart", height = "250px")
|
||
)
|
||
)
|
||
)
|
||
)
|
||
),
|
||
nav_panel(
|
||
"Cost Normalization",
|
||
layout_column_wrap(
|
||
col_widths = 1,
|
||
card(
|
||
full_screen = TRUE,
|
||
layout_sidebar(
|
||
sidebar = sidebar(
|
||
virtualSelectInput(
|
||
"storm_overview_cost_index_lf_select",
|
||
"Landfalls",
|
||
choices = NULL,
|
||
showValueAsTags = T,
|
||
multiple = T,
|
||
autoSelectFirstOption = T
|
||
),
|
||
radioGroupButtons(
|
||
"storm_overview_cost_index_scale",
|
||
label = "Value",
|
||
choices = c("Index", "Loss"),
|
||
status = "outline-primary rounded-0",
|
||
justified = T
|
||
),
|
||
checkboxGroupButtons(
|
||
"storm_overview_cost_index_mmh_mmp",
|
||
label = "MMH/MMP",
|
||
choices = c("MMH", "MMP"),
|
||
selected = c("MMH", "MMP"),
|
||
status = "outline-primary rounded-0",
|
||
justified = T
|
||
),
|
||
radioGroupButtons(
|
||
"storm_overview_cost_index_y_scale",
|
||
label = "Y-Axis Scale",
|
||
choices = c("Linear", "Log"),
|
||
status = "outline-primary rounded-0",
|
||
justified = T
|
||
)
|
||
),
|
||
dygraphOutput("cost_index_chart")
|
||
)
|
||
)
|
||
)
|
||
),
|
||
nav_panel(
|
||
"Human Factors",
|
||
layout_column_wrap(
|
||
width = 1,
|
||
layout_columns(
|
||
col_widths = 12,
|
||
row_heights = c("auto", "1fr", "auto"),
|
||
card(
|
||
fill = FALSE,
|
||
layout_columns(
|
||
selectInput(
|
||
"growth_trend_lf_select",
|
||
"Landfall Select",
|
||
choices = NULL
|
||
),
|
||
radioGroupButtons(
|
||
"growth_map_metric",
|
||
label = "Display Metric",
|
||
choices = c("Population", "Housing"),
|
||
selected = "Population",
|
||
status = "outline-primary rounded-0",
|
||
justified = T
|
||
)
|
||
),
|
||
),
|
||
card(
|
||
full_screen = TRUE,
|
||
card_body(
|
||
class = "p-0",
|
||
leafletOutput("growth_map", height = "100%")
|
||
)
|
||
),
|
||
card(
|
||
fill = FALSE,
|
||
sliderInput(
|
||
"growth_trend_map_slider",
|
||
label = NULL,
|
||
min = 0,
|
||
max = 2024,
|
||
step = 1,
|
||
animate = list(
|
||
interval = 250,
|
||
loop = F
|
||
),
|
||
value = 0,
|
||
sep = "",
|
||
width = "100%",
|
||
ticks = F
|
||
)
|
||
)
|
||
)
|
||
)
|
||
),
|
||
nav_panel(
|
||
"Fatality Map",
|
||
layout_columns(
|
||
col_widths = c(8, 4),
|
||
card(
|
||
full_screen = TRUE,
|
||
card_body(
|
||
class = "p-0",
|
||
leafletOutput("fatality_map", height = "100%")
|
||
)
|
||
),
|
||
layout_column_wrap(
|
||
width = 1,
|
||
card(
|
||
plotlyOutput("fatality_pie", height = "100%")
|
||
),
|
||
card(
|
||
plotlyOutput("fatality_heatmap", height = "100%")
|
||
)
|
||
)
|
||
)
|
||
),
|
||
nav_spacer(),
|
||
nav_menu(
|
||
title = "Export",
|
||
nav_item("Storm report")
|
||
)
|
||
)
|
||
),
|
||
nav_menu(
|
||
"Data",
|
||
nav_panel(
|
||
"What We Track",
|
||
layout_columns(
|
||
col_widths = 12,
|
||
layout_column_wrap(
|
||
width = 1 / 4,
|
||
card(),
|
||
card(),
|
||
card(),
|
||
card()
|
||
),
|
||
layout_columns(
|
||
col_widths = 6,
|
||
layout_columns(
|
||
col_widths = 12,
|
||
card(card_header("HURDAT2 Integration")),
|
||
card(card_header("Economic Data")),
|
||
card(card_header("Population and Housing Growth")),
|
||
card(card_header("Social Vulnerability"))
|
||
),
|
||
navset_card_tab(
|
||
height = 600,
|
||
title = "Geographic Data Coverage",
|
||
nav_panel(
|
||
"Cost"
|
||
),
|
||
nav_panel(
|
||
"Fatality"
|
||
),
|
||
nav_panel(
|
||
"SVI"
|
||
)
|
||
)
|
||
)
|
||
)
|
||
),
|
||
nav_panel(
|
||
"Export",
|
||
layout_column_wrap(
|
||
width = 1 / 2,
|
||
card(
|
||
card_header("Available Datasets"),
|
||
!!!download_cards
|
||
),
|
||
card()
|
||
)
|
||
)
|
||
),
|
||
nav_menu(
|
||
"About",
|
||
nav_panel("Our Studies"),
|
||
nav_panel("MMH/MMP Methodology")
|
||
)
|
||
)
|