Arquitectura¶
NSF Unidata IDD (feedtype NNEXRAD, Level III vía NOAAPort)
│
▼
┌───────────────────┐ ┌──────────────────────────┐
│ Contenedor LDM │ │ Procesador Python 3.12 │
│ (Unidata LDM) │────▶│ decodifica Level III │
│ ldmd + pqact │FILE │ (MetPy), grilla → AEQD, │
└───────────────────┘ dir │ extrae fenómenos │
└────────────┬─────────────┘
│
┌─────────────────┴─────────────────┐
▼ ▼
Cloudflare R2 Cloudflare D1
COG calibrados (AEQD) catálogo de radares,
metadata de rasters,
fenómenos, VWP
│ │
└────────────┬──────────────────────┘
▼
LAMULA-WebViewer (demo, OpenLayers)
ol/source/GeoTIFF + WebGLTileLayer
reproyecta AEQD → CRS del mapa en cliente
Componentes¶
Contenedor LDM (Unidata Local Data Manager)¶
Se conecta al IDD con un request del feedtype NNEXRAD filtrado por sitios y productos. pqact escribe cada producto como fichero en un directorio de entrada (acción FILE); el procesador lo consume vía watcher. Configuración en ldm/ (ldmd.conf, pqact.conf, registry.xml).
Gotcha pqact
Las líneas de continuación de pqact.conf exigen TAB, no espacios — con espacios la entrada se ignora en silencio.
Procesador Python 3.12¶
Servicio persistente con watcher (inotify/watchdog) sobre el directorio de entrada:
- Decodifica el producto con MetPy (
Level3File). - Grilla los datos radiales/raster a malla regular en proyección AEQD centrada en el radar (resampleo nearest neighbor).
- Escribe el COG calibrado con Rasterio (valores físicos escalados, CRS + geotransform embebidos, overviews internos) y lo sube a R2.
- Fenómenos (parsing propio sobre Symbology/Tabular), VWP y metadata de cada raster → D1.
Cloudflare R2¶
Almacén de COGs. Sirve al viewer con CORS + HTTP range requests (el cliente solo descarga los tiles/overviews que necesita). Convención de paths:
{site}/{product_code}/{YYYY}/{MM}/{DD}/{site}_{product_code}_{YYYYMMDD_HHMMSS}.tif
Cloudflare D1¶
Base SQLite serverless (tier gratuito, misma cuenta que R2). Tablas: catálogo de radares (poblado dinámicamente desde la metadata entrante, sin radares hardcodeados), descriptores de producto, metadata de rasters (clave R2, timestamps, VCP, elevación, min/max, proyección), fenómenos (granizo, mesociclones, TVS, tracking de celdas) y perfiles VWP.
El pipeline escribe a D1 desde fuera de Cloudflare vía HTTP API REST (/accounts/{id}/d1/database/{id}/query con token) — atención a rate limits y batching de inserts. El viewer accede vía binding interno de su Worker; cómo lo haga es asunto del viewer — el contrato es solo el schema en db/.
Estructura de carpetas¶
nexrad-l3-pipeline/
├── README.md
├── mkdocs.yml # esta documentación
├── docker-compose.yml # stack Swarm: LDM + procesador + monitor
├── ldm/ # Dockerfile y config del LDM
│ ├── ldmd.conf # request NNEXRAD por sitios/productos
│ └── pqact.conf # entrega FILE al directorio de entrada
├── ingest/ # paquete Python 3.12
│ ├── decoder/ # MetPy Level3File + parsing propio (Symbology, Tabular)
│ ├── gridding/ # polar/raster → AEQD, escritura COG
│ ├── phenomena/ # granizo, meso, TVS, celdas
│ ├── storage/ # clientes R2 (S3 API) y D1 (HTTP API)
│ └── retention/ # sweep + reconciliación
├── db/ # schema D1 + migraciones (contrato con el viewer)
└── docs/ # fuentes de esta documentación