feat(map): show CONUS-only banner for visitors outside continental US

Visitor geolocation already flows via the Cloudflare CF-IPLatitude /
CF-IPLongitude headers into the session; MapLive centers the map on
the visitor's location (or DFW as fallback). Add a banner that appears
above the map when those coords are outside the CONUS bbox
(lat 24.5-49.5, lon -125 to -66.5), letting overseas visitors know
propagation data won't be available at their location yet.

Map still centers on the visitor's actual location — this is a note,
not a re-center. Banner is fully absent from the DOM when not needed
(:if conditional).
This commit is contained in:
Graham McIntire 2026-04-17 08:28:07 -05:00
parent e57835c955
commit 6f452e7139
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 69 additions and 0 deletions

View file

@ -15,6 +15,14 @@ defmodule MicrowavepropWeb.MapLive do
@default_center %{lat: 32.897, lon: -97.038}
@default_zoom 7
# Continental-US bounding box (excludes Alaska, Hawaii, Puerto Rico). Used
# to decide whether to show the "data is CONUS-only" banner to visitors
# whose Cloudflare geolocation falls outside the dataset's coverage.
@conus_lat_min 24.5
@conus_lat_max 49.5
@conus_lon_min -125.0
@conus_lon_max -66.5
# How often to re-query oban_jobs for pipeline state while the map is
# open. Short enough that the "Updating…" chip shows up within a page-
# visit for an in-progress hourly run, long enough to be cheap.
@ -53,6 +61,7 @@ defmodule MicrowavepropWeb.MapLive do
valid_times = Propagation.available_valid_times(selected_band)
selected_time = recovered_time || closest_to_now(valid_times)
center = initial_center(session)
visitor = visitor_location(session)
bounds = bounds_around(center)
initial_scores = Propagation.scores_at(selected_band, selected_time, bounds)
@ -73,6 +82,7 @@ defmodule MicrowavepropWeb.MapLive do
bounds: bounds,
initial_center: center,
initial_zoom: @default_zoom,
outside_conus: outside_conus?(visitor),
grid_visible: false,
radar_visible: false,
antenna_height_ft: 33
@ -86,6 +96,25 @@ defmodule MicrowavepropWeb.MapLive do
defp initial_center(_session), do: @default_center
# Return the visitor's Cloudflare geolocation as a %{lat:, lon:} map, or
# nil when the CF headers weren't set (local dev, direct connect, tests
# without a stubbed session).
defp visitor_location(%{"cf_lat" => lat, "cf_lon" => lon}) when is_float(lat) and is_float(lon) do
%{lat: lat, lon: lon}
end
defp visitor_location(_session), do: nil
# True when the visitor's location is known and falls outside the CONUS
# bounding box. Missing geolocation returns false so we don't flash a
# banner at anyone we can't actually place.
defp outside_conus?(nil), do: false
defp outside_conus?(%{lat: lat, lon: lon}) do
lat < @conus_lat_min or lat > @conus_lat_max or
lon < @conus_lon_min or lon > @conus_lon_max
end
# Build a bounding box roughly matching the hardcoded default (~3.4° × 9°)
# around a given center so the initial HRRR score query still returns a
# useful tile set for the visible area.
@ -486,6 +515,22 @@ defmodule MicrowavepropWeb.MapLive do
<div class="flex w-screen h-screen overflow-hidden">
<%!-- Map container --%>
<div class="relative flex-1 min-w-0">
<%!-- CONUS-only data banner (shown only when visitor geolocation is
outside the continental US) --%>
<div
:if={@outside_conus}
id="conus-banner"
role="status"
class="alert alert-info absolute top-2 left-1/2 -translate-x-1/2 z-[1002] max-w-[calc(100vw-1rem)] md:max-w-xl shadow-lg text-sm"
>
<.icon name="hero-information-circle" class="size-5 shrink-0" />
<span>
This data set currently covers the continental United States only.
You're viewing the map centered on your location, but propagation
data won't be available there yet.
</span>
</div>
<%!-- Full-page map --%>
<div
id="propagation-map"

View file

@ -51,6 +51,30 @@ defmodule MicrowavepropWeb.MapLiveTest do
end
end
describe "CONUS-only banner" do
@banner_copy "This data set currently covers the continental United States only."
test "is hidden when no CF lat/lon in session", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/map")
refute html =~ ~s(id="conus-banner")
refute html =~ @banner_copy
end
test "is hidden when CF lat/lon is inside CONUS (DFW)", %{conn: conn} do
conn = Phoenix.ConnTest.init_test_session(conn, %{cf_lat: 32.9, cf_lon: -97.0})
{:ok, _lv, html} = live(conn, ~p"/map")
refute html =~ ~s(id="conus-banner")
refute html =~ @banner_copy
end
test "is visible when CF lat/lon is outside CONUS (London)", %{conn: conn} do
conn = Phoenix.ConnTest.init_test_session(conn, %{cf_lat: 51.5, cf_lon: -0.1})
{:ok, _lv, html} = live(conn, ~p"/map")
assert html =~ ~s(id="conus-banner")
assert html =~ @banner_copy
end
end
describe "select_band" do
test "updates selected band on event", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/map")