phase 0-2 complete
This commit is contained in:
@@ -1,17 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/blakeridgway/heloha/internal/radar"
|
||||
"github.com/blakeridgway/heloha/internal/server"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
const ingestEvery = 2 * time.Minute
|
||||
|
||||
var radarSites = []string{
|
||||
"KTLX", // Oklahoma City, OK
|
||||
"KINX", // Tulsa, OK
|
||||
"KVNX", // Vance AFB, OK
|
||||
"KFDR", // Frederick, OK
|
||||
"KSRX", // Fort Smith, AR
|
||||
"KLZK", // Little Rock, AR
|
||||
"KSHV", // Shreveport, LA
|
||||
"KFWS", // Fort Worth, TX
|
||||
"KDYX", // Dyess AFB, TX
|
||||
"KAMA", // Amarillo, TX
|
||||
"KLBB", // Lubbock, TX
|
||||
"KICT", // Wichita, KS
|
||||
"KDDC", // Dodge City, KS
|
||||
"KSGF", // Springfield, MO
|
||||
"KEAX", // Kansas City, MO
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||
ctx := context.Background()
|
||||
|
||||
fetcher, err := radar.NewFetcher(ctx)
|
||||
if err != nil {
|
||||
logger.Error("init fetcher", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
store := server.NewRadarStore()
|
||||
cache := server.NewTileCache()
|
||||
|
||||
ingestAll(ctx, logger, fetcher, store, cache)
|
||||
|
||||
go func() {
|
||||
tick := time.NewTicker(ingestEvery)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-tick.C:
|
||||
ingestAll(ctx, logger, fetcher, store, cache)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
indexTmpl := template.Must(template.ParseFiles("web/templates/index.html"))
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.RequestID)
|
||||
@@ -23,6 +75,31 @@ func main() {
|
||||
w.Write([]byte("ok"))
|
||||
})
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
indexTmpl.Execute(w, nil)
|
||||
})
|
||||
|
||||
// Composite tile: product + frame in path (new)
|
||||
r.Get("/api/v1/tile/composite/{product}/{frame}/{z}/{x}/{y}.png", server.CompositeTileHandler(store, cache))
|
||||
// Legacy composite route (product=reflectivity, frame=latest)
|
||||
r.Get("/api/v1/tile/composite/{z}/{x}/{y}.png", server.CompositeTileHandler(store, cache))
|
||||
// Per-site tile
|
||||
r.Get("/api/v1/tile/{site}/{z}/{x}/{y}.png", server.TileHandler(store, cache))
|
||||
|
||||
// Frame metadata for the loop player
|
||||
r.Get("/api/v1/frames", server.FramesHandler(store))
|
||||
|
||||
// Scan time (KTLX reference)
|
||||
r.Get("/api/v1/scan-time", func(w http.ResponseWriter, r *http.Request) {
|
||||
p := store.GetLatest("ktlx", "reflectivity")
|
||||
if p == nil {
|
||||
w.Write([]byte("—"))
|
||||
return
|
||||
}
|
||||
w.Write([]byte(p.Time.Format("15:04 UTC")))
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: r,
|
||||
@@ -37,3 +114,54 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// ingestAll fetches reflectivity + velocity for every site concurrently.
|
||||
func ingestAll(ctx context.Context, logger *slog.Logger, f *radar.Fetcher, store *server.RadarStore, cache *server.TileCache) {
|
||||
var wg sync.WaitGroup
|
||||
for _, site := range radarSites {
|
||||
wg.Add(2)
|
||||
go func(s string) {
|
||||
defer wg.Done()
|
||||
if err := ingestRefl(ctx, logger, f, store, s); err != nil {
|
||||
logger.Warn("refl ingest failed", "site", s, "err", err)
|
||||
}
|
||||
}(site)
|
||||
go func(s string) {
|
||||
defer wg.Done()
|
||||
if err := ingestVel(ctx, logger, f, store, s); err != nil {
|
||||
logger.Warn("vel ingest failed", "site", s, "err", err)
|
||||
}
|
||||
}(site)
|
||||
}
|
||||
wg.Wait()
|
||||
cache.Invalidate()
|
||||
logger.Info("ingest cycle complete")
|
||||
}
|
||||
|
||||
func ingestRefl(ctx context.Context, logger *slog.Logger, f *radar.Fetcher, store *server.RadarStore, site string) error {
|
||||
data, err := f.FetchLatest(ctx, site)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := radar.Parse(site, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.Set(site, "reflectivity", p)
|
||||
logger.Info("refl ok", "site", site, "time", p.Time, "radials", len(p.Radials))
|
||||
return nil
|
||||
}
|
||||
|
||||
func ingestVel(ctx context.Context, logger *slog.Logger, f *radar.Fetcher, store *server.RadarStore, site string) error {
|
||||
data, err := f.FetchVelocity(ctx, site)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := radar.ParseVelocity(site, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store.Set(site, "velocity", p)
|
||||
logger.Info("vel ok", "site", site, "time", p.Time)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user