184 lines
5.6 KiB
Elixir
184 lines
5.6 KiB
Elixir
defmodule AprsWeb.Helpers.AprsSymbols do
|
|
@moduledoc """
|
|
Helper functions for working with APRS symbols.
|
|
|
|
APRS symbols are organized in sprite sheets:
|
|
- Primary table (symbol_table_id = "/"): aprs-symbols-24-0.png
|
|
- Secondary table (symbol_table_id = "\\"): aprs-symbols-24-1.png
|
|
- Overlay characters: aprs-symbols-24-2.png
|
|
|
|
Each sprite sheet is a 16x6 grid (96 symbols total).
|
|
Symbol positioning is based on ASCII code of the symbol_code.
|
|
"""
|
|
|
|
@doc """
|
|
Get the sprite sheet filename for a given symbol table ID.
|
|
|
|
## Examples
|
|
|
|
iex> AprsWeb.Helpers.AprsSymbols.get_sprite_filename("/")
|
|
"aprs-symbols-24-0.png"
|
|
|
|
iex> AprsWeb.Helpers.AprsSymbols.get_sprite_filename("\\")
|
|
"aprs-symbols-24-1.png"
|
|
"""
|
|
def get_sprite_filename(symbol_table_id) do
|
|
case symbol_table_id do
|
|
"/" -> "aprs-symbols-24-0.png"
|
|
"\\" -> "aprs-symbols-24-1.png"
|
|
# Default to primary table
|
|
_ -> "aprs-symbols-24-0.png"
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Get the high-resolution sprite sheet filename for retina displays.
|
|
"""
|
|
def get_sprite_filename_2x(symbol_table_id) do
|
|
case symbol_table_id do
|
|
"/" -> "aprs-symbols-24-0@2x.png"
|
|
"\\" -> "aprs-symbols-24-1@2x.png"
|
|
_ -> "aprs-symbols-24-0@2x.png"
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Calculate the CSS background position for a symbol in the sprite sheet.
|
|
|
|
The sprite sheet is organized as a 16x6 grid.
|
|
ASCII codes 32-127 map to positions 0-95.
|
|
|
|
## Examples
|
|
|
|
iex> AprsWeb.Helpers.AprsSymbols.get_symbol_position(">")
|
|
{-1440, 0} # ASCII 62, position 30 -> column 14, row 1
|
|
|
|
iex> AprsWeb.Helpers.AprsSymbols.get_symbol_position("!")
|
|
{-216, 0} # ASCII 33, position 1 -> column 1, row 0
|
|
"""
|
|
def get_symbol_position(symbol_code) when is_binary(symbol_code) do
|
|
# Get first character if string
|
|
char_code = symbol_code |> String.first() |> String.to_charlist() |> List.first()
|
|
get_symbol_position_by_ascii(char_code)
|
|
end
|
|
|
|
def get_symbol_position(symbol_code) when is_integer(symbol_code) do
|
|
get_symbol_position_by_ascii(symbol_code)
|
|
end
|
|
|
|
defp get_symbol_position_by_ascii(ascii_code) do
|
|
# ASCII 32 (space) is position 0, ASCII 127 (DEL) is position 95
|
|
position = ascii_code - 32
|
|
|
|
# Clamp position to valid range 0-95
|
|
position = max(0, min(95, position))
|
|
|
|
# Calculate row and column (16 symbols per row)
|
|
col = rem(position, 16)
|
|
row = div(position, 16)
|
|
|
|
# Each symbol is 24x24 pixels
|
|
x = -col * 24
|
|
y = -row * 24
|
|
|
|
{x, y}
|
|
end
|
|
|
|
@doc """
|
|
Generate CSS style for displaying an APRS symbol using sprite positioning.
|
|
|
|
## Examples
|
|
|
|
iex> AprsWeb.Helpers.AprsSymbols.symbol_css_style("/", ">")
|
|
"background-image: url('/aprs-symbols/aprs-symbols-24-0.png'); background-position: -1440px 0px; width: 24px; height: 24px;"
|
|
"""
|
|
def symbol_css_style(symbol_table_id, symbol_code) do
|
|
sprite_file = get_sprite_filename(symbol_table_id)
|
|
{x, y} = get_symbol_position(symbol_code)
|
|
|
|
"background-image: url('/aprs-symbols/#{sprite_file}'); " <>
|
|
"background-position: #{x}px #{y}px; " <>
|
|
"width: 24px; height: 24px; " <>
|
|
"background-repeat: no-repeat;"
|
|
end
|
|
|
|
@doc """
|
|
Generate HTML for an APRS symbol with proper sprite positioning.
|
|
"""
|
|
def symbol_html(symbol_table_id, symbol_code, opts \\ []) do
|
|
css_class = Keyword.get(opts, :class, "aprs-symbol")
|
|
extra_style = Keyword.get(opts, :style, "")
|
|
|
|
base_style = symbol_css_style(symbol_table_id, symbol_code)
|
|
full_style = if extra_style == "", do: base_style, else: base_style <> " " <> extra_style
|
|
|
|
Phoenix.HTML.raw(~s(<div class="#{css_class}" style="#{full_style}"></div>))
|
|
end
|
|
|
|
@doc """
|
|
Get a data URL for the symbol that can be used in JavaScript/Leaflet.
|
|
This creates a small canvas with just the symbol extracted from the sprite.
|
|
"""
|
|
def get_symbol_data_attributes(symbol_table_id, symbol_code) do
|
|
sprite_file = get_sprite_filename(symbol_table_id)
|
|
sprite_file_2x = get_sprite_filename_2x(symbol_table_id)
|
|
{x, y} = get_symbol_position(symbol_code)
|
|
|
|
%{
|
|
"data-sprite" => sprite_file,
|
|
"data-sprite-2x" => sprite_file_2x,
|
|
"data-pos-x" => x,
|
|
"data-pos-y" => y,
|
|
"data-symbol-table" => symbol_table_id,
|
|
"data-symbol-code" => symbol_code
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Get the default symbol for unknown or invalid symbols.
|
|
"""
|
|
def default_symbol do
|
|
# Car icon as default
|
|
{"/", ">"}
|
|
end
|
|
|
|
@doc """
|
|
Validate if a symbol table ID and code combination is valid.
|
|
"""
|
|
def valid_symbol?(symbol_table_id, symbol_code) do
|
|
case {symbol_table_id, symbol_code} do
|
|
{table, code} when table in ["/", "\\"] and is_binary(code) and byte_size(code) > 0 ->
|
|
char_code = code |> String.first() |> String.to_charlist() |> List.first()
|
|
char_code >= 32 and char_code <= 127
|
|
|
|
_ ->
|
|
false
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Get a human-readable description for common APRS symbols.
|
|
This is a subset of common symbols - for a complete list, you'd want to
|
|
reference the official APRS symbol specification.
|
|
"""
|
|
def symbol_description(symbol_table_id, symbol_code) do
|
|
case {symbol_table_id, symbol_code} do
|
|
{"/", ">"} -> "Car"
|
|
{"/", "k"} -> "Truck"
|
|
{"/", "j"} -> "Jeep"
|
|
{"/", "f"} -> "Fire truck"
|
|
{"/", "a"} -> "Ambulance"
|
|
{"/", "b"} -> "Bike"
|
|
{"/", "g"} -> "Glider"
|
|
{"/", "^"} -> "Aircraft"
|
|
{"/", "s"} -> "Ship"
|
|
{"/", "Y"} -> "Yacht"
|
|
{"/", "-"} -> "House"
|
|
{"/", "r"} -> "Repeater"
|
|
{"/", "/"} -> "Position"
|
|
{"\\", "/"} -> "Triangle"
|
|
{"\\", ">"} -> "Car (alternate)"
|
|
_ -> "Unknown symbol"
|
|
end
|
|
end
|
|
end
|