package radar import ( "context" "fmt" "io" "net/http" "strings" "time" ) // nwsRadarBase is the NWS public product distribution server. // No credentials required; sn.last always holds the most recent scan. const nwsRadarBase = "https://tgftp.nws.noaa.gov/SL.us008001/DF.of/DC.radar" // Fetcher downloads NEXRAD Level 3 products from the NWS public server. type Fetcher struct { client *http.Client } func NewFetcher(_ context.Context) (*Fetcher, error) { return &Fetcher{client: &http.Client{Timeout: 30 * time.Second}}, nil } // FetchLatest downloads the latest Level 3 base reflectivity (0.5°) for site. func (f *Fetcher) FetchLatest(ctx context.Context, site string) ([]byte, error) { return f.fetchDS(ctx, site, "p94r0") } // FetchVelocity downloads the latest Level 3 base velocity (0.5°) for site. func (f *Fetcher) FetchVelocity(ctx context.Context, site string) ([]byte, error) { return f.fetchDS(ctx, site, "p99r0") } func (f *Fetcher) fetchDS(ctx context.Context, site, ds string) ([]byte, error) { url := fmt.Sprintf("%s/DS.%s/SI.%s/sn.last", nwsRadarBase, ds, strings.ToLower(site)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } resp, err := f.client.Do(req) if err != nil { return nil, fmt.Errorf("NWS fetch: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("NWS returned %s for %s", resp.Status, url) } return io.ReadAll(resp.Body) }