tests pass
This commit is contained in:
parent
e4d72ec6cc
commit
bd3ec7ce19
3 changed files with 189 additions and 91 deletions
|
|
@ -194,7 +194,27 @@ defmodule AprsWeb.MapLive.CallsignView do
|
|||
west: to_float(bounds["west"])
|
||||
}
|
||||
|
||||
socket = assign(socket, map_bounds: normalized_bounds)
|
||||
# Remove out-of-bounds visible packets
|
||||
new_visible_packets =
|
||||
socket.assigns.visible_packets
|
||||
|> Enum.filter(fn {_k, packet} -> within_bounds?(packet, normalized_bounds) end)
|
||||
|> Map.new()
|
||||
|
||||
markers_to_remove =
|
||||
socket.assigns.visible_packets
|
||||
|> Enum.reject(fn {_k, packet} -> within_bounds?(packet, normalized_bounds) end)
|
||||
|> Enum.map(fn {k, _} -> k end)
|
||||
|
||||
socket =
|
||||
if markers_to_remove == [] do
|
||||
socket
|
||||
else
|
||||
Enum.reduce(markers_to_remove, socket, fn k, acc ->
|
||||
push_event(acc, "remove_marker", %{id: k})
|
||||
end)
|
||||
end
|
||||
|
||||
socket = assign(socket, map_bounds: normalized_bounds, visible_packets: new_visible_packets)
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
|
|
@ -241,77 +261,58 @@ defmodule AprsWeb.MapLive.CallsignView do
|
|||
# Clean up packets older than 1 hour from the map display
|
||||
handle_cleanup_old_packets(socket)
|
||||
|
||||
%{event: "packet", payload: payload} ->
|
||||
# Sanitize the packet to prevent encoding errors
|
||||
# Stream packets from PubSub only if within bounds
|
||||
%Phoenix.Socket.Broadcast{topic: "aprs_messages", event: "packet", payload: payload} ->
|
||||
sanitized_packet = EncodingUtils.sanitize_packet(payload)
|
||||
|
||||
# Add received timestamp if not present
|
||||
sanitized_packet = Map.put_new(sanitized_packet, :received_at, DateTime.utc_now())
|
||||
{lat, lng} = get_coordinates(sanitized_packet)
|
||||
|
||||
# Check if this packet is from our target callsign
|
||||
if packet_matches_callsign?(sanitized_packet, socket.assigns.callsign) and
|
||||
has_position_data?(sanitized_packet) and
|
||||
packet_within_time_threshold?(sanitized_packet, socket.assigns.packet_age_threshold) do
|
||||
# Convert to a simple map structure for JSON encoding
|
||||
packet_data = build_packet_data(sanitized_packet)
|
||||
callsign_key =
|
||||
"#{sanitized_packet.base_callsign}#{if sanitized_packet.ssid, do: "-#{sanitized_packet.ssid}", else: ""}"
|
||||
|
||||
# Only push if we have valid packet data
|
||||
if packet_data do
|
||||
# Generate a unique key for this packet
|
||||
callsign_key =
|
||||
"#{sanitized_packet.base_callsign}#{if sanitized_packet.ssid, do: "-#{sanitized_packet.ssid}", else: ""}"
|
||||
if has_position_data?(sanitized_packet) and
|
||||
Map.has_key?(socket.assigns.visible_packets, callsign_key) and
|
||||
not within_bounds?(%{lat: lat, lon: lng}, socket.assigns.map_bounds) do
|
||||
socket = push_event(socket, "remove_marker", %{id: callsign_key})
|
||||
new_visible_packets = Map.delete(socket.assigns.visible_packets, callsign_key)
|
||||
{:noreply, assign(socket, visible_packets: new_visible_packets)}
|
||||
else
|
||||
if has_position_data?(sanitized_packet) and
|
||||
within_bounds?(%{lat: lat, lon: lng}, socket.assigns.map_bounds) and
|
||||
packet_matches_callsign?(sanitized_packet, socket.assigns.callsign) and
|
||||
packet_within_time_threshold?(
|
||||
sanitized_packet,
|
||||
socket.assigns.packet_age_threshold
|
||||
) do
|
||||
packet_data = build_packet_data(sanitized_packet)
|
||||
|
||||
# Update visible packets tracking
|
||||
visible_packets =
|
||||
Map.put(socket.assigns.visible_packets, callsign_key, sanitized_packet)
|
||||
if packet_data do
|
||||
visible_packets =
|
||||
Map.put(socket.assigns.visible_packets, callsign_key, sanitized_packet)
|
||||
|
||||
# Update last known position
|
||||
{lat, lng} = get_coordinates(sanitized_packet)
|
||||
last_known_position =
|
||||
if lat && lng, do: %{lat: lat, lng: lng}, else: socket.assigns.last_known_position
|
||||
|
||||
last_known_position =
|
||||
if lat && lng, do: %{lat: lat, lng: lng}, else: socket.assigns.last_known_position
|
||||
socket =
|
||||
socket
|
||||
|> push_event("new_packet", packet_data)
|
||||
|> assign(
|
||||
visible_packets: visible_packets,
|
||||
last_known_position: last_known_position
|
||||
)
|
||||
|
||||
# Push the packet to the client-side JavaScript
|
||||
socket =
|
||||
socket
|
||||
|> push_event("new_packet", packet_data)
|
||||
|> assign(
|
||||
visible_packets: visible_packets,
|
||||
last_known_position: last_known_position
|
||||
)
|
||||
|
||||
# If this is the first packet and map is ready, zoom to it
|
||||
# Or if this is a new position that's significantly different, update zoom
|
||||
socket =
|
||||
cond do
|
||||
map_empty?(socket) and socket.assigns.map_ready and last_known_position ->
|
||||
push_event(socket, "zoom_to_location", %{
|
||||
lat: last_known_position.lat,
|
||||
lng: last_known_position.lng,
|
||||
zoom: 12
|
||||
})
|
||||
|
||||
should_update_zoom?(socket.assigns.last_known_position, last_known_position) and
|
||||
socket.assigns.map_ready ->
|
||||
push_event(socket, "zoom_to_location", %{
|
||||
lat: last_known_position.lat,
|
||||
lng: last_known_position.lng,
|
||||
zoom: 12
|
||||
})
|
||||
|
||||
true ->
|
||||
socket
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
{:noreply, socket}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
else
|
||||
# Invalid packet data, skip it
|
||||
{:noreply, socket}
|
||||
end
|
||||
else
|
||||
# Ignore packets that don't match our callsign or don't have position data
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
_ ->
|
||||
# Ignore packets that don't match our callsign or don't have position data
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -687,16 +688,26 @@ defmodule AprsWeb.MapLive.CallsignView do
|
|||
socket = assign(socket, packet_age_threshold: one_hour_ago)
|
||||
|
||||
# Remove expired packets from visible_packets
|
||||
updated_visible_packets =
|
||||
expired_keys =
|
||||
socket.assigns.visible_packets
|
||||
|> Enum.filter(fn {_key, packet} ->
|
||||
packet_within_time_threshold?(packet, one_hour_ago)
|
||||
not packet_within_time_threshold?(packet, one_hour_ago)
|
||||
end)
|
||||
|> Map.new()
|
||||
|> Enum.map(fn {key, _} -> key end)
|
||||
|
||||
socket = assign(socket, visible_packets: updated_visible_packets)
|
||||
# Only update the client if there are expired markers
|
||||
socket =
|
||||
if expired_keys == [] do
|
||||
socket
|
||||
else
|
||||
Enum.reduce(expired_keys, socket, fn key, acc ->
|
||||
push_event(acc, "remove_marker", %{id: key})
|
||||
end)
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
# Use Map.drop/2 for better performance
|
||||
updated_visible_packets = Map.drop(socket.assigns.visible_packets, expired_keys)
|
||||
assign(socket, visible_packets: updated_visible_packets)
|
||||
end
|
||||
|
||||
defp packet_within_time_threshold?(packet, threshold) do
|
||||
|
|
@ -987,4 +998,43 @@ defmodule AprsWeb.MapLive.CallsignView do
|
|||
# Update zoom if position changed by more than ~5km (approximately 0.05 degrees)
|
||||
lat_diff > 0.05 or lng_diff > 0.05
|
||||
end
|
||||
|
||||
# Helper to check if a packet or lat/lon is within bounds
|
||||
defp within_bounds?(packet_or_coords, bounds) do
|
||||
{lat, lon} =
|
||||
cond do
|
||||
is_map(packet_or_coords) and Map.has_key?(packet_or_coords, :lat) and
|
||||
Map.has_key?(packet_or_coords, :lon) ->
|
||||
{packet_or_coords.lat, packet_or_coords.lon}
|
||||
|
||||
is_map(packet_or_coords) and Map.has_key?(packet_or_coords, "lat") and
|
||||
Map.has_key?(packet_or_coords, "lon") ->
|
||||
{packet_or_coords["lat"], packet_or_coords["lon"]}
|
||||
|
||||
is_map(packet_or_coords) and Map.has_key?(packet_or_coords, :latitude) and
|
||||
Map.has_key?(packet_or_coords, :longitude) ->
|
||||
{packet_or_coords.latitude, packet_or_coords.longitude}
|
||||
|
||||
is_tuple(packet_or_coords) and tuple_size(packet_or_coords) == 2 ->
|
||||
packet_or_coords
|
||||
|
||||
true ->
|
||||
{nil, nil}
|
||||
end
|
||||
|
||||
if is_nil(lat) or is_nil(lon) do
|
||||
false
|
||||
else
|
||||
lat_in_bounds = lat >= bounds.south && lat <= bounds.north
|
||||
|
||||
lng_in_bounds =
|
||||
if bounds.west <= bounds.east do
|
||||
lon >= bounds.west && lon <= bounds.east
|
||||
else
|
||||
lon >= bounds.west || lon <= bounds.east
|
||||
end
|
||||
|
||||
lat_in_bounds && lng_in_bounds
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -72,11 +72,32 @@ defmodule AprsWeb.MapLive.Enhanced do
|
|||
lng: center["lng"]
|
||||
}
|
||||
|
||||
# Remove out-of-bounds active markers
|
||||
new_active_markers =
|
||||
socket.assigns.active_markers
|
||||
|> Enum.filter(fn {_k, %{packet: packet}} -> within_bounds?(packet, bounds) end)
|
||||
|> Map.new()
|
||||
|
||||
markers_to_remove =
|
||||
socket.assigns.active_markers
|
||||
|> Enum.reject(fn {_k, %{packet: packet}} -> within_bounds?(packet, bounds) end)
|
||||
|> Enum.map(fn {k, _} -> k end)
|
||||
|
||||
socket =
|
||||
if markers_to_remove == [] do
|
||||
socket
|
||||
else
|
||||
Enum.reduce(markers_to_remove, socket, fn k, acc ->
|
||||
push_event(acc, "remove_marker", %{id: k})
|
||||
end)
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:map_bounds, bounds)
|
||||
|> assign(:map_center, normalized_center)
|
||||
|> assign(:map_zoom, zoom)
|
||||
|> assign(:active_markers, new_active_markers)
|
||||
|> load_markers_in_bounds()
|
||||
|
||||
{:noreply, socket}
|
||||
|
|
@ -166,12 +187,21 @@ defmodule AprsWeb.MapLive.Enhanced do
|
|||
end
|
||||
|
||||
def handle_info({:new_packet, packet}, socket) do
|
||||
# Handle real-time packet updates
|
||||
if has_position_data?(packet) and within_bounds?(packet, socket.assigns.map_bounds) do
|
||||
socket = add_packet_marker(socket, packet, false)
|
||||
{:noreply, socket}
|
||||
id = packet_to_marker_data(packet).id
|
||||
|
||||
if has_position_data?(packet) and Map.has_key?(socket.assigns.active_markers, id) and
|
||||
not within_bounds?(packet, socket.assigns.map_bounds) do
|
||||
socket = push_event(socket, "remove_marker", %{id: id})
|
||||
new_active_markers = Map.delete(socket.assigns.active_markers, id)
|
||||
{:noreply, assign(socket, :active_markers, new_active_markers)}
|
||||
else
|
||||
{:noreply, socket}
|
||||
# Only add marker if it is within bounds
|
||||
if has_position_data?(packet) and within_bounds?(packet, socket.assigns.map_bounds) do
|
||||
socket = add_packet_marker(socket, packet, false)
|
||||
{:noreply, socket}
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -305,10 +305,15 @@ defmodule AprsWeb.MapLive.Index do
|
|||
|> Enum.reject(fn {_k, packet} -> within_bounds?(packet, map_bounds) end)
|
||||
|> Enum.map(fn {k, _} -> k end)
|
||||
|
||||
# Remove markers for out-of-bounds packets
|
||||
socket =
|
||||
Enum.reduce(packets_to_remove, socket, fn k, acc ->
|
||||
push_event(acc, "remove_marker", %{id: k})
|
||||
end)
|
||||
if packets_to_remove == [] do
|
||||
socket
|
||||
else
|
||||
Enum.reduce(packets_to_remove, socket, fn k, acc ->
|
||||
push_event(acc, "remove_marker", %{id: k})
|
||||
end)
|
||||
end
|
||||
|
||||
# Fetch the latest packets within the new bounds and push to client (historical only)
|
||||
bounds_list = [map_bounds.west, map_bounds.south, map_bounds.east, map_bounds.north]
|
||||
|
|
@ -406,7 +411,6 @@ defmodule AprsWeb.MapLive.Index do
|
|||
defp handle_info_postgres_packet(packet, socket) do
|
||||
{lat, lon, _data_extended} = get_coordinates(packet)
|
||||
|
||||
# Always store the packet in all_packets
|
||||
callsign_key =
|
||||
if Map.has_key?(packet, "id"),
|
||||
do: to_string(packet["id"]),
|
||||
|
|
@ -415,10 +419,25 @@ defmodule AprsWeb.MapLive.Index do
|
|||
all_packets = Map.put(socket.assigns.all_packets, callsign_key, packet)
|
||||
socket = assign(socket, all_packets: all_packets)
|
||||
|
||||
# Only add marker if it is within bounds and not already present
|
||||
if is_nil(lat) or is_nil(lon) or Map.has_key?(socket.assigns.visible_packets, callsign_key),
|
||||
do: {:noreply, socket},
|
||||
else: handle_valid_postgres_packet(packet, lat, lon, socket)
|
||||
# Remove marker if packet is out of bounds but present
|
||||
if !is_nil(lat) and !is_nil(lon) and
|
||||
Map.has_key?(socket.assigns.visible_packets, callsign_key) and
|
||||
not within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds) do
|
||||
socket = push_event(socket, "remove_marker", %{id: callsign_key})
|
||||
new_visible_packets = Map.delete(socket.assigns.visible_packets, callsign_key)
|
||||
{:noreply, assign(socket, visible_packets: new_visible_packets)}
|
||||
# Only add marker if it is within bounds and not already present
|
||||
else
|
||||
if is_nil(lat) or is_nil(lon) or Map.has_key?(socket.assigns.visible_packets, callsign_key) do
|
||||
{:noreply, socket}
|
||||
else
|
||||
if within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds) do
|
||||
handle_valid_postgres_packet(packet, lat, lon, socket)
|
||||
else
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_valid_postgres_packet(packet, lat, lon, socket) do
|
||||
|
|
@ -698,26 +717,25 @@ defmodule AprsWeb.MapLive.Index do
|
|||
|
||||
one_hour_ago = DateTime.add(DateTime.utc_now(), -3600, :second)
|
||||
# Remove expired packets from visible_packets
|
||||
updated_visible_packets =
|
||||
socket.assigns.visible_packets
|
||||
|> Enum.filter(fn {_key, packet} ->
|
||||
packet_within_time_threshold?(packet, one_hour_ago)
|
||||
end)
|
||||
|> Map.new()
|
||||
|
||||
# Remove expired markers from the client
|
||||
expired_keys =
|
||||
socket.assigns.visible_packets
|
||||
|> Enum.reject(fn {_key, packet} ->
|
||||
packet_within_time_threshold?(packet, one_hour_ago)
|
||||
|> Enum.filter(fn {_key, packet} ->
|
||||
not packet_within_time_threshold?(packet, one_hour_ago)
|
||||
end)
|
||||
|> Enum.map(fn {key, _} -> key end)
|
||||
|
||||
# Only update the client if there are expired markers
|
||||
socket =
|
||||
Enum.reduce(expired_keys, socket, fn key, acc ->
|
||||
push_event(acc, "remove_marker", %{id: key})
|
||||
end)
|
||||
if expired_keys == [] do
|
||||
socket
|
||||
else
|
||||
Enum.reduce(expired_keys, socket, fn key, acc ->
|
||||
push_event(acc, "remove_marker", %{id: key})
|
||||
end)
|
||||
end
|
||||
|
||||
# Use Map.drop/2 for better performance
|
||||
updated_visible_packets = Map.drop(socket.assigns.visible_packets, expired_keys)
|
||||
assign(socket, visible_packets: updated_visible_packets)
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue