defmodule AprsmeWeb.MapLive.RfPath do @moduledoc """ Handles RF path parsing and visualization for APRS packets. """ alias Aprsme.Packets alias AprsmeWeb.MapLive.Utils @doc """ Parse RF path string to extract digipeater/igate stations. Excludes APRS-IS (Internet) paths that contain TCPIP. APRS-IS path indicators: - TCPIP: Packet came via Internet connection - NOGATE: Should not be gated to RF - qA*: Various APRS-IS q-constructs (qAC, qAS, qAR, etc.) These are not actual RF digipeaters, so we don't show path lines for them. """ @spec parse_rf_path(binary()) :: list(binary()) def parse_rf_path(path) when is_binary(path) do # Skip APRS-IS paths - these are Internet-only, not RF if String.contains?(path, "TCPIP") or String.contains?(path, "NOGATE") or String.contains?(path, "qA") do [] else path |> String.split(",") |> Enum.map(&String.trim/1) |> Enum.map(&extract_callsign_from_path_element/1) |> Enum.filter(&(&1 != "")) |> Enum.uniq() end end def parse_rf_path(_), do: [] @doc """ Get positions for path stations from the database. """ @spec get_path_station_positions(list(binary()), Phoenix.LiveView.Socket.t()) :: list(map()) def get_path_station_positions(path_stations, _socket) do # Limit to prevent excessive database queries limited_stations = Enum.take(path_stations, 10) limited_stations |> Enum.map(&get_station_position/1) |> Enum.filter(& &1) end defp extract_callsign_from_path_element(element) do # Remove asterisk (used flag) and extract callsign element |> String.replace("*", "") |> String.trim() |> String.upcase() |> validate_path_callsign() end defp validate_path_callsign(callsign) do if Utils.valid_callsign?(callsign) do callsign else "" end end defp get_station_position(callsign) do case Packets.get_latest_packet_for_callsign(callsign) do %{lat: lat, lon: lon} when is_number(lat) and is_number(lon) -> %{ callsign: callsign, lat: lat, lng: lon } _ -> nil end end end