diff --git a/lib/aprsme/cached_queries.ex b/lib/aprsme/cached_queries.ex index 2af3d4a..8075539 100644 --- a/lib/aprsme/cached_queries.ex +++ b/lib/aprsme/cached_queries.ex @@ -6,6 +6,7 @@ defmodule Aprsme.CachedQueries do alias Aprsme.Packet alias Aprsme.Packets alias Aprsme.Repo + alias Ecto.Adapters.SQL # 1 minute for frequently changing data @cache_ttl_short to_timeout(minute: 1) @@ -195,7 +196,7 @@ defmodule Aprsme.CachedQueries do """ result = - case Ecto.Adapters.SQL.query(Repo, query, [callsigns]) do + case SQL.query(Repo, query, [callsigns]) do {:ok, %{rows: rows}} -> Enum.map(rows, fn [callsign, lat, lon] -> %{ diff --git a/lib/aprsme_web/live/map_live/index.ex b/lib/aprsme_web/live/map_live/index.ex index c2dfcc6..0b4e911 100644 --- a/lib/aprsme_web/live/map_live/index.ex +++ b/lib/aprsme_web/live/map_live/index.ex @@ -546,7 +546,7 @@ defmodule AprsmeWeb.MapLive.Index do defp update_map_state(socket, map_center, zoom) do old_zoom = socket.assigns.map_zoom - crossing_threshold = is_crossing_zoom_threshold?(old_zoom, zoom) + crossing_threshold = crossing_zoom_threshold?(old_zoom, zoom) socket = assign(socket, map_center: map_center, map_zoom: zoom) @@ -557,7 +557,7 @@ defmodule AprsmeWeb.MapLive.Index do end end - defp is_crossing_zoom_threshold?(old_zoom, new_zoom) do + defp crossing_zoom_threshold?(old_zoom, new_zoom) do (old_zoom <= 8 and new_zoom > 8) or (old_zoom > 8 and new_zoom <= 8) end @@ -2050,17 +2050,6 @@ defmodule AprsmeWeb.MapLive.Index do }) end - # Helper function to parse coordinates from various formats - @spec parse_coordinate(any()) :: float() - defp parse_coordinate(coord) do - cond do - is_binary(coord) -> String.to_float(coord) - is_integer(coord) -> coord / 1.0 - is_float(coord) -> coord - true -> 0.0 - end - end - defp trigger_marker_display(socket) do # Clear heat map and show markers socket = push_event(socket, "show_markers", %{}) @@ -2100,7 +2089,7 @@ defmodule AprsmeWeb.MapLive.Index do # Only clear historical packets if: # 1. Bounds actually changed AND - # 2. This is not the initial load AND + # 2. This is not the initial load AND # 3. We've already completed the initial historical load socket = if bounds_changed and not is_initial_load and initial_historical_completed do @@ -2138,21 +2127,20 @@ defmodule AprsmeWeb.MapLive.Index do path |> String.split(",") |> Enum.map(&String.trim/1) - |> Enum.reject(&(&1 == "")) - # Skip TCPIP entries and qA* entries (but preserve the actual digipeater/igate callsigns) - |> Enum.reject(&String.contains?(&1, "TCPIP")) - |> Enum.reject(&String.starts_with?(&1, "qA")) + # Skip empty strings, TCPIP entries and qA* entries + |> Enum.reject(fn callsign -> + callsign == "" || String.contains?(callsign, "TCPIP") || String.starts_with?(callsign, "qA") + end) |> Enum.map(fn callsign -> # Remove any asterisk (used to mark heard stations) and WIDE patterns callsign |> String.replace("*", "") |> String.trim() end) - # Filter out WIDE patterns (both WIDE1-1 and WIDE1 forms) + # Filter out APRS built-in beacon patterns |> Enum.reject(fn callsign -> - String.starts_with?(callsign, "WIDE") + callsign == "" || aprs_beacon?(callsign) end) - |> Enum.reject(&(&1 == "")) |> Enum.uniq() end @@ -2165,4 +2153,59 @@ defmodule AprsmeWeb.MapLive.Index do end defp get_path_station_positions(_, _), do: [] + + # Check if a callsign is an APRS built-in beacon pattern + @spec aprs_beacon?(String.t()) :: boolean() + defp aprs_beacon?(callsign) do + # Common APRS beacon patterns to exclude + patterns = [ + # WIDE1, WIDE2, WIDE1-1, etc. + ~r/^WIDE\d(-\d)?$/i, + # TRACE patterns + ~r/^TRACE\d(-\d)?$/i, + # RELAY + ~r/^RELAY$/i, + # ECHO + ~r/^ECHO$/i, + # GATE + ~r/^GATE$/i, + # UNPROTO + ~r/^UNPROTO$/i, + # BEACON + ~r/^BEACON$/i, + # CQ + ~r/^CQ$/i, + # QTH + ~r/^QTH$/i, + # MAIL + ~r/^MAIL$/i, + # TEMP1, TEMP2, etc. + ~r/^TEMP\d+$/i, + # ID + ~r/^ID$/i, + # GPS + ~r/^GPS$/i, + # DF + ~r/^DF$/i, + # DGPS + ~r/^DGPS$/i, + # PHG patterns + ~r/^PHG\d+$/i, + # RNG patterns + ~r/^RNG\d+$/i, + # DFS patterns + ~r/^DFS\d+$/i, + # HOP patterns + ~r/^HOP\d+(-\d)?$/i, + # REGION patterns + ~r/^REGION\d+(-\d)?$/i, + # STATE patterns + ~r/^STATE\d+(-\d)?$/i + ] + + # Check if the callsign matches any beacon pattern + Enum.any?(patterns, fn pattern -> + Regex.match?(pattern, callsign) + end) + end end diff --git a/test/aprsme_web/live/map_live/rf_path_test.exs b/test/aprsme_web/live/map_live/rf_path_test.exs index d0173ff..ecfbb2b 100644 --- a/test/aprsme_web/live/map_live/rf_path_test.exs +++ b/test/aprsme_web/live/map_live/rf_path_test.exs @@ -73,15 +73,12 @@ defmodule AprsmeWeb.MapLive.RfPathTest do test "handles paths with asterisks correctly", %{conn: conn} do {:ok, _station} = - Repo.insert(%Packet{ + create_test_packet(%{ sender: "WA5VHU-8", base_callsign: "WA5VHU", ssid: "8", lat: Decimal.new("33.1500"), - lon: Decimal.new("-96.5500"), - has_position: true, - received_at: DateTime.truncate(DateTime.utc_now(), :second), - data_type: "position" + lon: Decimal.new("-96.5500") }) {:ok, view, _html} = live(conn, "/") @@ -113,6 +110,31 @@ defmodule AprsmeWeb.MapLive.RfPathTest do assert view.module == Index end + test "filters out APRS beacon patterns from paths", %{conn: conn} do + # Create a real station that should be included + {:ok, _station} = + create_test_packet(%{ + sender: "KC5ABC-9", + base_callsign: "KC5ABC", + ssid: "9", + lat: Decimal.new("33.2500"), + lon: Decimal.new("-96.6500") + }) + + {:ok, view, _html} = live(conn, "/") + + # Send a hover event with various beacon patterns that should be filtered + render_hook(view, "marker_hover_start", %{ + "id" => "test-beacons", + "path" => "KC5ABC-9,WIDE1-1,WIDE2-1,TRACE3-3,RELAY,ECHO,HOP7-7", + "lat" => 33.2837, + "lng" => -96.5728 + }) + + # Should filter out all beacon patterns but keep KC5ABC-9 + assert view.module == Index + end + test "handles empty paths gracefully", %{conn: conn} do {:ok, view, _html} = live(conn, "/") @@ -138,27 +160,21 @@ defmodule AprsmeWeb.MapLive.RfPathTest do setup do # Create test packets with positions {:ok, _digi1} = - Repo.insert(%Packet{ + create_test_packet(%{ sender: "K5GVL-10", base_callsign: "K5GVL", ssid: "10", lat: Decimal.new("33.1000"), - lon: Decimal.new("-96.6000"), - has_position: true, - received_at: DateTime.truncate(DateTime.utc_now(), :second), - data_type: "position" + lon: Decimal.new("-96.6000") }) {:ok, _digi2} = - Repo.insert(%Packet{ + create_test_packet(%{ sender: "N5TXZ-10", base_callsign: "N5TXZ", ssid: "10", lat: Decimal.new("33.2000"), - lon: Decimal.new("-96.5000"), - has_position: true, - received_at: DateTime.truncate(DateTime.utc_now(), :second), - data_type: "position" + lon: Decimal.new("-96.5000") }) :ok @@ -185,32 +201,26 @@ defmodule AprsmeWeb.MapLive.RfPathTest do # Create test packets - one inside a small bounds, one outside # Station inside typical Texas bounds {:ok, _inside_station} = - Repo.insert(%Packet{ + create_test_packet(%{ sender: "K5INSIDE-10", base_callsign: "K5INSIDE", ssid: "10", # Inside Texas area lat: Decimal.new("32.5000"), # Inside Texas area - lon: Decimal.new("-96.5000"), - has_position: true, - received_at: DateTime.truncate(DateTime.utc_now(), :second), - data_type: "position" + lon: Decimal.new("-96.5000") }) # Station outside bounds (way outside in Boston area) {:ok, _outside_station} = - Repo.insert(%Packet{ + create_test_packet(%{ sender: "W1OUTSIDE-10", base_callsign: "W1OUTSIDE", ssid: "10", # Boston area - far from Texas lat: Decimal.new("42.0000"), # Boston area - far from Texas - lon: Decimal.new("-71.0000"), - has_position: true, - received_at: DateTime.truncate(DateTime.utc_now(), :second), - data_type: "position" + lon: Decimal.new("-71.0000") }) :ok