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