prop/lib/microwaveprop_web/grid_bounds.ex
Graham McIntire 7c37bf67fe
refactor(web): drop the unreachable viewport_too_large guard
Raising the area cap to 4600 put it above the largest area clamp/1 can
emit (45° lat × 100° lon = 4500 sq deg), so :viewport_too_large became
unreachable and the 413 handlers in both cell controllers became dead
code.

Remove the cap, the error branch, the max_viewport_area_sq_deg/0
accessor and both payload_too_large/1 handlers. The clamp is what
bounds request cost: post-clamp nothing can ask for more than the full
grid (~92k cells, ~1.4 MB), which is materialized once per valid_time
and served from cache, so a global viewport costs no more than a fully
zoomed-out map.

Replaces the cap test with one asserting the clamp never lets absurd
input escape the supported extent — the property the guard was
standing in for.
2026-08-05 17:51:55 -05:00

55 lines
2 KiB
Elixir

defmodule MicrowavepropWeb.GridBounds do
@moduledoc """
Bounds clamp + sanity checks for grid-cell HTTP endpoints
(`/scores/cells`, `/weather/cells`).
The propagation grid covers HRRR (CONUS) and HRDPS (Canada to
60°N): roughly lat 25-60, lon -141 to -52. Any caller-provided
viewport is clamped to a generous superset of that bbox, which is
what bounds the work: after clamping no request can ask for more
than the full grid (~92k cells, ~1.4 MB), already materialized once
per valid_time and served from cache. A global or otherwise absurd
viewport therefore costs no more than a fully zoomed-out map does.
`nil` bounds are passed through (caller intends the full grid).
"""
# Generous superset of HRRR + HRDPS — a few extra degrees on each
# edge so legitimate clients zoomed slightly past the data extent
# still get a 200 with the actual covered cells.
@lat_min 20.0
@lat_max 65.0
@lon_min -150.0
@lon_max -50.0
@type bounds :: %{optional(String.t()) => float()} | nil
@doc """
Clamp `bounds` to the supported grid extent. Returns:
* `{:ok, nil}` when bounds are nil (caller wants the full grid)
* `{:ok, clamped_bounds}` otherwise
* `{:error, :invalid_bounds}` when north < south or east < west
"""
@spec clamp(bounds()) :: {:ok, bounds()} | {:error, :invalid_bounds}
def clamp(nil), do: {:ok, nil}
def clamp(%{"south" => s, "north" => n, "west" => w, "east" => e})
when is_number(s) and is_number(n) and is_number(w) and is_number(e) do
south = clamp_value(s, @lat_min, @lat_max)
north = clamp_value(n, @lat_min, @lat_max)
west = clamp_value(w, @lon_min, @lon_max)
east = clamp_value(e, @lon_min, @lon_max)
if north < south or east < west do
{:error, :invalid_bounds}
else
{:ok, %{"south" => south, "north" => north, "west" => west, "east" => east}}
end
end
def clamp(_), do: {:error, :invalid_bounds}
defp clamp_value(v, lo, hi), do: v |> max(lo) |> min(hi)
end