This commit is contained in:
Graham McIntire 2025-07-09 16:22:58 -05:00
parent 514d1b66d5
commit ef95f597b0
No known key found for this signature in database
6 changed files with 180 additions and 142 deletions

View file

@ -41,10 +41,11 @@ defmodule AprsmeWeb.AprsSymbol do
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
# Get symbol position using ASCII-based calculation # Get symbol position using ASCII-based calculation
symbol_code_ord = symbol_code symbol_code_ord =
|> String.to_charlist() symbol_code
|> List.first() |> String.to_charlist()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() |> List.first()
|> then(fn c -> if is_integer(c), do: c, else: 63 end)
index = symbol_code_ord - 33 index = symbol_code_ord - 33
safe_index = max(0, min(index, 93)) safe_index = max(0, min(index, 93))
@ -74,10 +75,11 @@ defmodule AprsmeWeb.AprsSymbol do
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png" sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
# Get position of the base symbol in the appropriate table # Get position of the base symbol in the appropriate table
base_symbol_ord = base_symbol_code base_symbol_ord =
|> String.to_charlist() base_symbol_code
|> List.first() |> String.to_charlist()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() |> List.first()
|> then(fn c -> if is_integer(c), do: c, else: 63 end)
index = base_symbol_ord - 33 index = base_symbol_ord - 33
safe_index = max(0, min(index, 93)) safe_index = max(0, min(index, 93))
@ -104,15 +106,21 @@ defmodule AprsmeWeb.AprsSymbol do
# Most overlay symbols are in the alternate table (1) # Most overlay symbols are in the alternate table (1)
case base_symbol_code do case base_symbol_code do
# Digipeater symbols are often in the alternate table (1) and have colored backgrounds # Digipeater symbols are often in the alternate table (1) and have colored backgrounds
"#" -> "1" # Digipeater - green star background # Digipeater - green star background
"a" -> "1" # Diamond shape - APRS overlay symbol (alternate table) "#" -> "1"
"A" -> "1" # Square shape - APRS overlay symbol (alternate table) # Diamond shape - APRS overlay symbol (alternate table)
"&" -> "1" # Diamond shape - alternate table "a" -> "1"
">" -> "1" # Arrow symbols # Square shape - APRS overlay symbol (alternate table)
"A" -> "1"
# Diamond shape - alternate table
"&" -> "1"
# Arrow symbols
">" -> "1"
"<" -> "1" "<" -> "1"
"^" -> "1" "^" -> "1"
"v" -> "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 # Most other symbols that can be overlaid are in the alternate table
_ -> "1" _ -> "1"
end end
@ -127,10 +135,11 @@ defmodule AprsmeWeb.AprsSymbol do
sprite_file = "/aprs-symbols/aprs-symbols-128-2@2x.png" sprite_file = "/aprs-symbols/aprs-symbols-128-2@2x.png"
# Get position of the overlay character in the overlay table # Get position of the overlay character in the overlay table
overlay_char_ord = overlay_char overlay_char_ord =
|> String.to_charlist() overlay_char
|> List.first() |> String.to_charlist()
|> (fn c -> if is_integer(c), do: c, else: 63 end).() |> List.first()
|> then(fn c -> if is_integer(c), do: c, else: 63 end)
index = overlay_char_ord - 33 index = overlay_char_ord - 33
safe_index = max(0, min(index, 93)) safe_index = max(0, min(index, 93))
@ -204,10 +213,14 @@ defmodule AprsmeWeb.AprsSymbol do
""" """
def get_table_id(symbol_table) do def get_table_id(symbol_table) do
case symbol_table do case symbol_table do
"/" -> "0" # Primary table # Primary table
"\\" -> "1" # Alternate table "/" -> "0"
"]" -> "2" # Overlay table (A-Z, 0-9) # Alternate table
_ -> "0" # Default to primary table "\\" -> "1"
# Overlay table (A-Z, 0-9)
"]" -> "2"
# Default to primary table
_ -> "0"
end end
end end
@ -226,36 +239,37 @@ defmodule AprsmeWeb.AprsSymbol do
# Check if this is an overlay symbol # Check if this is an overlay symbol
is_overlay = symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/) is_overlay = symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/)
symbol_html = if is_overlay do symbol_html =
# For overlay symbols, we need both the base symbol background and the overlay character if is_overlay do
overlay_sprite_info = get_overlay_character_sprite_info(symbol_table) # For overlay symbols, we need both the base symbol background and the overlay character
overlay_sprite_info = get_overlay_character_sprite_info(symbol_table)
""" """
<div style=" <div style="
position: relative; position: relative;
width: #{size}px; width: #{size}px;
height: #{size}px; height: #{size}px;
background-image: url(#{overlay_sprite_info.sprite_file}), url(#{sprite_info.sprite_file}); background-image: url(#{overlay_sprite_info.sprite_file}), url(#{sprite_info.sprite_file});
background-position: #{overlay_sprite_info.background_position}, #{sprite_info.background_position}; background-position: #{overlay_sprite_info.background_position}, #{sprite_info.background_position};
background-size: #{overlay_sprite_info.background_size}, #{sprite_info.background_size}; background-size: #{overlay_sprite_info.background_size}, #{sprite_info.background_size};
background-repeat: no-repeat, no-repeat; background-repeat: no-repeat, no-repeat;
image-rendering: pixelated; image-rendering: pixelated;
" title="#{symbol_table}#{symbol_code}"> " title="#{symbol_table}#{symbol_code}">
</div> </div>
""" """
else else
""" """
<div style=" <div style="
width: #{size}px; width: #{size}px;
height: #{size}px; height: #{size}px;
background-image: url(#{sprite_info.sprite_file}); background-image: url(#{sprite_info.sprite_file});
background-position: #{sprite_info.background_position}; background-position: #{sprite_info.background_position};
background-size: #{sprite_info.background_size}; background-size: #{sprite_info.background_size};
background-repeat: no-repeat; background-repeat: no-repeat;
image-rendering: pixelated; image-rendering: pixelated;
" title="#{symbol_table}#{symbol_code}"></div> " title="#{symbol_table}#{symbol_code}"></div>
""" """
end end
if callsign do if callsign do
""" """

View file

@ -91,7 +91,6 @@ defmodule AprsmeWeb.SymbolRenderer do
AprsmeWeb.AprsSymbol.get_sprite_info(symbol_table, symbol_code) AprsmeWeb.AprsSymbol.get_sprite_info(symbol_table, symbol_code)
end end
@doc """ @doc """
Renders an APRS symbol for use in Leaflet markers. Renders an APRS symbol for use in Leaflet markers.
Returns HTML string that can be used as marker content. Returns HTML string that can be used as marker content.

View file

@ -4,8 +4,8 @@ defmodule AprsmeWeb.InfoLive.Show do
use Gettext, backend: AprsmeWeb.Gettext use Gettext, backend: AprsmeWeb.Gettext
alias Aprsme.Packets alias Aprsme.Packets
alias AprsmeWeb.MapLive.PacketUtils
alias AprsmeWeb.AprsSymbol alias AprsmeWeb.AprsSymbol
alias AprsmeWeb.MapLive.PacketUtils
@neighbor_radius_km 10 @neighbor_radius_km 10
@neighbor_limit 10 @neighbor_limit 10
@ -333,7 +333,7 @@ defmodule AprsmeWeb.InfoLive.Show do
sprite_info = AprsSymbol.get_sprite_info(symbol_table_id, symbol_code) sprite_info = AprsSymbol.get_sprite_info(symbol_table_id, symbol_code)
overlay_sprite_info = AprsSymbol.get_overlay_character_sprite_info(symbol_table_id) overlay_sprite_info = AprsSymbol.get_overlay_character_sprite_info(symbol_table_id)
raw """ raw("""
<div style=" <div style="
position: relative; position: relative;
width: #{size}px; width: #{size}px;
@ -348,16 +348,16 @@ defmodule AprsmeWeb.InfoLive.Show do
margin-bottom: -6px; margin-bottom: -6px;
"> ">
</div> </div>
""" """)
else else
# Use style rendering for non-overlay symbols # Use style rendering for non-overlay symbols
raw """ raw("""
<div style="#{AprsSymbol.render_style(symbol_table_id, symbol_code, size)}"></div> <div style="#{AprsSymbol.render_style(symbol_table_id, symbol_code, size)}"></div>
""" """)
end end
else else
# Return empty if no packet # Return empty if no packet
raw "" raw("")
end end
end end

