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.
This commit is contained in:
Graham McIntire 2026-03-31 09:01:48 -05:00
parent 321133c3cb
commit 97d4bd9372
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
3 changed files with 47 additions and 16 deletions

View file

@ -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()
}

View file

@ -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)))

View file

@ -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"
>
</div>