Skip to content

Commit c930132

Browse files
committed
docs: add README with features, deployment, and API docs
1 parent 3f3c6f0 commit c930132

1 file changed

Lines changed: 229 additions & 0 deletions

File tree

README.md

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Model Forecast Viewer
2+
3+
[![GitHub last commit](https://img.shields.io/github/last-commit/ShianMike/ModelForecast?style=flat-square&color=blue)](https://github.com/ShianMike/ModelForecast/commits/main)
4+
[![GitHub stars](https://img.shields.io/github/stars/ShianMike/ModelForecast?style=flat-square)](https://github.com/ShianMike/ModelForecast/stargazers)
5+
[![GitHub forks](https://img.shields.io/github/forks/ShianMike/ModelForecast?style=flat-square)](https://github.com/ShianMike/ModelForecast/forks)
6+
[![Made with Python](https://img.shields.io/badge/Python-3.12+-3776AB?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
7+
[![Made with React](https://img.shields.io/badge/React-18-61DAFB?style=flat-square&logo=react&logoColor=black)](https://react.dev/)
8+
[![Deployed on Cloud Run](https://img.shields.io/badge/Cloud%20Run-deployed-4285F4?style=flat-square&logo=googlecloud&logoColor=white)](https://model-forecast-omtebvnjea-uc.a.run.app)
9+
[![License](https://img.shields.io/badge/license-Educational%20%2F%20Research-green?style=flat-square)](#license)
10+
11+
A full-stack weather model forecast viewer that fetches real-time gridded forecast data from NOAA NOMADS, decodes GRIB2 with a pure-Python decoder, and renders interactive map overlays with wind arrows, color-coded parameters, and animation controls — inspired by Pivotal Weather and Aguacero.
12+
13+
**Live site:** <https://model-forecast-omtebvnjea-uc.a.run.app/ModelForecast/>
14+
15+
---
16+
17+
## Features
18+
19+
### Interactive Forecast Map
20+
- **Leaflet-based map** with CartoDB dark/light basemap tiles
21+
- **Gap-free canvas overlay** rendering gridded forecast data with bilinear edge interpolation
22+
- **Wind arrows** — U/V component vectors drawn on the map when wind parameters are selected
23+
- **Opacity slider** — adjustable overlay transparency (0–100%, default 50%)
24+
- **Color bar legend** — dynamic per-parameter color scale with labeled tick marks
25+
- **Region presets** — quick-select CONUS, Northeast, Southeast, Central, West, and more
26+
27+
### NOAA NOMADS Integration
28+
- Direct GRIB2 filter downloads from NOMADS — no API keys, no rate limits
29+
- **Per-model variable/level overrides** — handles model-specific GRIB variable names (e.g., MSLMA for HRRR/RAP pressure, different cloud cover levels for NAM)
30+
- **Automatic latest-run detection** — finds the most recent model cycle with data available
31+
- **Geographic subsetting** — downloads only the bounding box needed, not the full grid
32+
- **30-minute response cache** with 10-minute latest-run cache
33+
34+
### Pure-Python GRIB2 Decoder
35+
- No C libraries required — works on Windows, Linux, macOS without compiled dependencies
36+
- **Grid templates:** 3.0 (regular lat/lon) and 3.30 (Lambert Conformal Conic with bilinear regridding)
37+
- **Packing templates:** 5.0 (simple packing) and 5.40 (JPEG2000)
38+
- **Sign-magnitude encoding** — correctly decodes GRIB2 scale factors (not two's complement)
39+
40+
### Supported Models
41+
42+
| Model | Resolution | Max Forecast | Cycles | Source |
43+
|-------|-----------|-------------|--------|--------|
44+
| **GFS** | 0.25° (~28 km) | 384 hours | 00/06/12/18Z | NOMADS `filter_gfs_0p25.pl` |
45+
| **HRRR** | 3 km | 48 hours | Every hour | NOMADS `filter_hrrr_2d.pl` |
46+
| **NAM** | 12 km | 84 hours | 00/06/12/18Z | NOMADS `filter_nam.pl` |
47+
| **RAP** | 13 km | 51 hours | Every hour | NOMADS `filter_rap.pl` |
48+
49+
### Forecast Parameters (18)
50+
51+
| Category | Parameters |
52+
|----------|-----------|
53+
| **Surface** | Temperature (2m), Dewpoint (2m), Relative Humidity (2m), Surface Pressure (MSLP) |
54+
| **Wind** | Wind Speed (10m), Wind Gusts (10m) |
55+
| **Precipitation** | Accumulated Precipitation, Snowfall |
56+
| **Radiation** | Shortwave Radiation, Cloud Cover |
57+
| **Convective** | CAPE, Convective Inhibition (CIN) |
58+
| **Visibility** | Surface Visibility |
59+
| **Upper-Air** | 500 hPa Geopotential Height, 850 hPa Temperature, 250/500/850 hPa Wind Speed |
60+
61+
### Animation Controls
62+
- Play/pause/step through forecast hours
63+
- Adjustable playback speed
64+
- Timeline slider with forecast hour labels
65+
- Frame caching for smooth playback
66+
67+
### Theme Support
68+
- Dark theme (default) and light theme toggle
69+
- Consistent styling across all components
70+
71+
---
72+
73+
## Project Structure
74+
75+
```
76+
├── app.py # Flask entry point (CORS, Talisman, rate limits, SPA catch-all)
77+
├── gunicorn.conf.py # Gunicorn WSGI config (reads PORT from env)
78+
├── requirements.txt # Python dependencies
79+
├── Dockerfile # Multi-stage build (Node + Python)
80+
├── .github/
81+
│ └── workflows/
82+
│ └── deploy.yml # GitHub Actions → Cloud Run CI/CD
83+
├── forecast/ # Core data package
84+
│ ├── nomads.py # NOMADS GRIB filter client (4 models, 18 variables)
85+
│ ├── grib2.py # Pure-Python GRIB2 decoder (lat/lon + Lambert grids)
86+
│ ├── parameters.py # Parameter definitions, color scales, categories
87+
│ └── open_meteo.py # Open-Meteo client (legacy, unused for grid forecasts)
88+
├── routes/ # Flask API blueprints
89+
│ ├── __init__.py # Blueprint registry
90+
│ ├── forecast_routes.py # /api/forecast, /api/color-scale
91+
│ ├── meta.py # /api/health, /api/models, /api/parameters
92+
│ └── helpers.py # NaN-safe JSON serializer
93+
└── frontend/ # React 18 + Vite 6
94+
├── src/
95+
│ ├── App.jsx # Main app, state management, frame cache
96+
│ ├── api.js # API client
97+
│ └── components/
98+
│ ├── Sidebar.jsx # Model/parameter/region selection
99+
│ ├── Header.jsx # Top bar, theme toggle, opacity slider
100+
│ ├── ForecastMap.jsx # Leaflet map container
101+
│ ├── CanvasOverlay.jsx # Canvas grid renderer + wind arrows
102+
│ ├── ColorBar.jsx # Color scale legend
103+
│ ├── AnimationControls.jsx # Play/pause/step/speed controls
104+
│ └── ParameterPicker.jsx # Grouped parameter selector
105+
├── public/
106+
│ └── manifest.json # PWA manifest
107+
├── package.json
108+
└── vite.config.js
109+
```
110+
111+
---
112+
113+
## Quick Start
114+
115+
### Backend (Python)
116+
117+
```bash
118+
pip install -r requirements.txt
119+
python app.py
120+
# → http://localhost:5001
121+
```
122+
123+
### Frontend (React)
124+
125+
```bash
126+
cd frontend
127+
npm install
128+
npm run dev
129+
# → http://localhost:3002
130+
```
131+
132+
The Vite dev server proxies `/api` requests to the backend at `localhost:5001`.
133+
134+
---
135+
136+
## Deployment
137+
138+
| Component | Platform | Region | URL |
139+
|-----------|----------|--------|-----|
140+
| **Full-stack** | Google Cloud Run | us-central1 | `https://model-forecast-omtebvnjea-uc.a.run.app` |
141+
142+
**Cloud Run configuration:** 512 MiB memory, 1 vCPU, max 3 instances, 300 s timeout, 2 workers × 4 threads.
143+
144+
### CI/CD Pipeline
145+
146+
Every push to `main` triggers a GitHub Actions workflow that:
147+
148+
1. Authenticates to GCP via Workload Identity Federation (keyless)
149+
2. Builds a multi-stage Docker image (Node 20 frontend build + Python 3.12 runtime)
150+
3. Pushes to Artifact Registry (`us-central1-docker.pkg.dev`)
151+
4. Deploys to Cloud Run
152+
153+
No service account keys are stored — authentication uses OIDC tokens scoped to the repository.
154+
155+
---
156+
157+
## API Endpoints
158+
159+
| Method | Endpoint | Description |
160+
|--------|----------|-------------|
161+
| `GET` | `/api/health` | Health check |
162+
| `GET` | `/api/models` | List available models with metadata (resolution, max hour, step) |
163+
| `GET` | `/api/parameters?model={m}` | Get supported parameters for a model, grouped by category |
164+
| `GET` | `/api/forecast?model={m}&variable={v}&fhour={h}&lat_min=...` | Gridded forecast data (lats, lons, values, wind U/V) |
165+
| `GET` | `/api/color-scale?cmap={name}` | Color scale stops for a parameter's colormap |
166+
167+
### `GET /api/forecast` — Query Parameters
168+
169+
| Parameter | Required | Example | Description |
170+
|-----------|----------|---------|-------------|
171+
| `model` | Yes | `gfs` | Model name (gfs, hrrr, nam, rap) |
172+
| `variable` | Yes | `temperature_2m` | Parameter key |
173+
| `fhour` | Yes | `6` | Forecast hour |
174+
| `lat_min` | No | `24.0` | Bounding box south edge (default: CONUS) |
175+
| `lat_max` | No | `50.0` | Bounding box north edge |
176+
| `lon_min` | No | `-125.0` | Bounding box west edge |
177+
| `lon_max` | No | `-66.0` | Bounding box east edge |
178+
179+
---
180+
181+
## Security
182+
183+
- **HTTPS:** forced in production via Flask-Talisman (HSTS 2-year preload)
184+
- **Content Security Policy:** restrictive CSP with nonce-based script-src
185+
- **CORS:** locked to production origins only (localhost allowed in development)
186+
- **Rate limiting:** Flask-Limiter — 200 req/min global, 30 req/sec burst
187+
- **Security headers:** X-Content-Type-Options, X-Frame-Options DENY, COOP, CORP, Permissions-Policy, Referrer-Policy
188+
- **Input validation:** path-traversal blocking, 16 MB request-size limit
189+
- **Workload Identity Federation:** keyless GCP auth for CI/CD — no service account keys in secrets
190+
191+
---
192+
193+
## Dependencies
194+
195+
### Python
196+
197+
| Package | Purpose |
198+
|---------|---------|
199+
| Flask 3.1 | Web framework |
200+
| flask-cors 4.0 | CORS headers |
201+
| flask-limiter 3.5 | Rate limiting |
202+
| flask-talisman 1.1 | Security headers |
203+
| gunicorn 22.0 | WSGI server |
204+
| requests 2.31 | NOMADS HTTP client |
205+
| numpy 1.26 | Array operations |
206+
| Pillow 10.0 | Image generation for color scales |
207+
208+
### Frontend
209+
210+
| Package | Purpose |
211+
|---------|---------|
212+
| React 18.3 | UI framework |
213+
| Vite 6 | Build tooling |
214+
| Leaflet + React-Leaflet | Interactive maps |
215+
| D3 7.9 | Data visualization |
216+
| Recharts 3.7 | Charts |
217+
| Lucide React | SVG iconography |
218+
219+
---
220+
221+
## Related Projects
222+
223+
- **[Sounding Analysis](https://github.com/ShianMike/SoundingAnalysis)** — Upper-air sounding analysis platform with Skew-T, hodograph, 50+ parameters, risk scanner, and radar overlays
224+
225+
---
226+
227+
## License
228+
229+
This project is for educational and research purposes.

0 commit comments

Comments
 (0)