View file

@ -1,4 +1,3 @@
<div class="min-h-screen bg-base-200"> <div class="min-h-screen bg-base-200">
<!-- Page header --> <!-- Page header -->
<div class="bg-base-100 shadow-sm"> <div class="bg-base-100 shadow-sm">
@ -15,9 +14,13 @@
</span> </span>
<%= if @packet do %> <%= if @packet do %>
<% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(@packet) %> <% {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)}" %>
<div title={display_symbol}> <div title={display_symbol}>
<%= render_symbol_html(@packet) %> {render_symbol_html(@packet)}
</div> </div>
<% end %> <% end %>
</div> </div>
@ -251,10 +254,15 @@
{ssid_info.callsign} {ssid_info.callsign}
</.link> </.link>
<%= if ssid_info.packet do %> <%= if ssid_info.packet do %>
<% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(ssid_info.packet) %> <% {symbol_table, 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)}" %> 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)}" %>
<div title={display_symbol}> <div title={display_symbol}>
<%= render_symbol_html(ssid_info.packet) %> {render_symbol_html(ssid_info.packet)}
</div> </div>
<% end %> <% end %>
</div> </div>
@ -353,10 +361,15 @@
{neighbor.callsign} {neighbor.callsign}
</.link> </.link>
<%= if neighbor.packet do %> <%= if neighbor.packet do %>
<% {symbol_table, symbol_code} = AprsmeWeb.AprsSymbol.extract_from_packet(neighbor.packet) %> <% {symbol_table, 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)}" %> 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)}" %>
<div title={display_symbol}> <div title={display_symbol}>
<%= render_symbol_html(neighbor.packet) %> {render_symbol_html(neighbor.packet)}
</div> </div>
<% end %> <% end %>
</div> </div>

