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( '

Welcome to the Hurricane Cost Normalization Web App

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.

Our Data

The core datasets used in this app are based on research by Muller et al. (2025) published in Bulletin of the American Meteorlogical Society. This study updates and refines hurricane damage normalization methodologies to provide a more accurate picture of how historical hurricanes would impact today\'s society.

Methodology Innovations

Our platform incorporates several methodological innovations over previously used cost normalization formulas:

' ) ), layout_columns( col_widths = 12, card( HTML( '

How to Use This App

1. Find a Storm — Storm Selector

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 Load Storm to explore it in detail.

2. Explore Storm Data — Storm Explorer

Once a storm is loaded, four analysis tabs become available:

Data Availability

All storms include HURDAT2 track data. Cost normalization and fatality data are available for a subset of storms, indicated by the Cost and Fatality badges in the Storm Selector.

' ) ), 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") ) )