Batch RF path queries, batch marker removals, and JS callsign index
- Replace N+1 get_latest_packet_for_callsign calls in RF path loading with single get_latest_packets_for_callsigns batch query (DISTINCT ON) - Change batch packet processing to collect marker removal IDs and push one remove_markers_batch event instead of individual remove_marker calls - Add callsignIndex reverse map (callsign_group → Set<markerID>) for O(1) lookup in new_packet/new_packets handlers instead of scanning all markers
This commit is contained in:
parent
4c8023f1f1
commit
84e65232e5
8 changed files with 273 additions and 80 deletions
144
assets/js/map.ts
144
assets/js/map.ts
|
|
@ -454,6 +454,9 @@ let MapAPRSMap = {
|
|||
// Track marker states to prevent unnecessary operations
|
||||
self.markerStates = new Map<string, MarkerState>();
|
||||
|
||||
// Reverse index: callsign_group → Set of marker IDs for O(1) lookup
|
||||
self.callsignIndex = new Map<string, Set<string>>();
|
||||
|
||||
// Store RF path lines for hover visualization
|
||||
self.rfPathLines = [];
|
||||
|
||||
|
|
@ -1043,7 +1046,7 @@ let MapAPRSMap = {
|
|||
data.callsign_group || data.callsign || data.id;
|
||||
|
||||
if (incomingCallsign) {
|
||||
// Find existing live markers for this callsign and convert them to historical dots
|
||||
// Find existing live markers for this callsign using reverse index for O(1) lookup
|
||||
const markersToConvert: string[] = [];
|
||||
|
||||
// Ensure markerStates exists
|
||||
|
|
@ -1052,20 +1055,19 @@ let MapAPRSMap = {
|
|||
return;
|
||||
}
|
||||
|
||||
self.markerStates.forEach((state, id) => {
|
||||
const stateCallsign = state.callsign_group || state.callsign;
|
||||
// Convert markers for the same callsign that are either live or were the
|
||||
// most-recent marker (historical loading marks all packets as historical,
|
||||
// so is_most_recent_for_callsign catches the "current" marker that was
|
||||
// loaded from history)
|
||||
if (
|
||||
stateCallsign === incomingCallsign &&
|
||||
(!state.historical || state.is_most_recent_for_callsign) &&
|
||||
String(id) !== String(data.id)
|
||||
) {
|
||||
markersToConvert.push(String(id));
|
||||
const existingIds = self.callsignIndex?.get(incomingCallsign);
|
||||
if (existingIds) {
|
||||
for (const id of existingIds) {
|
||||
const state = self.markerStates.get(id);
|
||||
if (
|
||||
state &&
|
||||
(!state.historical || state.is_most_recent_for_callsign) &&
|
||||
String(id) !== String(data.id)
|
||||
) {
|
||||
markersToConvert.push(String(id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Convert existing live markers to historical dots by updating their icon
|
||||
markersToConvert.forEach((id) => {
|
||||
|
|
@ -1146,46 +1148,39 @@ let MapAPRSMap = {
|
|||
const serverConvertKeys = payload.convert_to_historical;
|
||||
let markersToConvert: string[] = [];
|
||||
|
||||
// Build set of new packet IDs to avoid converting a marker we're about to add
|
||||
const allNewIds = new Set<string>();
|
||||
for (const data of packets) {
|
||||
if (data.id) allNewIds.add(String(data.id));
|
||||
}
|
||||
|
||||
// Determine which callsigns need conversion
|
||||
const callsignsToConvert: Set<string> = new Set();
|
||||
if (serverConvertKeys && serverConvertKeys.length > 0) {
|
||||
// Server told us which callsigns have replacements — find their
|
||||
// current marker IDs by scanning only the provided callsigns
|
||||
const convertSet = new Set(serverConvertKeys);
|
||||
const allNewIds = new Set<string>();
|
||||
for (const data of packets) {
|
||||
if (data.id) allNewIds.add(String(data.id));
|
||||
}
|
||||
self.markerStates.forEach((state, id) => {
|
||||
const stateCallsign = state.callsign_group || state.callsign;
|
||||
if (
|
||||
stateCallsign &&
|
||||
convertSet.has(stateCallsign) &&
|
||||
(!state.historical || state.is_most_recent_for_callsign) &&
|
||||
!allNewIds.has(String(id))
|
||||
) {
|
||||
markersToConvert.push(String(id));
|
||||
}
|
||||
});
|
||||
for (const cs of serverConvertKeys) callsignsToConvert.add(cs);
|
||||
} else {
|
||||
// Fallback: scan all markerStates (e.g. for batches with no replacements)
|
||||
const allNewCallsigns = new Set<string>();
|
||||
const allNewIds = new Set<string>();
|
||||
for (const data of packets) {
|
||||
const cs = data.callsign_group || data.callsign || data.id;
|
||||
if (cs) allNewCallsigns.add(cs);
|
||||
if (data.id) allNewIds.add(String(data.id));
|
||||
if (cs) callsignsToConvert.add(cs);
|
||||
}
|
||||
}
|
||||
|
||||
self.markerStates.forEach((state, id) => {
|
||||
const stateCallsign = state.callsign_group || state.callsign;
|
||||
if (
|
||||
stateCallsign &&
|
||||
allNewCallsigns.has(stateCallsign) &&
|
||||
(!state.historical || state.is_most_recent_for_callsign) &&
|
||||
!allNewIds.has(String(id))
|
||||
) {
|
||||
markersToConvert.push(String(id));
|
||||
// Use callsignIndex for O(1) lookup per callsign instead of scanning all markerStates
|
||||
if (self.callsignIndex) {
|
||||
for (const cs of callsignsToConvert) {
|
||||
const ids = self.callsignIndex.get(cs);
|
||||
if (!ids) continue;
|
||||
for (const id of ids) {
|
||||
if (allNewIds.has(String(id))) continue;
|
||||
const state = self.markerStates.get(id);
|
||||
if (
|
||||
state &&
|
||||
(!state.historical || state.is_most_recent_for_callsign)
|
||||
) {
|
||||
markersToConvert.push(String(id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Convert all existing live markers to historical dots
|
||||
|
|
@ -1871,6 +1866,17 @@ let MapAPRSMap = {
|
|||
callsign: data.callsign,
|
||||
});
|
||||
|
||||
// Update callsign reverse index
|
||||
const csGroup = data.callsign_group || data.callsign;
|
||||
if (csGroup && self.callsignIndex) {
|
||||
let ids = self.callsignIndex.get(csGroup);
|
||||
if (!ids) {
|
||||
ids = new Set<string>();
|
||||
self.callsignIndex.set(csGroup, ids);
|
||||
}
|
||||
ids.add(data.id);
|
||||
}
|
||||
|
||||
// Initialize trail for new marker - always add to trail for line drawing
|
||||
if (self.trailManager) {
|
||||
const isHistoricalDot =
|
||||
|
|
@ -1952,6 +1958,18 @@ let MapAPRSMap = {
|
|||
}
|
||||
}
|
||||
|
||||
// Update callsign reverse index
|
||||
if (markerState && self.callsignIndex) {
|
||||
const csGroup = markerState.callsign_group || markerState.callsign;
|
||||
if (csGroup) {
|
||||
const ids = self.callsignIndex.get(csGroup);
|
||||
if (ids) {
|
||||
ids.delete(markerId);
|
||||
if (ids.size === 0) self.callsignIndex.delete(csGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove trail - use callsign_group for proper trail identification
|
||||
if (self.trailManager) {
|
||||
const trailId =
|
||||
|
|
@ -2052,6 +2070,7 @@ let MapAPRSMap = {
|
|||
// Replace the markers and states with just the preserved ones
|
||||
self.markers!.clear();
|
||||
self.markerStates!.clear();
|
||||
if (self.callsignIndex) self.callsignIndex.clear();
|
||||
|
||||
markersToPreserve.forEach((marker, id) => {
|
||||
self.markers!.set(id, marker);
|
||||
|
|
@ -2059,6 +2078,19 @@ let MapAPRSMap = {
|
|||
|
||||
statesToPreserve.forEach((state, id) => {
|
||||
self.markerStates!.set(id, state);
|
||||
|
||||
// Rebuild callsign index from preserved markers
|
||||
if (self.callsignIndex) {
|
||||
const csGroup = state.callsign_group || state.callsign;
|
||||
if (csGroup) {
|
||||
let ids = self.callsignIndex.get(csGroup);
|
||||
if (!ids) {
|
||||
ids = new Set<string>();
|
||||
self.callsignIndex.set(csGroup, ids);
|
||||
}
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Don't clear trails - keep them visible
|
||||
|
|
@ -2143,6 +2175,9 @@ let MapAPRSMap = {
|
|||
const marker = self.markers!.get(markerId);
|
||||
|
||||
if (marker) {
|
||||
// Get state before deletion for index cleanup
|
||||
const state = self.markerStates!.get(markerId);
|
||||
|
||||
try {
|
||||
// Remove marker from appropriate layer with safety checks
|
||||
if (
|
||||
|
|
@ -2175,6 +2210,18 @@ let MapAPRSMap = {
|
|||
self.markers!.delete(markerId);
|
||||
self.markerStates!.delete(markerId);
|
||||
}
|
||||
|
||||
// Update callsign reverse index
|
||||
if (state && self.callsignIndex) {
|
||||
const csGroup = state.callsign_group || state.callsign;
|
||||
if (csGroup) {
|
||||
const ids = self.callsignIndex.get(csGroup);
|
||||
if (ids) {
|
||||
ids.delete(markerId);
|
||||
if (ids.size === 0) self.callsignIndex.delete(csGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -2381,6 +2428,9 @@ let MapAPRSMap = {
|
|||
if (self.markerStates !== undefined) {
|
||||
self.markerStates!.clear();
|
||||
}
|
||||
if (self.callsignIndex !== undefined) {
|
||||
self.callsignIndex!.clear();
|
||||
}
|
||||
if (self.map !== undefined) {
|
||||
self.map!.remove();
|
||||
self.map = undefined;
|
||||
|
|
|
|||
1
assets/js/types/map.d.ts
vendored
1
assets/js/types/map.d.ts
vendored
|
|
@ -12,6 +12,7 @@ export interface LiveViewHookContext {
|
|||
map?: LeafletMap;
|
||||
markers?: Map<string, Marker>;
|
||||
markerStates?: Map<string, MarkerState>;
|
||||
callsignIndex?: Map<string, Set<string>>;
|
||||
markerLayer?: LayerGroup;
|
||||
heatLayer?: HeatLayer;
|
||||
trailManager?: TrailManager;
|
||||
|
|
|
|||
|
|
@ -875,6 +875,15 @@ defmodule Aprsme.Packets do
|
|||
PreparedQueries.get_latest_positions_for_callsigns(callsigns)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the latest full packet for each callsign in a single batch query.
|
||||
Returns a list of `Packet` structs with lat/lon populated.
|
||||
"""
|
||||
@spec get_latest_packets_for_callsigns(list(String.t())) :: [Packet.t()]
|
||||
def get_latest_packets_for_callsigns(callsigns) when is_list(callsigns) do
|
||||
PreparedQueries.get_latest_packets_for_callsigns(callsigns)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the most recent weather packet for a callsign.
|
||||
Looks for any packet containing weather data fields.
|
||||
|
|
|
|||
|
|
@ -27,6 +27,27 @@ defmodule Aprsme.Packets.PreparedQueries do
|
|||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get the latest full packet for each callsign in a single query.
|
||||
Returns a list of `Packet` structs with lat/lon populated.
|
||||
Uses DISTINCT ON to get only the most recent packet per callsign.
|
||||
"""
|
||||
@spec get_latest_packets_for_callsigns(list(String.t())) :: [Packet.t()]
|
||||
def get_latest_packets_for_callsigns([]), do: []
|
||||
|
||||
def get_latest_packets_for_callsigns(callsigns) when is_list(callsigns) do
|
||||
normalized = Enum.map(callsigns, &String.upcase(String.trim(&1)))
|
||||
|
||||
Repo.all(
|
||||
from(p in Packet,
|
||||
where: fragment("upper(?)", p.sender) in ^normalized,
|
||||
distinct: fragment("upper(?)", p.sender),
|
||||
order_by: [asc: fragment("upper(?)", p.sender), desc: p.received_at],
|
||||
select: %{p | lat: fragment("ST_Y(?)", p.location), lon: fragment("ST_X(?)", p.location)}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get latest positions for multiple callsigns in a single query.
|
||||
Returns a list of `%{callsign: String.t(), lat: float(), lng: float()}` maps.
|
||||
|
|
|
|||
|
|
@ -850,14 +850,23 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
end
|
||||
|
||||
def handle_info({:packet_batch, packets}, socket) do
|
||||
# Process batch: collect marker data without pushing events, then send one batch push
|
||||
{socket, marker_data_list} =
|
||||
Enum.reduce(packets, {socket, []}, fn packet, {acc_socket, markers} ->
|
||||
{new_socket, marker_data} = process_packet_for_batch(packet, acc_socket)
|
||||
# Process batch: collect marker data and removal IDs without pushing events
|
||||
{socket, marker_data_list, removal_ids} =
|
||||
Enum.reduce(packets, {socket, [], []}, fn packet, {acc_socket, markers, removals} ->
|
||||
{new_socket, marker_data, removed_id} = process_packet_for_batch(packet, acc_socket)
|
||||
markers = if marker_data, do: [marker_data | markers], else: markers
|
||||
{new_socket, markers}
|
||||
removals = if removed_id, do: [removed_id | removals], else: removals
|
||||
{new_socket, markers, removals}
|
||||
end)
|
||||
|
||||
# Push a single batched remove event for all removals
|
||||
socket =
|
||||
if removal_ids == [] do
|
||||
socket
|
||||
else
|
||||
DisplayManager.remove_markers_batch(socket, removal_ids)
|
||||
end
|
||||
|
||||
socket = assign(socket, :last_update_at, DateTime.utc_now())
|
||||
|
||||
# Send heat map update once if in heat map mode, otherwise batch markers
|
||||
|
|
@ -930,13 +939,8 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
end
|
||||
|
||||
def handle_info({:load_rf_path_station_packets, stations}, socket) do
|
||||
# Load the most recent packet for each RF path station
|
||||
station_packets =
|
||||
stations
|
||||
|> Enum.map(fn callsign ->
|
||||
Packets.get_latest_packet_for_callsign(callsign)
|
||||
end)
|
||||
|> Enum.filter(& &1)
|
||||
# Load the most recent packet for each RF path station in a single batch query
|
||||
station_packets = Packets.get_latest_packets_for_callsigns(stations)
|
||||
|
||||
socket =
|
||||
if Enum.any?(station_packets) do
|
||||
|
|
@ -1094,7 +1098,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
socket = assign(socket, :tracked_callsign_latest_packet, packet)
|
||||
PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
else
|
||||
{socket, nil}
|
||||
{socket, nil, nil}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
|
|||
|
||||
@doc """
|
||||
Process a packet for batch display. Updates socket state but does NOT push
|
||||
events. Returns `{socket, marker_data | nil}` where marker_data is the data
|
||||
that should be included in a batched push_event, or nil if no marker needed.
|
||||
events. Returns `{socket, marker_data | nil, removed_id | nil}` where
|
||||
marker_data is the data that should be included in a batched push_event,
|
||||
and removed_id is a callsign key that was removed from visible_packets
|
||||
(for batched remove_markers_batch events).
|
||||
"""
|
||||
@spec process_packet_for_marker_data(map(), Socket.t()) :: {Socket.t(), map() | nil}
|
||||
@spec process_packet_for_marker_data(map(), Socket.t()) :: {Socket.t(), map() | nil, String.t() | nil}
|
||||
def process_packet_for_marker_data(packet, socket) do
|
||||
{lat, lon, _data_extended} = CoordinateUtils.get_coordinates(packet)
|
||||
callsign_key = SharedPacketUtils.get_callsign_key(packet)
|
||||
|
|
@ -116,7 +118,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
|
|||
end
|
||||
|
||||
# Batch-mode visibility: same logic as visibility_action but returns
|
||||
# {socket, marker_data | nil} without pushing events
|
||||
# {socket, marker_data | nil, removed_id | nil} without pushing events
|
||||
defp batch_visibility_action(packet, lat, lon, callsign_key, socket) when not is_nil(lat) and not is_nil(lon) do
|
||||
in_bounds = within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds)
|
||||
has_marker = Map.has_key?(socket.assigns.visible_packets, callsign_key)
|
||||
|
|
@ -124,19 +126,21 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
|
|||
end
|
||||
|
||||
defp batch_visibility_action(_packet, _lat, _lon, _callsign_key, socket) do
|
||||
{socket, nil}
|
||||
{socket, nil, nil}
|
||||
end
|
||||
|
||||
# Out of bounds but has marker — remove it (still pushes remove_marker since
|
||||
# removals are rare and don't benefit from batching)
|
||||
# Out of bounds but has marker — remove state only, return the removed key
|
||||
# so the caller can batch all removals into one push_event
|
||||
defp batch_dispatch({false, true}, _packet, _lat, _lon, callsign_key, socket) do
|
||||
socket = remove_marker_from_map(callsign_key, socket)
|
||||
{socket, nil}
|
||||
new_visible_packets = Map.delete(socket.assigns.visible_packets, callsign_key)
|
||||
socket = assign(socket, :visible_packets, new_visible_packets)
|
||||
{socket, nil, callsign_key}
|
||||
end
|
||||
|
||||
# In bounds, no existing marker — add it
|
||||
defp batch_dispatch({true, false}, packet, _lat, _lon, _callsign_key, socket) do
|
||||
prepare_packet_state(packet, socket)
|
||||
{socket, marker_data} = prepare_packet_state(packet, socket)
|
||||
{socket, marker_data, nil}
|
||||
end
|
||||
|
||||
# In bounds, existing marker — update if significant movement
|
||||
|
|
@ -146,15 +150,16 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
|
|||
|
||||
if is_number(existing_lat) and is_number(existing_lon) and
|
||||
GeoUtils.significant_movement?(existing_lat, existing_lon, lat, lon, 50) do
|
||||
prepare_packet_state(packet, socket)
|
||||
{socket, marker_data} = prepare_packet_state(packet, socket)
|
||||
{socket, marker_data, nil}
|
||||
else
|
||||
new_visible_packets = Map.put(socket.assigns.visible_packets, callsign_key, packet)
|
||||
{assign(socket, :visible_packets, new_visible_packets), nil}
|
||||
{assign(socket, :visible_packets, new_visible_packets), nil, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defp batch_dispatch(_, _packet, _lat, _lon, _callsign_key, socket) do
|
||||
{socket, nil}
|
||||
{socket, nil, nil}
|
||||
end
|
||||
|
||||
# Shared: updates visible_packets state and builds marker data.
|
||||
|
|
|
|||
|
|
@ -24,6 +24,82 @@ defmodule Aprsme.Packets.PreparedQueriesTest do
|
|||
|> Repo.insert()
|
||||
end
|
||||
|
||||
describe "get_latest_packets_for_callsigns/1" do
|
||||
test "returns full packet structs for multiple callsigns" do
|
||||
{:ok, _} =
|
||||
create_positioned_packet(%{
|
||||
sender: "K5GVL-10",
|
||||
base_callsign: "K5GVL",
|
||||
ssid: "10",
|
||||
lat: Decimal.new("33.1000"),
|
||||
lon: Decimal.new("-96.6000"),
|
||||
comment: "Digi TX"
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
create_positioned_packet(%{
|
||||
sender: "N5TXZ-10",
|
||||
base_callsign: "N5TXZ",
|
||||
ssid: "10",
|
||||
lat: Decimal.new("33.2000"),
|
||||
lon: Decimal.new("-96.5000"),
|
||||
comment: "iGate"
|
||||
})
|
||||
|
||||
result = PreparedQueries.get_latest_packets_for_callsigns(["K5GVL-10", "N5TXZ-10"])
|
||||
|
||||
assert length(result) == 2
|
||||
assert Enum.all?(result, &is_struct(&1, Packet))
|
||||
|
||||
senders = Enum.map(result, & &1.sender)
|
||||
assert "K5GVL-10" in senders
|
||||
assert "N5TXZ-10" in senders
|
||||
|
||||
k5gvl = Enum.find(result, &(&1.sender == "K5GVL-10"))
|
||||
assert_in_delta k5gvl.lat, 33.1, 0.01
|
||||
assert_in_delta k5gvl.lon, -96.6, 0.01
|
||||
end
|
||||
|
||||
test "returns empty list for empty input" do
|
||||
assert PreparedQueries.get_latest_packets_for_callsigns([]) == []
|
||||
end
|
||||
|
||||
test "returns empty list for nonexistent callsigns" do
|
||||
result = PreparedQueries.get_latest_packets_for_callsigns(["NONEXIST-1", "FAKE-2"])
|
||||
assert result == []
|
||||
end
|
||||
|
||||
test "returns latest packet when multiple exist for a callsign" do
|
||||
{:ok, _} =
|
||||
create_positioned_packet(%{
|
||||
sender: "K5GVL-10",
|
||||
base_callsign: "K5GVL",
|
||||
ssid: "10",
|
||||
lat: Decimal.new("33.1000"),
|
||||
lon: Decimal.new("-96.6000"),
|
||||
received_at: DateTime.add(DateTime.utc_now(), -3600, :second)
|
||||
})
|
||||
|
||||
{:ok, _} =
|
||||
create_positioned_packet(%{
|
||||
sender: "K5GVL-10",
|
||||
base_callsign: "K5GVL",
|
||||
ssid: "10",
|
||||
lat: Decimal.new("34.0000"),
|
||||
lon: Decimal.new("-97.0000"),
|
||||
received_at: DateTime.truncate(DateTime.utc_now(), :second)
|
||||
})
|
||||
|
||||
result = PreparedQueries.get_latest_packets_for_callsigns(["K5GVL-10"])
|
||||
|
||||
assert length(result) == 1
|
||||
packet = hd(result)
|
||||
assert is_struct(packet, Packet)
|
||||
assert_in_delta packet.lat, 34.0, 0.01
|
||||
assert_in_delta packet.lon, -97.0, 0.01
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_latest_positions_for_callsigns/1" do
|
||||
test "returns positions for multiple callsigns" do
|
||||
{:ok, _} =
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
socket = build_socket()
|
||||
packet = build_packet()
|
||||
|
||||
{updated_socket, marker_data} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
{updated_socket, marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
assert marker_data
|
||||
assert is_map(marker_data)
|
||||
|
|
@ -57,7 +57,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
# Packet outside the bounds (north: 34, south: 32, east: -95, west: -97)
|
||||
packet = build_packet(%{lat: 40.0, lon: -80.0})
|
||||
|
||||
{updated_socket, marker_data} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
{updated_socket, marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
assert marker_data == nil
|
||||
assert map_size(updated_socket.assigns.visible_packets) == 0
|
||||
|
|
@ -67,7 +67,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
socket = build_socket()
|
||||
packet = build_packet(%{lat: nil, lon: nil})
|
||||
|
||||
{updated_socket, marker_data} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
{updated_socket, marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
assert marker_data == nil
|
||||
assert map_size(updated_socket.assigns.visible_packets) == 0
|
||||
|
|
@ -77,7 +77,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
socket = build_socket(%{map_zoom: 7})
|
||||
packet = build_packet()
|
||||
|
||||
{_updated_socket, marker_data} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
{_updated_socket, marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
assert marker_data == nil
|
||||
end
|
||||
|
|
@ -86,7 +86,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
socket = build_socket()
|
||||
packet = build_packet()
|
||||
|
||||
{updated_socket, _marker_data} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
{updated_socket, _marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
# Socket should not have any push_events queued
|
||||
# In LiveView, push_event adds to socket.private[:push_events]
|
||||
|
|
@ -104,7 +104,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
# Second packet for same callsign (also no :id so key = sender)
|
||||
second_packet = %{lat: 33.2, lon: -96.4} |> build_packet() |> Map.delete(:id)
|
||||
|
||||
{_updated_socket, marker_data} = PacketProcessor.process_packet_for_marker_data(second_packet, socket)
|
||||
{_updated_socket, marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(second_packet, socket)
|
||||
|
||||
assert marker_data
|
||||
assert marker_data["convert_from"] == "K5GVL-10"
|
||||
|
|
@ -114,10 +114,37 @@ defmodule AprsmeWeb.MapLive.PacketProcessorTest do
|
|||
socket = build_socket()
|
||||
packet = build_packet()
|
||||
|
||||
{_updated_socket, marker_data} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
{_updated_socket, marker_data, _removed} = PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
assert marker_data
|
||||
refute Map.has_key?(marker_data, "convert_from")
|
||||
end
|
||||
|
||||
test "returns removed callsign key when out-of-bounds packet had existing marker" do
|
||||
# Set up socket with an existing visible packet
|
||||
existing_packet = %{lat: 33.1, lon: -96.5} |> build_packet() |> Map.delete(:id)
|
||||
socket = build_socket(%{visible_packets: %{"K5GVL-10" => existing_packet}})
|
||||
|
||||
# New packet from same callsign is out of bounds
|
||||
out_of_bounds_packet = %{lat: 40.0, lon: -80.0} |> build_packet() |> Map.delete(:id)
|
||||
|
||||
{updated_socket, marker_data, removed_id} =
|
||||
PacketProcessor.process_packet_for_marker_data(out_of_bounds_packet, socket)
|
||||
|
||||
assert marker_data == nil
|
||||
assert removed_id == "K5GVL-10"
|
||||
# The marker should be removed from visible_packets
|
||||
refute Map.has_key?(updated_socket.assigns.visible_packets, "K5GVL-10")
|
||||
end
|
||||
|
||||
test "returns nil removed_id for normal in-bounds packets" do
|
||||
socket = build_socket()
|
||||
packet = build_packet()
|
||||
|
||||
{_updated_socket, _marker_data, removed_id} =
|
||||
PacketProcessor.process_packet_for_marker_data(packet, socket)
|
||||
|
||||
assert removed_id == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue