30 years of NOAA-verified flood data for the Charleston, SC metro — interactive map, risk zones, and location decision analysis.
- Overview
- Key Features
- Dataset Headline Numbers
- Architecture
- Data Flow
- Data & Metadata Provenance
- Event Distribution
- Technology Stack
- Setup & Installation
- Usage
- Core Capabilities
- Project Roadmap
- Development Status
- Important Limitations
- Contributing
- License & Acknowledgements
The Charleston Flood History Tracker is a static, browser-based geospatial application that visualises 314 unique flood-family events recorded by NOAA NCEI across the Charleston, SC metro between 1995 and 2024. Five study cities are covered — Charleston, North Charleston, Summerville, Goose Creek, and Hanahan — each within a 20-mile radius search perimeter.
The app also includes comparison-only benchmark cities in West Virginia (Fayetteville, Oak Hill, Bridgeport, Fairmont, and Clarksburg) to reinforce a key public-safety point: flooding is widespread across regions and is not limited to coastal South Carolina.
The application is aimed at residents making location and insurance decisions, journalists and researchers studying coastal flood risk, and public-health educators who need authoritative, citable data to communicate flood timing and frequency patterns.
Unlike forecast tools, this project answers the retrospective question: "How often, when, and where has flooding actually been documented here?"
Important
This is a historical analysis tool, not a real-time forecast system. Always follow active NWS warnings and local emergency instructions during a flood event.
| Icon | Feature | Description | Impact | Status |
|---|---|---|---|---|
| 🗺️ | Interactive Map | OpenLayers + OSM base map with flood event markers | High | ✅ Stable |
| 🎨 | Type-based Colours | Flash Flood vs Flood events rendered in distinct colours | Medium | ✅ Stable |
| 📏 | Damage-scaled Symbols | Marker radius scales with reported property damage | High | ✅ Stable |
| 🟦 | Risk-zone Polygons | Per-city Gaussian heat surface classified into 5 risk levels | High | ✅ Stable |
| 🔎 | Live Filters | Year range slider, event type toggle, minimum damage threshold | High | ✅ Stable |
| 🏙️ | City Comparison Panel | Side-by-side event count and peak-month table for all 5 cities | High | ✅ Stable |
| 🧭 | Decision Analysis Section | Safety, timing, insurance, and adaptation guidance per city | Critical | ✅ Stable |
| 🐍 | NOAA Data Pipeline | Python script that ingests, filters, scores, and emits processed JSON | Critical | ✅ Stable |
| 🐳 | Docker Deployment | Single-command containerised Nginx deployment | Medium | ✅ Stable |
| 🧪 | Test Suite | Pytest unit tests for all data-pipeline helpers | Medium | ✅ Stable |
Standout capabilities:
- Gaussian risk-zone scoring weights log-scaled damage, injuries/fatalities, and coastal/surge event emphasis to produce a scientifically grounded 5-tier risk surface per city.
- Decision-analysis section is content-driven, not generic — it answers the specific questions a person considering buying or renting in the metro would actually ask.
- Zero external API calls at runtime — the app is fully static after the dataset build step, making it deployable anywhere (GitHub Pages, Nginx, Netlify, local file open).
- $29.2 M in documented regional damage captured across included events, making the cost-of-flooding argument concrete.
| Metric | Value |
|---|---|
| Study period | 1995 – 2024 (30 years) |
| Unique flood events (deduped) | 314 |
| Cities covered | 5 |
| Search radius per city | 20 miles |
| Reported regional damage | $29,233,340 |
| Flash Flood events | 297 (94.6%) |
| Flood events | 17 (5.4%) |
| Peak month (Aug) | 86 events |
| Peak month (Oct) | 61 events |
| Peak month (Jul) | 58 events |
Per-city event counts (1995–2024):
| City | Events in 20-mi Radius |
|---|---|
| Hanahan, SC | 291 |
| Goose Creek, SC | 290 |
| North Charleston, SC | 284 |
| Charleston, SC | 255 |
| Summerville, SC | 206 |
Note
City counts overlap — a single NOAA event can fall within the 20-mile radius of multiple cities. The 314 figure is the deduplicated regional count.
flowchart TD
subgraph Pipeline["🐍 Data Pipeline (Python)"]
A[NOAA NCEI Bulk CSV Files<br/>1995–2024] --> B[build_dataset.py]
B --> C{Filter: SC flood-family<br/>events in 20-mi radius}
C --> D[Deduplicate by EVENT_ID]
D --> E[Gaussian Risk-Zone Scoring<br/>per city grid]
E --> F[charleston_floods_30y.json]
end
subgraph Frontend["🌐 Browser App (Vanilla JS)"]
F --> G[app.js — data loader]
G --> H[OpenLayers Map<br/>OSM base tiles]
G --> I[Event Markers<br/>type colour + damage scale]
G --> J[Risk-Zone Polygons<br/>5-tier heat surface]
G --> K[Filter Controls<br/>year · type · damage]
G --> L[City Comparison Panel]
G --> M[Decision Analysis Section]
end
subgraph Deploy["🐳 Deployment"]
H & I & J & K & L & M --> N[index.html]
N --> O{Deployment target}
O --> P[python -m http.server<br/>local dev]
O --> Q[Docker + Nginx<br/>port 8091]
O --> R[GitHub Pages / Netlify<br/>static host]
end
Component responsibilities:
| Component | File | Responsibility |
|---|---|---|
| Data pipeline | scripts/build_dataset.py |
Download, filter, score, emit JSON |
| Map engine | src/app.js |
Render map, events, risk zones, filters, panels |
| Stylesheet | src/styles.css |
Responsive layout, legend, panel styles |
| Dataset | data/processed/charleston_floods_30y.json |
Versioned output — committed to repo |
| Container | docker/docker-compose.yml + Dockerfile |
Nginx static server on port 8091 |
| Tests | tests/test_build_dataset.py |
Pytest suite for pipeline helpers |
The pipeline is completely decoupled from the frontend. The Python script runs once (or on demand) and writes a static JSON file that the browser reads directly — no server-side runtime, no database, no external API calls during page load.
sequenceDiagram
participant Dev as Developer
participant Script as build_dataset.py
participant NOAA as NOAA NCEI CSV
participant JSON as processed JSON
participant Browser as Browser / User
Dev->>Script: python3 scripts/build_dataset.py
Script->>NOAA: HTTP GET StormEvents_details_YYYY.csv (×30)
NOAA-->>Script: CSV rows (all SC events)
Script->>Script: Filter flood-family + 20-mi radius
Script->>Script: Deduplicate by EVENT_ID
Script->>Script: Gaussian risk scoring per city grid
Script-->>JSON: Write charleston_floods_30y.json
Dev->>Browser: Open index.html (or docker up)
Browser->>JSON: fetch('./data/processed/charleston_floods_30y.json')
JSON-->>Browser: 314 events + risk zones + metadata
Browser->>Browser: Render OpenLayers map
Browser->>Browser: Apply user filters (year / type / damage)
Browser-->>Dev: Interactive flood history map
This project intentionally separates primary event data from contextual metadata used to improve interpretation.
| Source | What we use | How it is used in this project | Notes |
|---|---|---|---|
| NOAA NCEI Storm Events CSV bulk files | StormEvents_details-ftp_v1.0_dYYYY_*.csv.gz |
Canonical source for event ID, time, county, lat/lon, event type, injuries/deaths, DAMAGE_PROPERTY, DAMAGE_CROPS, narratives |
Downloaded by scripts/build_dataset.py and written into data/processed/charleston_floods_30y.json |
| Source | What we query | Why it matters | Integration status |
|---|---|---|---|
| NWS Charleston event archive | Significant event summary pages and office archive references | Confirms local meteorological context, rain totals, and event framing for known flood periods | Referenced as supporting context |
| Iowa State IEM NWS text archive | AFOS text products (LSR, FFW, etc.) for KCHS | Provides time-stamped warning/report context not always visible in Storm Events CSV fields | Referenced as supporting context |
| NWS text products (LSR/FFW) | LSRCHS and FFWCHS examples for Aug 2005 |
Verified that North Charleston was under a Flash Flood Warning during the target event window | Referenced as supporting context |
| SHELDUS (ASU/CEMHS) | County-level hazard/loss catalogs | Candidate secondary source for cross-checking loss totals when NOAA records are blank or sparse | Research in progress |
- Primary values in the shipped JSON come from NOAA Storm Events CSVs.
- When NOAA damage fields are blank but narratives describe physical impact, we flag the event as
damageUnreported: true. - External sources (NWS office pages, IEM product archives, newspapers, SHELDUS) are treated as corroborating metadata unless a defensible, traceable replacement value is established.
- No external estimate is silently substituted into NOAA numeric damage fields.
- The Aug 24, 2005 Charleston-area flash flood event associated with Hawthorne Trailer Park / Rivers Avenue has narrative evidence of substantial impact, but NOAA
DAMAGE_PROPERTYis blank in the source CSV. - Supporting metadata confirms contemporaneous flash flood warning context for North Charleston.
- Current project behavior is to keep numeric damage at reported NOAA value and surface uncertainty with
damageUnreportedindicators.
pie title Charleston Metro — Flood Event Type Split (1995–2024)
"Flash Flood" : 297
"Flood" : 17
xychart-beta
title "Monthly Flood Event Count — Charleston Metro (1995–2024)"
x-axis ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
y-axis "Events" 0 --> 100
bar [14, 9, 12, 18, 22, 35, 58, 86, 45, 61, 21, 11]
Tip
The Aug–Oct window accounts for the single largest concentration of flood events, driven by Atlantic hurricane season and tropical moisture. Plan insurance renewals and preparedness reviews before August.
| Technology | Version | Purpose | Why Chosen | Alternatives Considered |
|---|---|---|---|---|
| OpenLayers | 10.8 | Interactive web map | Fully open-source, no API key, rich vector layer API | Leaflet (fewer vector features), Mapbox (paid) |
| OpenStreetMap | — | Base tile layer | Free, no API key, global coverage | Google Maps (paid), Esri (paid) |
| Python | 3.10+ | Data pipeline | Pandas + requests ecosystem, rapid CSV wrangling | Node.js (weaker data libs for geospatial) |
| Pandas | latest | CSV ingestion & filtering | Vectorised operations on large yearly CSVs | Polars (less ecosystem maturity) |
| NumPy / SciPy | latest | Gaussian risk-zone surface | Native grid computation, ndimage smoothing | Manual convolution (slower) |
| Vanilla JS (ES2022) | — | Frontend logic | Zero build tool, zero dependencies at runtime | React (overkill for static app) |
| Nginx | alpine | Static file server | Minimal image size, production-grade caching headers | Apache (heavier), Python http.server (dev only) |
| Docker + Compose | latest | Containerised deployment | Reproducible environment, one-command deploy | Manual server config |
| Pytest | latest | Pipeline test suite | Standard Python testing, fixtures, parametrize | unittest (more verbose) |
- Python 3.10 or newer
pip(orpipenv/uv)- A modern browser (Chrome, Firefox, Edge, Safari)
- Docker + Docker Compose (optional — for containerised deployment)
git clone https://github.com/hkevin01/charleston-flood-history-tracker.git
cd charleston-flood-history-trackerpip install -r requirements.txt📋 Core Python dependencies
| Package | Purpose |
|---|---|
pandas |
CSV ingestion and tabular filtering |
numpy |
Grid arithmetic for risk-zone scoring |
scipy |
Gaussian smoothing (ndimage.gaussian_filter) |
requests |
HTTP download of NOAA bulk CSVs |
pytest |
Test suite runner |
This step downloads ~30 annual NOAA CSV files into data/raw/ (gitignored) and emits data/processed/charleston_floods_30y.json.
python3 scripts/build_dataset.pyExpected output (abbreviated):
[2026-04-26 18:09:55] Downloading StormEvents_details_1995.csv ...
...
[2026-04-26 18:09:56] 314 unique events written → data/processed/charleston_floods_30y.json
Warning
The raw CSV downloads total ~200 MB. They are excluded from the repo via .gitignore. Do not commit files in data/raw/.
python3 -m http.server 8091Then open: http://localhost:8091
Tip
Press Ctrl+C to stop the local server.
docker compose -f docker/docker-compose.yml up -dOpen http://localhost:8091. To stop:
docker compose -f docker/docker-compose.yml down| Control | Action |
|---|---|
| Scroll / pinch | Zoom in and out |
| Click + drag | Pan the map |
| Click event marker | Open popup with event details (type, date, damage, injuries) |
| Click risk-zone polygon | Show zone classification and city |
- Year range slider — narrow events to a specific period (e.g. 2010–2020).
- Event type toggle — show Flash Flood only, Flood only, or both.
- Minimum damage threshold — filter out low-damage events to focus on significant floods.
Shows event counts and peak months for all five study cities side by side — useful for comparing relative flood exposure when considering a move within the metro.
The panel also includes a separate Regional Benchmark (WV Comparison Cities) table. These benchmark cities are not mapped in the Charleston view; they are included as cross-region context only.
Scroll below the map for the structured guidance section covering:
- How often flooding happens (frequency by city)
- When flooding tends to happen (seasonal peaks)
- What to do — home vs car vs sheltering in place
- Core safety measures referenced against NWS guidance
- Insurance discussion — NFIP scope, homeowners/renters exclusions, auto comprehensive
- How residents adapt to persistent flood risk
The pipeline generates a per-city Gaussian risk surface over a regular lat/lon grid. Each flood event contributes a weighted Gaussian kernel whose amplitude is determined by:
- Log-scaled reported damage (
DAMAGE_PROPERTY) - Injury and fatality impact (
INJURIES_DIRECT,DEATHS_DIRECT) - Event-type emphasis — coastal and storm-surge categories receive a higher multiplier
The resulting continuous surface is classified into five tiers via quantile thresholds:
| Zone | Label | Colour |
|---|---|---|
| 1 | Low | 🟦 Light blue |
| 2 | Guarded | 🟩 Green |
| 3 | Elevated | 🟨 Yellow |
| 4 | High | 🟧 Orange |
| 5 | Most Affected | 🟥 Red |
An event is associated with a city when its start OR end coordinate falls within the 20-mile (≈ 32 km) great-circle radius from the city centre. Events are then deduplicated regionally by NOAA EVENT_ID to prevent double-counting in aggregate statistics.
Note
Overlap is intentional at the per-city level — a single storm can produce flooding within 20 miles of multiple city centres simultaneously.
The decision-analysis section uses the NOAA event data as evidence but supplements it with:
- NWS Flood Safety guidance for action recommendations
- NFIP / FloodSmart guidance for insurance coverage explanations
- NAIC Auto Insurance Database as the best available public proxy for auto comprehensive claim frequency (no flood-only, city-level auto payout open dataset exists)
Caution
Auto insurance flood payout data is presented as state-level context only. City-level flood-specific auto payout open data does not exist in public NAIC or FEMA releases. Do not treat NAIC metrics as city-specific figures.
gantt
title Charleston Flood Tracker — Development Roadmap
dateFormat YYYY-MM-DD
section Phase 1 · Data Pipeline
NOAA ingestion & filtering :done, p1a, 2026-01-01, 2026-02-01
Risk-zone scoring :done, p1b, 2026-02-01, 2026-03-01
JSON output & tests :done, p1c, 2026-03-01, 2026-03-15
section Phase 2 · Map Experience
OpenLayers map + markers :done, p2a, 2026-03-01, 2026-03-20
Risk-zone polygons :done, p2b, 2026-03-15, 2026-04-01
Filter controls :done, p2c, 2026-04-01, 2026-04-10
section Phase 3 · Analysis & Delivery
City comparison panel :done, p3a, 2026-04-10, 2026-04-18
Decision-analysis section :done, p3b, 2026-04-15, 2026-04-22
Docker + docs + tests :done, p3c, 2026-04-20, 2026-04-26
section Phase 4 · Enhancements
Historical storm-track overlay :done, p4a, 2026-05-01, 2026-06-01
FEMA flood-zone layer integration :done, p4b, 2026-05-15, 2026-06-15
Exportable city risk report (PDF) :done, p4c, 2026-06-01, 2026-07-01
Mobile-optimised layout :done, p4d, 2026-06-15, 2026-07-15
Data-quality audit filters :done, p4e, 2026-07-10, 2026-07-25
Filtered event CSV export :done, p4f, 2026-07-25, 2026-08-20
| Phase | Goals | Target | Status |
|---|---|---|---|
| 1 — Data Pipeline | NOAA ingestion, filtering, risk scoring, JSON output | 2026-03 | ✅ Complete |
| 2 — Map Experience | OpenLayers map, markers, polygons, filters | 2026-04 | ✅ Complete |
| 3 — Analysis & Delivery | Decision analysis, Docker, tests, docs | 2026-04-26 | ✅ Complete |
| 4 — Enhancements | Storm tracks, FEMA zones, PDF report, mobile, data quality filters | 2026-08 | 🟡 In Progress |
| Version | Stability | Test Coverage | Known Limitations |
|---|---|---|---|
| v1.0.0 (current) | ✅ Stable | Unit tests for pipeline helpers | No flood-only city-level auto payout data available publicly |
| v1.0.0 | ✅ Stable | Manual UI verification | Per-event damage dollars are sometimes unreported in NOAA source rows |
| v1.0.0 | ✅ Stable | Docker smoke-tested | On-map compact legend overlay control not yet implemented |
mindmap
root((Charleston Flood Tracker))
Data
NOAA NCEI CSVs
30 annual files
SC flood-family filter
Spatial Logic
20-mile radius
EVENT_ID dedup
Risk Scoring
Gaussian surface
5-tier classification
Frontend
OpenLayers Map
Event markers
Risk polygons
Filter controls
Panels
City comparison
Decision analysis
Deployment
Local dev
python http.server
Docker
Nginx alpine
Static host
GitHub Pages
Netlify
Testing
Pytest suite
Pipeline helpers
Filter logic
Warning
NOAA Storm Events is an observational record, not a complete census. Events are included when a National Weather Service office issues a warning or a damage report is submitted. Minor flooding that went unreported will not appear in the dataset.
Warning
No flood-only, city-level auto insurance payout open data exists. The NAIC Auto Insurance Database reports state-level comprehensive claim frequency. It is used in the decision-analysis section as a directional proxy only — not a Charleston-specific figure.
Note
The 20-mile radius produces intentional overlap. City-level event counts in the comparison panel will sum to more than the 314 deduplicated regional events. This is expected and documented.
Some events display a data-quality flag called damageUnreported.
- Plain meaning: the NOAA record shows
$0because the damage field was left blank, not because no damage happened. - Why you might see it: the narrative says things like roads flooded, cars underwater, or structures impacted, but no dollar estimate was entered in the original report.
- How to read it: treat
$0as unknown / not reported for that event, not as confirmed zero loss.
Contributions are welcome — whether that's correcting data, improving the map UX, or adding a new analysis dimension.
Quick workflow:
- Fork the repo
- Create a branch:
git checkout -b feat/your-feature - Make changes and run tests:
pytest tests/ - Open a Pull Request using the PR template
📋 Detailed contributing guidelines
- Python: PEP 8.
blackformatter accepted. - JavaScript: Match the existing style in
src/app.js— no build tools, no bundlers.
- All pipeline helper functions must have a corresponding Pytest test in
tests/test_build_dataset.py. - UI changes should be verified in at least Chrome and Firefox before submitting.
If you have a source that contradicts or supplements the current dataset, open a Data issue with a link to the authoritative source (NOAA NCEI, NWS, FEMA).
data/raw/— raw NOAA CSV downloads are gitignored and must stay that way.- Any file containing API keys or credentials.
| Template | Use for |
|---|---|
| Bug report | Something isn't working |
| Feature request | New capability or enhancement |
| Data issue | Incorrect, missing, or outdated flood data |
Please read the Code of Conduct before contributing.
This project is released under the MIT License — you are free to use, copy, modify, and distribute it with attribution. See LICENSE for the full text.
| Source | Description | Link |
|---|---|---|
| NOAA NCEI Storm Events | Primary flood event records (1995–2024) | CSV directory |
| NWS Charleston Event Archive | Local event summary and historical office context | weather.gov/chs/events |
| Iowa State IEM NWS Text Archive | Historical AFOS text products (LSR/FFW/AFD/etc.) | mesonet.agron.iastate.edu |
| NWS Flood Safety | Action guidance referenced in decision-analysis section | weather.gov |
| NFIP / FloodSmart | Insurance guidance | floodsmart.gov |
| NAIC Auto Insurance DB | State-level auto comprehensive claim frequency proxy | naic.org |
| SHELDUS (ASU/CEMHS) | County-level hazard/loss cross-reference source | cemhs.asu.edu/sheldus |
| OpenStreetMap | Base map tiles | openstreetmap.org |
Structural approach inspired by a companion Tornado History Tracker project for the same Charleston metro study area.
This project is a historical analysis and planning-support tool. It is not a forecast system and must not be used as a substitute for official NWS warnings, local emergency instructions, or professional insurance advice during an active flood event.