diff --git a/lib/aprsme_web/aprs_symbol.ex b/lib/aprsme_web/aprs_symbol.ex
index 840f728..5e78be7 100644
--- a/lib/aprsme_web/aprs_symbol.ex
+++ b/lib/aprsme_web/aprs_symbol.ex
@@ -1,13 +1,13 @@
defmodule AprsmeWeb.AprsSymbol do
@moduledoc """
Shared library for APRS symbol handling and rendering.
-
+
This module provides centralized functions for:
- Symbol table and code normalization
- Sprite file mapping
- Symbol positioning calculations
- HTML generation for symbols
-
+
All APRS symbol logic should use this module to ensure consistency
across the application.
"""
@@ -15,9 +15,9 @@ defmodule AprsmeWeb.AprsSymbol do
@doc """
Gets sprite information for a given symbol table and code.
Returns a map with sprite_file, background_position, and background_size.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.get_sprite_info("/", "_")
%{
sprite_file: "/aprs-symbols/aprs-symbols-128-0@2x.png",
@@ -34,21 +34,22 @@ defmodule AprsmeWeb.AprsSymbol do
# Normal symbol table processing
symbol_table = normalize_symbol_table(symbol_table)
symbol_code = normalize_symbol_code(symbol_code)
-
+
# Map symbol table to sprite file ID
table_id = get_table_id(symbol_table)
-
+
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
# Get symbol position using ASCII-based calculation
- symbol_code_ord = symbol_code
- |> String.to_charlist()
- |> List.first()
- |> (fn c -> if is_integer(c), do: c, else: 63 end).()
-
+ symbol_code_ord =
+ symbol_code
+ |> String.to_charlist()
+ |> List.first()
+ |> then(fn c -> if is_integer(c), do: c, else: 63 end)
+
index = symbol_code_ord - 33
safe_index = max(0, min(index, 93))
-
+
# Calculate positioning for 16-column grid
column = rem(safe_index, 16)
row = div(safe_index, 16)
@@ -72,16 +73,17 @@ defmodule AprsmeWeb.AprsSymbol do
# Check which table to use based on the symbol code
table_id = get_overlay_base_table_id(base_symbol_code)
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
-
+
# Get position of the base symbol in the appropriate table
- base_symbol_ord = base_symbol_code
- |> String.to_charlist()
- |> List.first()
- |> (fn c -> if is_integer(c), do: c, else: 63 end).()
-
+ base_symbol_ord =
+ base_symbol_code
+ |> String.to_charlist()
+ |> List.first()
+ |> then(fn c -> if is_integer(c), do: c, else: 63 end)
+
index = base_symbol_ord - 33
safe_index = max(0, min(index, 93))
-
+
# Calculate positioning for 16-column grid
column = rem(safe_index, 16)
row = div(safe_index, 16)
@@ -104,15 +106,21 @@ defmodule AprsmeWeb.AprsSymbol do
# Most overlay symbols are in the alternate table (1)
case base_symbol_code do
# Digipeater symbols are often in the alternate table (1) and have colored backgrounds
- "#" -> "1" # Digipeater - green star background
- "a" -> "1" # Diamond shape - APRS overlay symbol (alternate table)
- "A" -> "1" # Square shape - APRS overlay symbol (alternate table)
- "&" -> "1" # Diamond shape - alternate table
- ">" -> "1" # Arrow symbols
+ # Digipeater - green star background
+ "#" -> "1"
+ # Diamond shape - APRS overlay symbol (alternate table)
+ "a" -> "1"
+ # Square shape - APRS overlay symbol (alternate table)
+ "A" -> "1"
+ # Diamond shape - alternate table
+ "&" -> "1"
+ # Arrow symbols
+ ">" -> "1"
"<" -> "1"
"^" -> "1"
"v" -> "1"
- "i" -> "1" # Black square background - alternate table
+ # Black square background - alternate table
+ "i" -> "1"
# Most other symbols that can be overlaid are in the alternate table
_ -> "1"
end
@@ -125,16 +133,17 @@ defmodule AprsmeWeb.AprsSymbol do
def get_overlay_character_sprite_info(overlay_char) do
# Use overlay table (table 2) for the overlay character
sprite_file = "/aprs-symbols/aprs-symbols-128-2@2x.png"
-
+
# Get position of the overlay character in the overlay table
- overlay_char_ord = overlay_char
- |> String.to_charlist()
- |> List.first()
- |> (fn c -> if is_integer(c), do: c, else: 63 end).()
-
+ overlay_char_ord =
+ overlay_char
+ |> String.to_charlist()
+ |> List.first()
+ |> then(fn c -> if is_integer(c), do: c, else: 63 end)
+
index = overlay_char_ord - 33
safe_index = max(0, min(index, 93))
-
+
# Calculate positioning for 16-column grid
column = rem(safe_index, 16)
row = div(safe_index, 16)
@@ -150,9 +159,9 @@ defmodule AprsmeWeb.AprsSymbol do
@doc """
Normalizes a symbol table identifier.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.normalize_symbol_table("/")
"/"
@@ -172,9 +181,9 @@ defmodule AprsmeWeb.AprsSymbol do
@doc """
Normalizes a symbol code.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.normalize_symbol_code("_")
"_"
@@ -190,9 +199,9 @@ defmodule AprsmeWeb.AprsSymbol do
@doc """
Maps a symbol table to its sprite file ID.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.get_table_id("/")
"0"
@@ -204,59 +213,64 @@ defmodule AprsmeWeb.AprsSymbol do
"""
def get_table_id(symbol_table) do
case symbol_table do
- "/" -> "0" # Primary table
- "\\" -> "1" # Alternate table
- "]" -> "2" # Overlay table (A-Z, 0-9)
- _ -> "0" # Default to primary table
+ # Primary table
+ "/" -> "0"
+ # Alternate table
+ "\\" -> "1"
+ # Overlay table (A-Z, 0-9)
+ "]" -> "2"
+ # Default to primary table
+ _ -> "0"
end
end
@doc """
Renders an APRS symbol as HTML for use in Leaflet markers.
Returns HTML string that can be used as marker content.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.render_marker_html("/", "_", "W1AW")
"
..."
"""
def render_marker_html(symbol_table, symbol_code, callsign \\ nil, size \\ 32) do
sprite_info = get_sprite_info(symbol_table, symbol_code)
-
+
# Check if this is an overlay symbol
is_overlay = symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/)
-
- symbol_html = if is_overlay do
- # For overlay symbols, we need both the base symbol background and the overlay character
- overlay_sprite_info = get_overlay_character_sprite_info(symbol_table)
-
- """
-
-
- """
- else
- """
-
- """
- end
-
+
+ symbol_html =
+ if is_overlay do
+ # For overlay symbols, we need both the base symbol background and the overlay character
+ overlay_sprite_info = get_overlay_character_sprite_info(symbol_table)
+
+ """
+
+
+ """
+ else
+ """
+
+ """
+ end
+
if callsign do
"""
@@ -289,23 +303,23 @@ defmodule AprsmeWeb.AprsSymbol do
@doc """
Renders an APRS symbol as a style string for use in templates.
Returns a CSS style string that can be used directly in HTML.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.render_style("/", "_", 32)
"width: 32px; height: 32px; background-image: url(/aprs-symbols/aprs-symbols-128-0@2x.png); ..."
"""
def render_style(symbol_table, symbol_code, size \\ 32) do
sprite_info = get_sprite_info(symbol_table, symbol_code)
-
+
"width: #{size}px; height: #{size}px; background-image: url(#{sprite_info.sprite_file}); background-position: #{sprite_info.background_position}; background-size: #{sprite_info.background_size}; background-repeat: no-repeat; image-rendering: pixelated; opacity: 1.0; display: inline-block; vertical-align: middle; margin-bottom: -6px;"
end
@doc """
Extracts symbol information from a packet with fallbacks.
-
+
## Examples
-
+
iex> AprsmeWeb.AprsSymbol.extract_from_packet(%{symbol_table_id: "/", symbol_code: "_"})
{"/", "_"}
@@ -315,7 +329,7 @@ defmodule AprsmeWeb.AprsSymbol do
def extract_from_packet(packet) do
symbol_table_id = get_packet_field(packet, :symbol_table_id, "/")
symbol_code = get_packet_field(packet, :symbol_code, ">")
-
+
{symbol_table_id, symbol_code}
end
@@ -329,4 +343,4 @@ defmodule AprsmeWeb.AprsSymbol do
Map.get(data_extended, to_string(field)) ||
default
end
-end
\ No newline at end of file
+end
diff --git a/lib/aprsme_web/live/components/symbol_renderer.ex b/lib/aprsme_web/live/components/symbol_renderer.ex
index f5c21a4..0132d6c 100644
--- a/lib/aprsme_web/live/components/symbol_renderer.ex
+++ b/lib/aprsme_web/live/components/symbol_renderer.ex
@@ -91,7 +91,6 @@ defmodule AprsmeWeb.SymbolRenderer do
AprsmeWeb.AprsSymbol.get_sprite_info(symbol_table, symbol_code)
end
-
@doc """
Renders an APRS symbol for use in Leaflet markers.
Returns HTML string that can be used as marker content.
diff --git a/lib/aprsme_web/live/info_live/show.ex b/lib/aprsme_web/live/info_live/show.ex
index 8aadc48..40b8dbf 100644
--- a/lib/aprsme_web/live/info_live/show.ex
+++ b/lib/aprsme_web/live/info_live/show.ex
@@ -4,8 +4,8 @@ defmodule AprsmeWeb.InfoLive.Show do
use Gettext, backend: AprsmeWeb.Gettext
alias Aprsme.Packets
- alias AprsmeWeb.MapLive.PacketUtils
alias AprsmeWeb.AprsSymbol
+ alias AprsmeWeb.MapLive.PacketUtils
@neighbor_radius_km 10
@neighbor_limit 10
@@ -326,14 +326,14 @@ defmodule AprsmeWeb.InfoLive.Show do
def render_symbol_html(packet, size \\ 32) do
if packet do
{symbol_table_id, symbol_code} = AprsSymbol.extract_from_packet(packet)
-
+
# Check if this is an overlay symbol
if symbol_table_id && String.match?(symbol_table_id, ~r/^[A-Z0-9]$/) do
# Use layered sprite backgrounds for overlay symbols
sprite_info = AprsSymbol.get_sprite_info(symbol_table_id, symbol_code)
overlay_sprite_info = AprsSymbol.get_overlay_character_sprite_info(symbol_table_id)
-
- raw """
+
+ raw("""
- """
+ """)
else
# Use style rendering for non-overlay symbols
- raw """
+ raw("""
- """
+ """)
end
else
# Return empty if no packet
- raw ""
+ raw("")
end
end
diff --git a/lib/aprsme_web/live/info_live/show.html.heex b/lib/aprsme_web/live/info_live/show.html.heex
index ce3bf9e..f021f63 100644
--- a/lib/aprsme_web/live/info_live/show.html.heex
+++ b/lib/aprsme_web/live/info_live/show.html.heex
@@ -1,4 +1,3 @@
-
@@ -15,9 +14,13 @@
<%= if @packet do %>
<% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(@packet) %>
- <% display_symbol = if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/), do: symbol_table, else: "#{AprsmeWeb.AprsSymbol.normalize_symbol_table(symbol_table)}#{AprsmeWeb.AprsSymbol.normalize_symbol_code(symbol_code)}" %>
+ <% display_symbol =
+ if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/),
+ do: symbol_table,
+ else:
+ "#{AprsmeWeb.AprsSymbol.normalize_symbol_table(symbol_table)}#{AprsmeWeb.AprsSymbol.normalize_symbol_code(symbol_code)}" %>
- <%= render_symbol_html(@packet) %>
+ {render_symbol_html(@packet)}
<% end %>
@@ -251,10 +254,15 @@
{ssid_info.callsign}
<%= if ssid_info.packet do %>
- <% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(ssid_info.packet) %>
- <% display_symbol = if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/), do: symbol_table, else: "#{AprsmeWeb.AprsSymbol.normalize_symbol_table(symbol_table)}#{AprsmeWeb.AprsSymbol.normalize_symbol_code(symbol_code)}" %>
+ <% {symbol_table, symbol_code} =
+ AprsmeWeb.AprsSymbol.extract_from_packet(ssid_info.packet) %>
+ <% display_symbol =
+ if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/),
+ do: symbol_table,
+ else:
+ "#{AprsmeWeb.AprsSymbol.normalize_symbol_table(symbol_table)}#{AprsmeWeb.AprsSymbol.normalize_symbol_code(symbol_code)}" %>
- <%= render_symbol_html(ssid_info.packet) %>
+ {render_symbol_html(ssid_info.packet)}
<% end %>
@@ -353,10 +361,15 @@
{neighbor.callsign}
<%= if neighbor.packet do %>
- <% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(neighbor.packet) %>
- <% display_symbol = if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/), do: symbol_table, else: "#{AprsmeWeb.AprsSymbol.normalize_symbol_table(symbol_table)}#{AprsmeWeb.AprsSymbol.normalize_symbol_code(symbol_code)}" %>
+ <% {symbol_table, symbol_code} =
+ AprsmeWeb.AprsSymbol.extract_from_packet(neighbor.packet) %>
+ <% display_symbol =
+ if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/),
+ do: symbol_table,
+ else:
+ "#{AprsmeWeb.AprsSymbol.normalize_symbol_table(symbol_table)}#{AprsmeWeb.AprsSymbol.normalize_symbol_code(symbol_code)}" %>
- <%= render_symbol_html(neighbor.packet) %>
+ {render_symbol_html(neighbor.packet)}
<% end %>
diff --git a/test/aprsme_web/aprs_symbol_test.exs b/test/aprsme_web/aprs_symbol_test.exs
index 733ce62..cc6d813 100644
--- a/test/aprsme_web/aprs_symbol_test.exs
+++ b/test/aprsme_web/aprs_symbol_test.exs
@@ -1,15 +1,16 @@
defmodule AprsmeWeb.AprsSymbolTest do
use ExUnit.Case, async: true
+
alias AprsmeWeb.AprsSymbol
describe "overlay symbol rendering" do
test "D& should show black diamond background from table 1" do
# Test the get_sprite_info for overlay symbol D&
sprite_info = AprsSymbol.get_sprite_info("D", "&")
-
+
# Should use table 1 for the diamond background (alternate table)
assert sprite_info.sprite_file == "/aprs-symbols/aprs-symbols-128-1@2x.png"
-
+
# Calculate expected position for & (ASCII 38)
# index = 38 - 33 = 5
# column = 5 % 16 = 5
@@ -23,10 +24,10 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "N# should show green star background from table 1" do
# Test the get_sprite_info for overlay symbol N#
sprite_info = AprsSymbol.get_sprite_info("N", "#")
-
+
# Should use table 1 for the green star background
assert sprite_info.sprite_file == "/aprs-symbols/aprs-symbols-128-1@2x.png"
-
+
# Calculate expected position for # (ASCII 35)
# index = 35 - 33 = 2
# column = 2 % 16 = 2
@@ -40,10 +41,10 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "overlay character sprite info for D" do
# Test getting the overlay character sprite for letter D
overlay_info = AprsSymbol.get_overlay_character_sprite_info("D")
-
+
# Should use table 2 for overlay characters
assert overlay_info.sprite_file == "/aprs-symbols/aprs-symbols-128-2@2x.png"
-
+
# Calculate expected position for D (ASCII 68)
# index = 68 - 33 = 35
# column = 35 % 16 = 3
@@ -57,10 +58,10 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "overlay character sprite info for N" do
# Test getting the overlay character sprite for letter N
overlay_info = AprsSymbol.get_overlay_character_sprite_info("N")
-
+
# Should use table 2 for overlay characters
assert overlay_info.sprite_file == "/aprs-symbols/aprs-symbols-128-2@2x.png"
-
+
# Calculate expected position for N (ASCII 78)
# index = 78 - 33 = 45
# column = 45 % 16 = 13
@@ -73,26 +74,28 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "render_marker_html for overlay symbol D&" do
html = AprsSymbol.render_marker_html("D", "&", "W5MRC-15", 32)
-
+
# Should contain both background images (overlay first from table 2, base from table 1)
- assert html =~ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-1@2x.png)"
-
+ assert html =~
+ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-1@2x.png)"
+
# Should have both background positions (overlay first, then base)
assert html =~ "background-position: -96.0px -64.0px, -160.0px 0.0px"
-
+
# Should include the callsign
assert html =~ "W5MRC-15"
end
test "render_marker_html for overlay symbol N#" do
html = AprsSymbol.render_marker_html("N", "#", "TEST-1", 32)
-
+
# Should contain both background images (overlay first from table 2, base from table 1)
- assert html =~ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-1@2x.png)"
-
+ assert html =~
+ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-1@2x.png)"
+
# Should have both background positions (overlay first, then base)
assert html =~ "background-position: -416.0px -64.0px, -64.0px 0.0px"
-
+
# Should include the callsign
assert html =~ "TEST-1"
end
@@ -100,9 +103,9 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "normal symbol /_" do
# Test a normal symbol from primary table
sprite_info = AprsSymbol.get_sprite_info("/", "_")
-
+
assert sprite_info.sprite_file == "/aprs-symbols/aprs-symbols-128-0@2x.png"
-
+
# Calculate expected position for _ (ASCII 95)
# index = 95 - 33 = 62
# column = 62 % 16 = 14
@@ -116,9 +119,9 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "alternate table symbol \\#" do
# Test a symbol from alternate table
sprite_info = AprsSymbol.get_sprite_info("\\", "#")
-
+
assert sprite_info.sprite_file == "/aprs-symbols/aprs-symbols-128-1@2x.png"
-
+
# Calculate expected position for # (ASCII 35)
# index = 35 - 33 = 2
# column = 2 % 16 = 2
@@ -131,12 +134,18 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "get_overlay_base_table_id mapping" do
# Test the table mapping for overlay base symbols
- assert AprsSymbol.get_overlay_base_table_id("#") == "1" # Green star in alternate table
- assert AprsSymbol.get_overlay_base_table_id("&") == "1" # Diamond in alternate table
- assert AprsSymbol.get_overlay_base_table_id("i") == "1" # Black square in alternate table
- assert AprsSymbol.get_overlay_base_table_id(">") == "1" # Arrow in alternate table
- assert AprsSymbol.get_overlay_base_table_id("^") == "1" # Arrow in alternate table
- assert AprsSymbol.get_overlay_base_table_id("?") == "1" # Default to alternate table
+ # Green star in alternate table
+ assert AprsSymbol.get_overlay_base_table_id("#") == "1"
+ # Diamond in alternate table
+ assert AprsSymbol.get_overlay_base_table_id("&") == "1"
+ # Black square in alternate table
+ assert AprsSymbol.get_overlay_base_table_id("i") == "1"
+ # Arrow in alternate table
+ assert AprsSymbol.get_overlay_base_table_id(">") == "1"
+ # Arrow in alternate table
+ assert AprsSymbol.get_overlay_base_table_id("^") == "1"
+ # Default to alternate table
+ assert AprsSymbol.get_overlay_base_table_id("?") == "1"
end
end
-end
\ No newline at end of file
+end
diff --git a/test/aprsme_web/live/map_live/overlay_rendering_test.exs b/test/aprsme_web/live/map_live/overlay_rendering_test.exs
index 157d203..83be065 100644
--- a/test/aprsme_web/live/map_live/overlay_rendering_test.exs
+++ b/test/aprsme_web/live/map_live/overlay_rendering_test.exs
@@ -1,5 +1,6 @@
defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
use ExUnit.Case, async: true
+
alias AprsmeWeb.MapLive.PacketUtils
describe "overlay symbol rendering in map" do
@@ -21,18 +22,19 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
# Process the packet through PacketUtils
result = PacketUtils.build_packet_data(packet, true, "en-US")
-
+
# Check that symbol_html was generated
assert Map.has_key?(result, "symbol_html")
symbol_html = result["symbol_html"]
-
+
# Verify the overlay symbol is rendered correctly with overlay character on top
# The overlay character (D) should be first in the background-image list
- assert symbol_html =~ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-2@2x.png)"
-
+ assert symbol_html =~
+ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-2@2x.png)"
+
# Verify the positions (overlay character D first at -96.0px -64.0px, then base & at -160.0px 0.0px)
assert symbol_html =~ "background-position: -96.0px -64.0px, -160.0px 0.0px"
-
+
# Verify callsign is included
assert symbol_html =~ "W5MRC-15"
end
@@ -56,14 +58,15 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
# Process the packet
result = PacketUtils.build_packet_data(packet, true, "en-US")
symbol_html = result["symbol_html"]
-
+
# Verify the overlay uses different sprite tables
# Overlay character N from table 2, base # from table 1
- assert symbol_html =~ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-1@2x.png)"
-
+ assert symbol_html =~
+ "background-image: url(/aprs-symbols/aprs-symbols-128-2@2x.png), url(/aprs-symbols/aprs-symbols-128-1@2x.png)"
+
# Verify the positions (overlay N first, then base #)
assert symbol_html =~ "background-position: -416.0px -64.0px, -64.0px 0.0px"
-
+
# Verify callsign
assert symbol_html =~ "TEST-1"
end
@@ -87,16 +90,16 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
# Process the packet
result = PacketUtils.build_packet_data(packet, true, "en-US")
symbol_html = result["symbol_html"]
-
+
# Verify it's a single background image (no overlay)
assert symbol_html =~ "background-image: url(/aprs-symbols/aprs-symbols-128-0@2x.png)"
assert not (symbol_html =~ "background-image: url(/aprs-symbols/aprs-symbols-128-0@2x.png), url")
-
+
# Verify the position for _ symbol
assert symbol_html =~ "background-position: -448.0px -96.0px"
-
+
# Verify callsign
assert symbol_html =~ "WEATHER-1"
end
end
-end
\ No newline at end of file
+end