View file

@ -1,5 +1,6 @@
defmodule AprsmeWeb.AprsSymbolTest do defmodule AprsmeWeb.AprsSymbolTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
alias AprsmeWeb.AprsSymbol alias AprsmeWeb.AprsSymbol
describe "overlay symbol rendering" do describe "overlay symbol rendering" do
@ -75,7 +76,8 @@ defmodule AprsmeWeb.AprsSymbolTest do
html = AprsSymbol.render_marker_html("D", "&", "W5MRC-15", 32) html = AprsSymbol.render_marker_html("D", "&", "W5MRC-15", 32)
# Should contain both background images (overlay first from table 2, base from table 1) # 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) # Should have both background positions (overlay first, then base)
assert html =~ "background-position: -96.0px -64.0px, -160.0px 0.0px" assert html =~ "background-position: -96.0px -64.0px, -160.0px 0.0px"
@ -88,7 +90,8 @@ defmodule AprsmeWeb.AprsSymbolTest do
html = AprsSymbol.render_marker_html("N", "#", "TEST-1", 32) html = AprsSymbol.render_marker_html("N", "#", "TEST-1", 32)
# Should contain both background images (overlay first from table 2, base from table 1) # 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) # Should have both background positions (overlay first, then base)
assert html =~ "background-position: -416.0px -64.0px, -64.0px 0.0px" assert html =~ "background-position: -416.0px -64.0px, -64.0px 0.0px"
@ -131,12 +134,18 @@ defmodule AprsmeWeb.AprsSymbolTest do
test "get_overlay_base_table_id mapping" do test "get_overlay_base_table_id mapping" do
# Test the table mapping for overlay base symbols # Test the table mapping for overlay base symbols
assert AprsSymbol.get_overlay_base_table_id("#") == "1" # Green star in 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"
assert AprsSymbol.get_overlay_base_table_id("i") == "1" # Black square in alternate table # Diamond in alternate table
assert AprsSymbol.get_overlay_base_table_id(">") == "1" # Arrow in alternate table assert AprsSymbol.get_overlay_base_table_id("&") == "1"
assert AprsSymbol.get_overlay_base_table_id("^") == "1" # Arrow in alternate table # Black square in alternate table
assert AprsSymbol.get_overlay_base_table_id("?") == "1" # Default to 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 end
end end

View file

@ -1,5 +1,6 @@
defmodule AprsmeWeb.MapLive.OverlayRenderingTest do defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
use ExUnit.Case, async: true use ExUnit.Case, async: true
alias AprsmeWeb.MapLive.PacketUtils alias AprsmeWeb.MapLive.PacketUtils
describe "overlay symbol rendering in map" do describe "overlay symbol rendering in map" do
@ -28,7 +29,8 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
# Verify the overlay symbol is rendered correctly with overlay character on top # Verify the overlay symbol is rendered correctly with overlay character on top
# The overlay character (D) should be first in the background-image list # 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) # 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" assert symbol_html =~ "background-position: -96.0px -64.0px, -160.0px 0.0px"
@ -59,7 +61,8 @@ defmodule AprsmeWeb.MapLive.OverlayRenderingTest do
# Verify the overlay uses different sprite tables # Verify the overlay uses different sprite tables
# Overlay character N from table 2, base # from table 1 # 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 #) # Verify the positions (overlay N first, then base #)
assert symbol_html =~ "background-position: -416.0px -64.0px, -64.0px 0.0px" assert symbol_html =~ "background-position: -416.0px -64.0px, -64.0px 0.0px"