From 97d4bd937289c7d15e5846e2d6ed903217d5087a Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Tue, 31 Mar 2026 09:01:48 -0500 Subject: [PATCH] Only send visible map scores based on viewport bounds The JS hook sends map bounds on load and on pan/zoom. The server queries only scores within those bounds, dramatically reducing the payload for band switches and map updates. At zoom 7 (DFW area) this sends ~2k scores instead of ~95k. --- assets/js/propagation_map_hook.js | 17 ++++++++++++++--- lib/microwaveprop/propagation.ex | 22 +++++++++++++++------- lib/microwaveprop_web/live/map_live.ex | 24 ++++++++++++++++++------ 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/assets/js/propagation_map_hook.js b/assets/js/propagation_map_hook.js index c6ba812f..6dd6a856 100644 --- a/assets/js/propagation_map_hook.js +++ b/assets/js/propagation_map_hook.js @@ -23,13 +23,14 @@ export const PropagationMap = { { min: 0, r: 255, g: 79, b: 79 } // NEGLIGIBLE - red ] - const initialScores = JSON.parse(this.el.dataset.scores || "[]") - this.renderScores(initialScores) - this.handleEvent("update_scores", ({ scores }) => { this.renderScores(scores) }) + // Send bounds to server on load and on pan/zoom + this.sendBounds() + this.map.on("moveend", () => this.sendBounds()) + // Legend const legend = L.control({ position: "bottomright" }) legend.onAdd = () => { @@ -166,6 +167,16 @@ export const PropagationMap = { return { r: last.r, g: last.g, b: last.b } }, + sendBounds() { + const b = this.map.getBounds() + this.pushEvent("map_bounds", { + south: b.getSouth(), + north: b.getNorth(), + west: b.getWest(), + east: b.getEast() + }) + }, + destroyed() { if (this.map) this.map.remove() } diff --git a/lib/microwaveprop/propagation.ex b/lib/microwaveprop/propagation.ex index e2a5c14b..ebeb6456 100644 --- a/lib/microwaveprop/propagation.ex +++ b/lib/microwaveprop/propagation.ex @@ -88,8 +88,8 @@ defmodule Microwaveprop.Propagation do {:ok, total} end - @doc "Get the latest scores for a band." - def latest_scores(band_mhz) do + @doc "Get the latest scores for a band, optionally within a bounding box." + def latest_scores(band_mhz, bounds \\ nil) do latest_time_query = from(gs in GridScore, where: gs.band_mhz == ^band_mhz, @@ -101,15 +101,23 @@ defmodule Microwaveprop.Propagation do [] latest_time -> - Repo.all( - from(gs in GridScore, - where: gs.band_mhz == ^band_mhz and gs.valid_time == ^latest_time, - select: %{lat: gs.lat, lon: gs.lon, score: gs.score} - ) + from(gs in GridScore, + where: gs.band_mhz == ^band_mhz and gs.valid_time == ^latest_time, + select: %{lat: gs.lat, lon: gs.lon, score: gs.score} ) + |> maybe_filter_bounds(bounds) + |> Repo.all() end end + defp maybe_filter_bounds(query, nil), do: query + + defp maybe_filter_bounds(query, %{"south" => s, "north" => n, "west" => w, "east" => e}) do + from(gs in query, + where: gs.lat >= ^s and gs.lat <= ^n and gs.lon >= ^w and gs.lon <= ^e + ) + end + @doc "Get the latest valid_time across all scores." def latest_valid_time do Repo.one(from(gs in GridScore, select: max(gs.valid_time))) diff --git a/lib/microwaveprop_web/live/map_live.ex b/lib/microwaveprop_web/live/map_live.ex index 7c45e530..35e9431a 100644 --- a/lib/microwaveprop_web/live/map_live.ex +++ b/lib/microwaveprop_web/live/map_live.ex @@ -10,7 +10,6 @@ defmodule MicrowavepropWeb.MapLive do @impl true def mount(_params, _session, socket) do bands = BandConfig.all_bands() - scores = Propagation.latest_scores(@default_band) valid_time = Propagation.latest_valid_time() if connected?(socket) do @@ -22,15 +21,16 @@ defmodule MicrowavepropWeb.MapLive do page_title: "Propagation Map", bands: bands, selected_band: @default_band, - scores: scores, - valid_time: valid_time + scores: [], + valid_time: valid_time, + bounds: nil )} end @impl true def handle_event("select_band", %{"value" => band_str}, socket) do band = String.to_integer(band_str) - scores = Propagation.latest_scores(band) + scores = Propagation.latest_scores(band, socket.assigns.bounds) socket = socket @@ -41,9 +41,21 @@ defmodule MicrowavepropWeb.MapLive do {:noreply, socket} end + def handle_event("map_bounds", bounds, socket) do + scores = Propagation.latest_scores(socket.assigns.selected_band, bounds) + + socket = + socket + |> assign(:bounds, bounds) + |> assign(:scores, scores) + |> push_event("update_scores", %{scores: scores}) + + {:noreply, socket} + end + @impl true def handle_info({:propagation_updated, valid_time}, socket) do - scores = Propagation.latest_scores(socket.assigns.selected_band) + scores = Propagation.latest_scores(socket.assigns.selected_band, socket.assigns.bounds) socket = socket @@ -70,7 +82,7 @@ defmodule MicrowavepropWeb.MapLive do id="propagation-map" phx-hook="PropagationMap" phx-update="ignore" - data-scores={Jason.encode!(@scores)} + data-scores="[]" class="absolute inset-0 z-0" >