Security: - Add SQL identifier validation to DbOptimizer.analyze_table/vacuum_table - Move health/readiness routes to non-rate-limited pipeline Correctness: - Fix monitor reference leak in StreamingPacketsPubSub (track refs, demonitor on unsubscribe) - Persist circuit breaker half-open state transitions - Simplify ShutdownHandler.terminate to avoid pointless Process.send_after Performance: - Batch marker removal into single WebSocket event (remove_markers_batch) - Use Task.Supervisor.start_child for fire-and-forget broadcasts Dead code removal: - Remove copy_insert/regular_batch_insert/rows_to_csv from DbOptimizer - Remove dead packet_pipeline_integration tests - Remove unused telemetry event detach Other: - Fix Callsign.valid? docstring to match permissive behavior - Fix DatabaseMetrics to delegate instead of hardcoding pool stats - Add circuit breaker and encoding test coverage
129 lines
4 KiB
Elixir
129 lines
4 KiB
Elixir
defmodule AprsmeWeb.MapLive.DisplayManager do
|
|
@moduledoc """
|
|
Handles heat maps, markers, and zoom threshold display logic.
|
|
"""
|
|
|
|
alias Aprsme.Packets.Clustering
|
|
alias AprsmeWeb.MapLive.BoundsManager
|
|
alias AprsmeWeb.MapLive.DataBuilder
|
|
alias Phoenix.LiveView
|
|
alias Phoenix.LiveView.Socket
|
|
|
|
# Zoom level at or below which the map switches to heat map mode
|
|
@heat_map_max_zoom 8
|
|
|
|
@doc """
|
|
Handle zoom threshold crossing between heat map and marker modes.
|
|
"""
|
|
@spec handle_zoom_threshold_crossing(Socket.t(), integer()) :: Socket.t()
|
|
def handle_zoom_threshold_crossing(socket, zoom) do
|
|
if zoom <= @heat_map_max_zoom do
|
|
# Switching to heat map
|
|
socket
|
|
|> LiveView.push_event("clear_all_markers", %{})
|
|
|> send_heat_map_for_current_bounds()
|
|
else
|
|
# Switching to markers
|
|
trigger_marker_display(socket)
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Check if zoom level crosses the threshold between heat map and marker modes.
|
|
"""
|
|
@spec crossing_zoom_threshold?(integer(), integer()) :: boolean()
|
|
def crossing_zoom_threshold?(old_zoom, new_zoom) do
|
|
(old_zoom <= @heat_map_max_zoom and new_zoom > @heat_map_max_zoom) or
|
|
(old_zoom > @heat_map_max_zoom and new_zoom <= @heat_map_max_zoom)
|
|
end
|
|
|
|
@doc """
|
|
Send heat map data for current bounds.
|
|
"""
|
|
@spec send_heat_map_for_current_bounds(Socket.t()) :: Socket.t()
|
|
def send_heat_map_for_current_bounds(socket) do
|
|
# Get all packets within current bounds
|
|
all_packets =
|
|
Map.values(socket.assigns.visible_packets) ++
|
|
Map.values(socket.assigns.historical_packets)
|
|
|
|
# Filter by bounds
|
|
filtered_packets =
|
|
all_packets
|
|
|> BoundsManager.filter_packets_by_bounds(socket.assigns.map_bounds)
|
|
|> Enum.uniq_by(fn packet ->
|
|
Map.get(packet, :id) || Map.get(packet, "id")
|
|
end)
|
|
|
|
send_heat_map_for_packets(socket, filtered_packets)
|
|
end
|
|
|
|
@doc """
|
|
Send heat map data for specific packets.
|
|
"""
|
|
@spec send_heat_map_for_packets(Socket.t(), list()) :: Socket.t()
|
|
def send_heat_map_for_packets(socket, packets) do
|
|
# Get clustering data
|
|
case Clustering.cluster_packets(packets, socket.assigns.map_zoom) do
|
|
{:heat_map, heat_points} ->
|
|
LiveView.push_event(socket, "show_heat_map", %{heat_points: heat_points})
|
|
|
|
{:raw_packets, _packets} ->
|
|
# Shouldn't happen at zoom <= 8, but handle it anyway
|
|
socket
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Send heat map data for filtered packets.
|
|
"""
|
|
@spec send_heat_map_data(Socket.t(), map()) :: Socket.t()
|
|
def send_heat_map_data(socket, filtered_packets) do
|
|
# Convert map of packets to list
|
|
packet_list = Map.values(filtered_packets)
|
|
send_heat_map_for_packets(socket, packet_list)
|
|
end
|
|
|
|
@doc """
|
|
Trigger marker display mode.
|
|
"""
|
|
@spec trigger_marker_display(Socket.t()) :: Socket.t()
|
|
def trigger_marker_display(socket) do
|
|
# Clear heat map and show markers
|
|
socket = LiveView.push_event(socket, "show_markers", %{})
|
|
|
|
# Re-send all visible packets as markers
|
|
visible_packets_list = DataBuilder.build_packet_data_list_from_map(socket.assigns.visible_packets, true, socket)
|
|
|
|
socket = add_markers_if_any(socket, visible_packets_list)
|
|
|
|
# Trigger historical packet reload for markers
|
|
start_progressive_historical_loading(socket)
|
|
end
|
|
|
|
@doc """
|
|
Add markers to map if any exist.
|
|
"""
|
|
@spec add_markers_if_any(Socket.t(), list()) :: Socket.t()
|
|
def add_markers_if_any(socket, []), do: socket
|
|
|
|
def add_markers_if_any(socket, markers) do
|
|
LiveView.push_event(socket, "add_markers", %{markers: markers})
|
|
end
|
|
|
|
@doc """
|
|
Remove multiple markers from map.
|
|
"""
|
|
@spec remove_markers_batch(Socket.t(), list()) :: Socket.t()
|
|
def remove_markers_batch(socket, []), do: socket
|
|
|
|
def remove_markers_batch(socket, marker_ids) do
|
|
LiveView.push_event(socket, "remove_markers_batch", %{ids: marker_ids})
|
|
end
|
|
|
|
# Placeholder for historical loading function - this should be moved to HistoricalLoader
|
|
defp start_progressive_historical_loading(socket) do
|
|
# This function should be moved to HistoricalLoader module
|
|
socket
|
|
end
|
|
end
|