Documentation
¶
Overview ¶
Package radar answers "is it likely to rain or snow where this truck will be, when it gets there?" from NOAA RRFS forecast composite reflectivity. A build job (rivolt-infra apps/platform/maps/radar) turns each forecast hour into one small grid file and serves it inside the cluster; this package reads those files, projects a point onto the grid, and reports the strongest echo around it at the hours either side of an arrival time. Nothing here talks to NOAA, and nothing about a user leaves the server.
Package radar serves live weather radar tiles from NOAA/NWS (single-owner-era "live radar" feature).
The source is the National Weather Service's open GeoServer (opengeo.ncep.noaa.gov): the MRMS (Multi-Radar/Multi-Sensor) base reflectivity mosaic, quality controlled, 1 km, a frame about every two minutes and about two hours of history, for CONUS, Alaska, Hawaii, the Caribbean and Guam. NWS content is public domain, free for any lawful purpose provided we do not claim it as our own, imply an endorsement or affiliation, or modify it and present it as official (weather.gov/disclaimer; the service's GetCapabilities says Fees none, AccessConstraints none).
Users' browsers and phones never call NOAA: they ask this server for tiles, and NOAA sees only this server, with no user, no viewport and no account. A tile is cached by frame time (shared by every user, so the upstream load follows the number of distinct tiles in use and the frame rate, not the number of users) in process and, where the install has Redis, across replicas, with one fetch per tile per frame across pods.
Index ¶
Constants ¶
const ( TileSize = 256 MaxZoom = 10 // deepest zoom the route admits; the client asks up to ClientMaxZoom and the map overzooms past it ClientMinZoom = 3 // below this a continent-sized radar picture is useless and the tile count is large ClientMaxZoom = 9 // the mosaic is 1 km: finer zooms only resample it )
Tile and cache parameters.
const ( // MaxCycleAge is how old a cycle may be (by its nominal time) before its hours are treated as absent. Extended cycles come every 6 h and // appear about 3 h after their nominal time, so 9 h keeps one cycle usable through the whole gap until the next. MaxCycleAge = 9 * time.Hour // DefaultCacheBytes caps the decoded grids a pod holds (about 22 hours of CONUS). DefaultCacheBytes = 48 << 20 )
const DefaultBaseURL = "https://opengeo.ncep.noaa.gov/geoserver"
DefaultBaseURL is the NWS GeoServer.
const NoData = 255
NoData is the reserved byte of the dBZ plane: no value there. It is never read as "dry".
const RainDBZ = 20.0
RainDBZ is the reflectivity at which a cell counts as "rain likely". Below it, echoes are drizzle-or-less or clutter.
Variables ¶
ErrUnavailable means the radar feed could not be read or is stale: the caller answers 503 and the client hides the layer for now.
Functions ¶
func ParseTilePath ¶
ParseTilePath parses "/z/x/y.png" strictly: no leading zeros, no extra segments or characters, x and y inside the zoom's grid, z up to MaxZoom.
func ResampleRows ¶
ResampleRows turns an EPSG:4326 picture of a tile's lon/lat box into the Web Mercator tile: columns map one to one (longitude is linear in both); each output row takes the source row at its own latitude. src spans the tile's box top to bottom.
Types ¶
type Config ¶
type Config struct {
BaseURL string // the GeoServer; empty is DefaultBaseURL
HTTP *http.Client
Redis redis.Cmdable // nil: process-local caching only
Prefix string // Redis key prefix (the install's)
Now func() time.Time
// MaxUpstream bounds the concurrent requests to NOAA from this pod (default 6).
MaxUpstream int
// UserAgent identifies this server to NOAA (default names Rivolt and a contact).
UserAgent string
}
Config wires a Service.
type Echo ¶
Echo is what a point sees at one forecast hour: the strongest dBZ in the 3x3 cells around it, whether any of them says snow, and whether any cell had a value at all.
type Frames ¶
Frames is what a client needs to show "radar as of": the newest frame and the recent ones (oldest first).
type Grid ¶
type Grid struct {
Header Header
// contains filtered or unexported fields
}
Grid is one forecast hour: the dBZ plane as the file has it, and the snow flags as a bit set (a missing snow flag reads as no snow claim).
type Header ¶
type Header struct {
V int `json:"v"`
Domain string `json:"domain"`
Cycle string `json:"cycle"`
FH int `json:"fh"`
CycleTime time.Time `json:"cycle_time"`
ValidTime time.Time `json:"valid_time"`
NX int `json:"nx"`
NY int `json:"ny"`
X0 float64 `json:"x0"` // projected metres of the grid's top-left corner
Y0 float64 `json:"y0"`
DX float64 `json:"dx"` // metres per column
DY float64 `json:"dy"` // metres per row (negative: row 0 is the top)
DBZOffset float64 `json:"dbz_offset"`
DBZStep float64 `json:"dbz_step"`
NoData int `json:"nodata"`
Planes []string `json:"planes"`
Proj LCC `json:"proj"`
}
Header is the JSON header of a grid file (see radar_build.py).
type LCC ¶
type LCC struct {
Type string `json:"type"`
Lat0 float64 `json:"lat0"`
Lon0 float64 `json:"lon0"`
Lat1 float64 `json:"lat1"`
Lat2 float64 `json:"lat2"`
RadiusM float64 `json:"radius_m"`
}
LCC is the Lambert conformal conic projection of the RRFS CONUS grid on a sphere (the parameters come from the file's header).
type Level ¶
type Level string
Level is the coarse reading shown to people. Amounts are never claimed.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service fetches, reprojects if it must, and caches radar tiles.
func (*Service) Frames ¶
Frames returns the newest frame and the recent ones, from the CONUS mosaic's time dimension (the others follow within seconds and are asked for by nearest time). Cached for a minute in process and across pods.
func (*Service) Handler ¶
Handler is the radar routes, mounted with its prefix stripped (/api/maps/radar):
GET /frame -> {"frame":"2026-11-09T00:24:11Z","frame_unix":1794266651,"frames_unix":[...oldest first...],"min_zoom":3,"max_zoom":9}
GET /{z}/{x}/{y}.png -> a 256x256 PNG, transparent where there is no radar (outside the US mosaics, or no echo).
Query t=<frame_unix> asks for one of the recent frames (anything else is the newest).
Both answer the frame time in X-Radar-Frame (RFC 3339, UTC). An unreachable or stale feed is 503 with Retry-After: the client hides the layer for a while; it is never a transparent 200, which would read as "no rain". Nothing about the caller is sent upstream or logged.
func (*Service) Tile ¶
func (s *Service) Tile(ctx context.Context, z, x, y int, frameUnix int64) ([]byte, time.Time, error)
Tile returns the radar tile (z, x, y, Web Mercator) for the frame at frameUnix (0: the newest), the frame actually used, and the PNG. A tile outside every mosaic is a transparent PNG. A frame that is not one of the recent frames is replaced by the newest, so the time parameter is not an open relay into NOAA's history.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store fetches forecast hours from the in-cluster radar service and keeps decoded grids in a size-capped LRU.
func NewStore ¶
NewStore returns a store reading from baseURL (for example http://radar.radar.svc), or nil when baseURL is empty (radar off).
func (*Store) At ¶
At answers for a point at time t, or false when the forecast cannot say: radar is off, the point is outside the grid or the horizon, the newest usable cycle is too old, or the service did not answer. Absence is never reported as dry.
func (*Store) CachedBytes ¶
CachedBytes is the decoded size currently held, for a gauge.