defmodule Microwaveprop.Propagation.GridTest do use ExUnit.Case, async: true alias Microwaveprop.Propagation.Grid describe "conus_points/0" do test "generates grid at 0.125 degree resolution" do points = Grid.conus_points() assert length(points) > 5000 assert length(points) < 100_000 end test "all points within CONUS bounds" do Enum.each(Grid.conus_points(), fn {lat, lon} -> assert lat >= 25.0 and lat <= 50.0 assert lon >= -125.0 and lon <= -66.0 end) end test "points are on 0.125 degree grid" do Enum.each(Grid.conus_points(), fn {lat, lon} -> assert Float.round(lat * 8, 0) == lat * 8 assert Float.round(lon * 8, 0) == lon * 8 end) end test "first point is at SW corner" do [{lat, lon} | _] = Grid.conus_points() assert lat == 25.0 assert lon == -125.0 end test "last point is at NE corner" do points = Grid.conus_points() {lat, lon} = List.last(points) assert lat == 50.0 assert lon == -66.0 end end describe "step/0" do test "returns 0.125" do assert Grid.step() == 0.125 end end describe "bounds/0" do test "returns CONUS bounding box" do bounds = Grid.bounds() assert bounds.lat_min == 25.0 assert bounds.lat_max == 50.0 assert bounds.lon_min == -125.0 assert bounds.lon_max == -66.0 end end describe "contains?/1" do test "true for DFW (firmly inside the box)" do assert Grid.contains?(%{"lat" => 32.9, "lon" => -97.0}) end test "true for every corner of the CONUS bounding box (inclusive)" do assert Grid.contains?(%{"lat" => 25.0, "lon" => -125.0}) assert Grid.contains?(%{"lat" => 50.0, "lon" => -66.0}) assert Grid.contains?(%{"lat" => 25.0, "lon" => -66.0}) assert Grid.contains?(%{"lat" => 50.0, "lon" => -125.0}) end test "false for OCONUS positions (UK, Winnipeg, Alberta, etc.)" do # 27 stuck hrrr-queued contacts in prod landed on points like # these. Add them as regression fixtures. refute Grid.contains?(%{"lat" => 51.4, "lon" => 0.47}) refute Grid.contains?(%{"lat" => 51.39, "lon" => -114.04}) refute Grid.contains?(%{"lat" => 50.1875, "lon" => -97.625}) end test "false for nil pos or missing keys" do refute Grid.contains?(nil) refute Grid.contains?(%{}) refute Grid.contains?(%{"lat" => 32.9}) refute Grid.contains?(%{"lon" => -97.0}) end test "false when lat or lon is nil" do refute Grid.contains?(%{"lat" => nil, "lon" => -97.0}) refute Grid.contains?(%{"lat" => 32.9, "lon" => nil}) end end describe "wgrib2_grid_spec/0" do test "lon_start / lat_start match the bounding box SW corner" do spec = Grid.wgrib2_grid_spec() assert spec.lon_start == -125.0 assert spec.lat_start == 25.0 end test "grid step matches Grid.step/0 on both axes" do spec = Grid.wgrib2_grid_spec() assert spec.lon_step == Grid.step() assert spec.lat_step == Grid.step() end test "lon_count and lat_count span the full CONUS box inclusive" do spec = Grid.wgrib2_grid_spec() # (lon_max - lon_min) / step + 1 = (-66 - -125) / 0.125 + 1 = 473 assert spec.lon_count == 473 # (lat_max - lat_min) / step + 1 = (50 - 25) / 0.125 + 1 = 201 assert spec.lat_count == 201 end test "total cells in wgrib2_grid_spec matches conus_points length" do spec = Grid.wgrib2_grid_spec() points = Grid.conus_points() assert length(points) == spec.lon_count * spec.lat_count end end describe "hrdps_only_points/0" do test "covers cells north of HRRR's lat_max" do points = Grid.hrdps_only_points() # At least one cell strictly north of HRRR's lat_max=50 assert Enum.any?(points, fn {lat, _lon} -> lat > 50.0 end) end test "is disjoint from conus_points/0" do conus = MapSet.new(Grid.conus_points()) hrdps = MapSet.new(Grid.hrdps_only_points()) assert MapSet.disjoint?(conus, hrdps), "hrdps_only_points must not overlap conus_points" end test "all points within HRDPS bbox" do Enum.each(Grid.hrdps_only_points(), fn {lat, lon} -> assert lat >= 49.0 and lat <= 60.0 assert lon >= -141.0 and lon <= -52.0 end) end test "points are on 0.125 degree grid" do Enum.each(Grid.hrdps_only_points(), fn {lat, lon} -> assert Float.round(lat * 8, 0) == lat * 8 assert Float.round(lon * 8, 0) == lon * 8 end) end test "no point is inside the CONUS bounding box" do Enum.each(Grid.hrdps_only_points(), fn {lat, lon} -> in_conus = lat >= 25.0 and lat <= 50.0 and lon >= -125.0 and lon <= -66.0 refute in_conus, "point #{lat},#{lon} is inside CONUS bbox" end) end end describe "point_source/1" do test "classifies cells inside CONUS as :hrrr" do assert Grid.point_source({32.9, -97.0}) == :hrrr assert Grid.point_source({49.0, -98.0}) == :hrrr end test "classifies Canadian cells outside HRRR as :hrdps" do assert Grid.point_source({53.32, -60.42}) == :hrdps assert Grid.point_source({50.125, -97.0}) == :hrdps end test "classifies points outside both bboxes as :neither" do assert Grid.point_source({-30.0, 150.0}) == :neither assert Grid.point_source({65.0, -90.0}) == :neither end test "accepts %{lat: _, lon: _} maps as well as tuples" do assert Grid.point_source(%{lat: 32.9, lon: -97.0}) == :hrrr assert Grid.point_source(%{lat: 53.32, lon: -60.42}) == :hrdps end end end