Refactor conditionals to use multi-clause pattern matching
Replace case/cond/if blocks with multi-clause function dispatch
and pattern matching in function heads across 9 modules:
- WeatherUnits: case to multi-clause unit_system/1, unit_labels via do_unit_labels/1
- PacketUtils: if to pattern match on %{"id" => id}, multi-clause threshold/callsign
- SetLocale: case to multi-clause extract_locale/1
- IPGeolocation: if/cond to pattern match on conn, binary prefix matching for private_ip?
- DeviceIdentification: cond to extracted pattern_matches?/2
- MobileChannel: cond to multi-clause do_callsign_match/2
- PacketProcessor: cond to dispatch_visibility/6 on {in_bounds, has_marker} tuple
- PacketManager: destructure packet_state in heads, multi-clause maybe_cleanup/3
- AprsSymbol: case/if to multi-clause get_table_id/1, normalize_symbol_code/1
This commit is contained in:
parent
6b624e1365
commit
4366fa36da
9 changed files with 171 additions and 256 deletions
|
|
@ -221,28 +221,23 @@ defmodule Aprsme.DeviceIdentification do
|
||||||
end
|
end
|
||||||
|
|
||||||
Enum.find(devices, fn device ->
|
Enum.find(devices, fn device ->
|
||||||
pattern = device.identifier
|
pattern_matches?(device.identifier, identifier)
|
||||||
|
|
||||||
cond do
|
|
||||||
String.contains?(pattern, "?") ->
|
|
||||||
try do
|
|
||||||
regex = wildcard_pattern_to_regex(pattern)
|
|
||||||
Regex.match?(regex, identifier)
|
|
||||||
rescue
|
|
||||||
_e in Regex.CompileError ->
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
String.contains?(pattern, "*") ->
|
|
||||||
# Compare literally if pattern contains * but not ?
|
|
||||||
pattern == identifier
|
|
||||||
|
|
||||||
true ->
|
|
||||||
pattern == identifier
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp pattern_matches?(pattern, identifier) do
|
||||||
|
if String.contains?(pattern, "?") do
|
||||||
|
try do
|
||||||
|
regex = wildcard_pattern_to_regex(pattern)
|
||||||
|
Regex.match?(regex, identifier)
|
||||||
|
rescue
|
||||||
|
_e in Regex.CompileError -> false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
pattern == identifier
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Converts a pattern with ? wildcards to a regex
|
# Converts a pattern with ? wildcards to a regex
|
||||||
defp wildcard_pattern_to_regex(pattern) when is_binary(pattern) do
|
defp wildcard_pattern_to_regex(pattern) when is_binary(pattern) do
|
||||||
# Replace ? with a placeholder, escape all regex metacharacters except the placeholder,
|
# Replace ? with a placeholder, escape all regex metacharacters except the placeholder,
|
||||||
|
|
|
||||||
|
|
@ -31,38 +31,37 @@ defmodule AprsmeWeb.AprsSymbol do
|
||||||
background_size: String.t()
|
background_size: String.t()
|
||||||
}
|
}
|
||||||
def get_sprite_info(symbol_table, symbol_code) do
|
def get_sprite_info(symbol_table, symbol_code) do
|
||||||
# For overlay symbols (A-Z, 0-9), display the base symbol with overlay
|
compute_sprite_info(overlay_symbol?(symbol_table), symbol_table, symbol_code)
|
||||||
if symbol_table && String.match?(symbol_table, ~r/^[A-Z0-9]$/) do
|
end
|
||||||
# Use the base symbol from the overlay table, not the overlay character itself
|
|
||||||
get_overlay_base_symbol_info(symbol_code)
|
|
||||||
else
|
|
||||||
# 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
|
defp overlay_symbol?(table) when is_binary(table), do: String.match?(table, ~r/^[A-Z0-9]$/)
|
||||||
table_id = get_table_id(symbol_table)
|
defp overlay_symbol?(_), do: false
|
||||||
|
|
||||||
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
|
defp compute_sprite_info(true, _symbol_table, symbol_code) do
|
||||||
|
get_overlay_base_symbol_info(symbol_code)
|
||||||
|
end
|
||||||
|
|
||||||
# Get symbol position using ASCII-based calculation
|
defp compute_sprite_info(false, symbol_table, symbol_code) do
|
||||||
symbol_code_ord = get_symbol_code_ord(symbol_code)
|
symbol_table = normalize_symbol_table(symbol_table)
|
||||||
|
symbol_code = normalize_symbol_code(symbol_code)
|
||||||
|
|
||||||
index = symbol_code_ord - 33
|
table_id = get_table_id(symbol_table)
|
||||||
safe_index = max(0, min(index, 93))
|
sprite_file = "/aprs-symbols/aprs-symbols-128-#{table_id}@2x.png"
|
||||||
|
|
||||||
# Calculate positioning for 16-column grid
|
symbol_code_ord = get_symbol_code_ord(symbol_code)
|
||||||
column = rem(safe_index, 16)
|
index = symbol_code_ord - 33
|
||||||
row = div(safe_index, 16)
|
safe_index = max(0, min(index, 93))
|
||||||
x = -column * 128
|
|
||||||
y = -row * 128
|
|
||||||
|
|
||||||
%{
|
column = rem(safe_index, 16)
|
||||||
sprite_file: sprite_file,
|
row = div(safe_index, 16)
|
||||||
background_position: "#{x / 4}px #{y / 4}px",
|
x = -column * 128
|
||||||
background_size: "512px 192px"
|
y = -row * 128
|
||||||
}
|
|
||||||
end
|
%{
|
||||||
|
sprite_file: sprite_file,
|
||||||
|
background_position: "#{x / 4}px #{y / 4}px",
|
||||||
|
background_size: "512px 192px"
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -188,9 +187,9 @@ defmodule AprsmeWeb.AprsSymbol do
|
||||||
">"
|
">"
|
||||||
"""
|
"""
|
||||||
@spec normalize_symbol_code(String.t() | nil) :: String.t()
|
@spec normalize_symbol_code(String.t() | nil) :: String.t()
|
||||||
def normalize_symbol_code(symbol_code) do
|
def normalize_symbol_code(nil), do: ">"
|
||||||
if symbol_code && symbol_code != "", do: symbol_code, else: ">"
|
def normalize_symbol_code(""), do: ">"
|
||||||
end
|
def normalize_symbol_code(symbol_code), do: symbol_code
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Maps a symbol table to its sprite file ID.
|
Maps a symbol table to its sprite file ID.
|
||||||
|
|
@ -207,18 +206,10 @@ defmodule AprsmeWeb.AprsSymbol do
|
||||||
"2"
|
"2"
|
||||||
"""
|
"""
|
||||||
@spec get_table_id(String.t()) :: String.t()
|
@spec get_table_id(String.t()) :: String.t()
|
||||||
def get_table_id(symbol_table) do
|
def get_table_id("/"), do: "0"
|
||||||
case symbol_table do
|
def get_table_id("\\"), do: "1"
|
||||||
# Primary table
|
def get_table_id("]"), do: "2"
|
||||||
"/" -> "0"
|
def get_table_id(_), do: "0"
|
||||||
# Alternate table
|
|
||||||
"\\" -> "1"
|
|
||||||
# Overlay table (A-Z, 0-9)
|
|
||||||
"]" -> "2"
|
|
||||||
# Default to primary table
|
|
||||||
_ -> "0"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Renders an APRS symbol as HTML for use in Leaflet markers.
|
Renders an APRS symbol as HTML for use in Leaflet markers.
|
||||||
|
|
|
||||||
|
|
@ -478,27 +478,19 @@ defmodule AprsmeWeb.MobileChannel do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp callsign_matches?(packet_callsign, tracked_callsign) do
|
defp callsign_matches?(packet_callsign, tracked_callsign) do
|
||||||
# Normalize both to uppercase
|
normalized_packet = String.upcase(packet_callsign)
|
||||||
packet_callsign = String.upcase(packet_callsign)
|
normalized_tracked = String.upcase(tracked_callsign)
|
||||||
tracked_callsign = String.upcase(tracked_callsign)
|
do_callsign_match(normalized_packet, normalized_tracked)
|
||||||
|
end
|
||||||
|
|
||||||
cond do
|
defp do_callsign_match(callsign, callsign), do: true
|
||||||
# Exact match
|
|
||||||
packet_callsign == tracked_callsign ->
|
|
||||||
true
|
|
||||||
|
|
||||||
# Wildcard match (e.g., "W5ISP*" matches "W5ISP-9")
|
defp do_callsign_match(packet_callsign, tracked_callsign) do
|
||||||
String.contains?(tracked_callsign, "*") ->
|
if String.contains?(tracked_callsign, "*") do
|
||||||
pattern = String.replace(tracked_callsign, "*", "")
|
pattern = String.replace(tracked_callsign, "*", "")
|
||||||
String.starts_with?(packet_callsign, pattern)
|
String.starts_with?(packet_callsign, pattern)
|
||||||
|
else
|
||||||
# Base callsign match (e.g., "W5ISP" matches "W5ISP-9")
|
String.starts_with?(packet_callsign, tracked_callsign <> "-")
|
||||||
String.starts_with?(packet_callsign, tracked_callsign <> "-") ->
|
|
||||||
true
|
|
||||||
|
|
||||||
# No match
|
|
||||||
true ->
|
|
||||||
false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,55 +35,43 @@ defmodule AprsmeWeb.MapLive.PacketManager do
|
||||||
Add packets to visible storage with memory management.
|
Add packets to visible storage with memory management.
|
||||||
Returns updated state and packet IDs for broadcasting.
|
Returns updated state and packet IDs for broadcasting.
|
||||||
"""
|
"""
|
||||||
def add_visible_packets(packet_state, packets) when is_list(packets) do
|
def add_visible_packets(%{visible_packet_ids: visible_ids, packet_count: packet_count} = packet_state, packets)
|
||||||
# Store packets and get IDs
|
when is_list(packets) do
|
||||||
packet_ids = PacketStore.store_packets(packets)
|
packet_ids = PacketStore.store_packets(packets)
|
||||||
|
new_visible_ids = packet_ids ++ visible_ids
|
||||||
|
|
||||||
# Update visible packet tracking
|
|
||||||
new_visible_ids = packet_ids ++ packet_state.visible_packet_ids
|
|
||||||
|
|
||||||
# Apply memory management
|
|
||||||
{managed_ids, cleanup_needed} = apply_memory_limits(new_visible_ids, @max_visible_packets)
|
{managed_ids, cleanup_needed} = apply_memory_limits(new_visible_ids, @max_visible_packets)
|
||||||
|
|
||||||
updated_state = %{
|
updated_state = %{
|
||||||
packet_state
|
packet_state
|
||||||
| visible_packet_ids: managed_ids,
|
| visible_packet_ids: managed_ids,
|
||||||
packet_count: Map.put(packet_state.packet_count, :visible, length(managed_ids))
|
packet_count: Map.put(packet_count, :visible, length(managed_ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
# Clean up if needed
|
final_state = maybe_cleanup(updated_state, cleanup_needed, :visible)
|
||||||
final_state =
|
|
||||||
if cleanup_needed do
|
|
||||||
perform_cleanup(updated_state, :visible)
|
|
||||||
else
|
|
||||||
updated_state
|
|
||||||
end
|
|
||||||
|
|
||||||
{final_state, packet_ids}
|
{final_state, packet_ids}
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Add packets to historical storage with sliding window management.
|
Add packets to historical storage with sliding window management.
|
||||||
"""
|
"""
|
||||||
def add_historical_packets(packet_state, packets) when is_list(packets) do
|
def add_historical_packets(
|
||||||
|
%{historical_packet_ids: historical_ids, packet_count: packet_count} = packet_state,
|
||||||
|
packets
|
||||||
|
)
|
||||||
|
when is_list(packets) do
|
||||||
packet_ids = PacketStore.store_packets(packets)
|
packet_ids = PacketStore.store_packets(packets)
|
||||||
|
new_historical_ids = packet_ids ++ historical_ids
|
||||||
|
|
||||||
new_historical_ids = packet_ids ++ packet_state.historical_packet_ids
|
|
||||||
{managed_ids, cleanup_needed} = apply_memory_limits(new_historical_ids, @max_historical_packets)
|
{managed_ids, cleanup_needed} = apply_memory_limits(new_historical_ids, @max_historical_packets)
|
||||||
|
|
||||||
updated_state = %{
|
updated_state = %{
|
||||||
packet_state
|
packet_state
|
||||||
| historical_packet_ids: managed_ids,
|
| historical_packet_ids: managed_ids,
|
||||||
packet_count: Map.put(packet_state.packet_count, :historical, length(managed_ids))
|
packet_count: Map.put(packet_count, :historical, length(managed_ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
final_state =
|
final_state = maybe_cleanup(updated_state, cleanup_needed, :historical)
|
||||||
if cleanup_needed do
|
|
||||||
perform_cleanup(updated_state, :historical)
|
|
||||||
else
|
|
||||||
updated_state
|
|
||||||
end
|
|
||||||
|
|
||||||
{final_state, packet_ids}
|
{final_state, packet_ids}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -165,15 +153,15 @@ defmodule AprsmeWeb.MapLive.PacketManager do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp perform_cleanup(packet_state, _type) do
|
defp maybe_cleanup(state, false, _type), do: state
|
||||||
|
|
||||||
|
defp maybe_cleanup(%{last_cleanup: last_cleanup} = state, true, _type) do
|
||||||
current_time = System.monotonic_time(:millisecond)
|
current_time = System.monotonic_time(:millisecond)
|
||||||
|
|
||||||
# Only cleanup if enough time has passed to avoid excessive cleanup
|
if current_time - last_cleanup > 30_000 do
|
||||||
# 30 seconds
|
%{state | last_cleanup: current_time}
|
||||||
if current_time - packet_state.last_cleanup > 30_000 do
|
|
||||||
%{packet_state | last_cleanup: current_time}
|
|
||||||
else
|
else
|
||||||
packet_state
|
state
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,35 +51,40 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
|
||||||
"""
|
"""
|
||||||
@spec handle_packet_visibility(map(), number() | nil, number() | nil, binary(), Socket.t()) :: Socket.t()
|
@spec handle_packet_visibility(map(), number() | nil, number() | nil, binary(), Socket.t()) :: Socket.t()
|
||||||
def handle_packet_visibility(packet, lat, lon, callsign_key, socket) do
|
def handle_packet_visibility(packet, lat, lon, callsign_key, socket) do
|
||||||
cond do
|
visibility_action(packet, lat, lon, callsign_key, socket)
|
||||||
should_remove_marker?(lat, lon, callsign_key, socket) ->
|
end
|
||||||
remove_marker_from_map(callsign_key, socket)
|
|
||||||
|
|
||||||
should_add_marker?(lat, lon, callsign_key, socket) ->
|
defp visibility_action(packet, lat, lon, callsign_key, socket) when not is_nil(lat) and not is_nil(lon) do
|
||||||
handle_valid_postgres_packet(packet, lat, lon, socket)
|
in_bounds = within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds)
|
||||||
|
has_marker = Map.has_key?(socket.assigns.visible_packets, callsign_key)
|
||||||
|
dispatch_visibility({in_bounds, has_marker}, packet, lat, lon, callsign_key, socket)
|
||||||
|
end
|
||||||
|
|
||||||
should_update_marker?(lat, lon, callsign_key, socket) ->
|
defp visibility_action(_packet, _lat, _lon, _callsign_key, socket), do: socket
|
||||||
# Marker exists and is within bounds - check if there's significant movement
|
|
||||||
existing_packet = socket.assigns.visible_packets[callsign_key]
|
|
||||||
{existing_lat, existing_lon, _} = CoordinateUtils.get_coordinates(existing_packet)
|
|
||||||
|
|
||||||
# Check if we have valid existing coordinates
|
defp dispatch_visibility({false, true}, _packet, _lat, _lon, callsign_key, socket) do
|
||||||
if is_number(existing_lat) and is_number(existing_lon) and
|
remove_marker_from_map(callsign_key, socket)
|
||||||
GeoUtils.significant_movement?(existing_lat, existing_lon, lat, lon, 50) do
|
end
|
||||||
# Significant movement detected (more than 50 meters), update the marker
|
|
||||||
handle_valid_postgres_packet(packet, lat, lon, socket)
|
|
||||||
else
|
|
||||||
# Just GPS drift (less than 50 meters) or invalid coordinates,
|
|
||||||
# update the packet data but don't send visual update
|
|
||||||
new_visible_packets = Map.put(socket.assigns.visible_packets, callsign_key, packet)
|
|
||||||
assign(socket, :visible_packets, new_visible_packets)
|
|
||||||
end
|
|
||||||
|
|
||||||
true ->
|
defp dispatch_visibility({true, false}, packet, lat, lon, _callsign_key, socket) do
|
||||||
socket
|
handle_valid_postgres_packet(packet, lat, lon, socket)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp dispatch_visibility({true, true}, packet, lat, lon, callsign_key, socket) do
|
||||||
|
existing_packet = socket.assigns.visible_packets[callsign_key]
|
||||||
|
{existing_lat, existing_lon, _} = CoordinateUtils.get_coordinates(existing_packet)
|
||||||
|
|
||||||
|
if is_number(existing_lat) and is_number(existing_lon) and
|
||||||
|
GeoUtils.significant_movement?(existing_lat, existing_lon, lat, lon, 50) do
|
||||||
|
handle_valid_postgres_packet(packet, lat, lon, socket)
|
||||||
|
else
|
||||||
|
new_visible_packets = Map.put(socket.assigns.visible_packets, callsign_key, packet)
|
||||||
|
assign(socket, :visible_packets, new_visible_packets)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp dispatch_visibility(_, _packet, _lat, _lon, _callsign_key, socket), do: socket
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Handle valid postgres packet by adding it to visible packets and displaying it.
|
Handle valid postgres packet by adding it to visible packets and displaying it.
|
||||||
"""
|
"""
|
||||||
|
|
@ -128,24 +133,6 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
|
||||||
|
|
||||||
# Private helper functions
|
# Private helper functions
|
||||||
|
|
||||||
defp should_remove_marker?(lat, lon, callsign_key, socket) do
|
|
||||||
!is_nil(lat) and !is_nil(lon) and
|
|
||||||
Map.has_key?(socket.assigns.visible_packets, callsign_key) and
|
|
||||||
not within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp should_add_marker?(lat, lon, callsign_key, socket) do
|
|
||||||
!is_nil(lat) and !is_nil(lon) and
|
|
||||||
not Map.has_key?(socket.assigns.visible_packets, callsign_key) and
|
|
||||||
within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp should_update_marker?(lat, lon, callsign_key, socket) do
|
|
||||||
!is_nil(lat) and !is_nil(lon) and
|
|
||||||
Map.has_key?(socket.assigns.visible_packets, callsign_key) and
|
|
||||||
within_bounds?(%{lat: lat, lon: lon}, socket.assigns.map_bounds)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Use shared bounds utility
|
# Use shared bounds utility
|
||||||
defp within_bounds?(coords, bounds) do
|
defp within_bounds?(coords, bounds) do
|
||||||
BoundsUtils.within_bounds?(coords, bounds)
|
BoundsUtils.within_bounds?(coords, bounds)
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,8 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
||||||
Get unique callsign key from packet.
|
Get unique callsign key from packet.
|
||||||
"""
|
"""
|
||||||
@spec get_callsign_key(map()) :: binary()
|
@spec get_callsign_key(map()) :: binary()
|
||||||
def get_callsign_key(packet) do
|
def get_callsign_key(%{"id" => id}), do: to_string(id)
|
||||||
if Map.has_key?(packet, "id"),
|
def get_callsign_key(_packet), do: [:positive] |> System.unique_integer() |> to_string()
|
||||||
do: to_string(packet["id"]),
|
|
||||||
else: [:positive] |> System.unique_integer() |> to_string()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Prune oldest packets from a map to enforce memory limits.
|
Prune oldest packets from a map to enforce memory limits.
|
||||||
|
|
@ -155,18 +152,13 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
||||||
Check if a packet is within the time threshold (not too old).
|
Check if a packet is within the time threshold (not too old).
|
||||||
"""
|
"""
|
||||||
@spec packet_within_time_threshold?(struct(), any()) :: boolean()
|
@spec packet_within_time_threshold?(struct(), any()) :: boolean()
|
||||||
def packet_within_time_threshold?(packet, threshold) do
|
def packet_within_time_threshold?(%{received_at: received_at}, threshold) when not is_nil(received_at) do
|
||||||
case packet do
|
threshold_dt = convert_threshold_to_datetime(threshold)
|
||||||
%{received_at: received_at} when not is_nil(received_at) ->
|
DateTime.compare(received_at, threshold_dt) in [:gt, :eq]
|
||||||
threshold_dt = convert_threshold_to_datetime(threshold)
|
|
||||||
DateTime.compare(received_at, threshold_dt) in [:gt, :eq]
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
# If no timestamp, treat as current
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def packet_within_time_threshold?(_packet, _threshold), do: true
|
||||||
|
|
||||||
defp convert_threshold_to_datetime(threshold) when is_integer(threshold) do
|
defp convert_threshold_to_datetime(threshold) when is_integer(threshold) do
|
||||||
# Assume seconds since epoch
|
# Assume seconds since epoch
|
||||||
DateTime.from_unix!(threshold)
|
DateTime.from_unix!(threshold)
|
||||||
|
|
@ -228,11 +220,10 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
||||||
def generate_callsign(packet) do
|
def generate_callsign(packet) do
|
||||||
base_callsign = get_packet_field(packet, :base_callsign, "")
|
base_callsign = get_packet_field(packet, :base_callsign, "")
|
||||||
ssid = get_packet_field(packet, :ssid, nil)
|
ssid = get_packet_field(packet, :ssid, nil)
|
||||||
|
format_callsign_with_ssid(base_callsign, ssid)
|
||||||
if ssid != nil and ssid != "" do
|
|
||||||
"#{base_callsign}-#{ssid}"
|
|
||||||
else
|
|
||||||
base_callsign
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp format_callsign_with_ssid(base_callsign, nil), do: base_callsign
|
||||||
|
defp format_callsign_with_ssid(base_callsign, ""), do: base_callsign
|
||||||
|
defp format_callsign_with_ssid(base_callsign, ssid), do: "#{base_callsign}-#{ssid}"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -13,23 +13,17 @@ defmodule AprsmeWeb.Plugs.IPGeolocation do
|
||||||
def init(opts), do: opts
|
def init(opts), do: opts
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def call(conn, _opts) do
|
def call(%{request_path: "/", method: "GET"} = conn, _opts) do
|
||||||
# Only run for the main map page
|
conn
|
||||||
if conn.request_path == "/" && conn.method == "GET" do
|
|> get_session(:ip_geolocation)
|
||||||
case get_session(conn, :ip_geolocation) do
|
|> handle_geolocation(conn)
|
||||||
nil ->
|
|
||||||
# No cached geolocation, fetch it
|
|
||||||
perform_geolocation(conn)
|
|
||||||
|
|
||||||
_cached ->
|
|
||||||
# Already have geolocation in session
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
else
|
|
||||||
conn
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def call(conn, _opts), do: conn
|
||||||
|
|
||||||
|
defp handle_geolocation(nil, conn), do: perform_geolocation(conn)
|
||||||
|
defp handle_geolocation(_cached, conn), do: conn
|
||||||
|
|
||||||
defp perform_geolocation(conn) do
|
defp perform_geolocation(conn) do
|
||||||
ip = get_client_ip(conn)
|
ip = get_client_ip(conn)
|
||||||
Logger.info("IP geolocation: Starting lookup for IP #{ip}")
|
Logger.info("IP geolocation: Starting lookup for IP #{ip}")
|
||||||
|
|
@ -133,16 +127,12 @@ defmodule AprsmeWeb.Plugs.IPGeolocation do
|
||||||
|
|
||||||
defp valid_ip_for_geolocation?(_), do: false
|
defp valid_ip_for_geolocation?(_), do: false
|
||||||
|
|
||||||
defp private_ip?(ip) do
|
defp private_ip?("127." <> _), do: true
|
||||||
cond do
|
defp private_ip?("::1" <> _), do: true
|
||||||
String.starts_with?(ip, "127.") -> true
|
defp private_ip?("10." <> _), do: true
|
||||||
String.starts_with?(ip, "::1") -> true
|
defp private_ip?("192.168." <> _), do: true
|
||||||
String.starts_with?(ip, "10.") -> true
|
defp private_ip?("172." <> _ = ip), do: in_172_private_range?(ip)
|
||||||
String.starts_with?(ip, "192.168.") -> true
|
defp private_ip?(_), do: false
|
||||||
String.starts_with?(ip, "172.") -> in_172_private_range?(ip)
|
|
||||||
true -> false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if IP is in 172.16.0.0/12 range (172.16.0.0 - 172.31.255.255)
|
# Check if IP is in 172.16.0.0/12 range (172.16.0.0 - 172.31.255.255)
|
||||||
defp in_172_private_range?(ip) do
|
defp in_172_private_range?(ip) do
|
||||||
|
|
|
||||||
|
|
@ -17,15 +17,14 @@ defmodule AprsmeWeb.Plugs.SetLocale do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_locale_from_header(conn) do
|
defp get_locale_from_header(conn) do
|
||||||
case get_req_header(conn, "accept-language") do
|
conn
|
||||||
[accept_language | _] ->
|
|> get_req_header("accept-language")
|
||||||
parse_accept_language(accept_language)
|
|> extract_locale()
|
||||||
|
|
||||||
_ ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp extract_locale([accept_language | _]), do: parse_accept_language(accept_language)
|
||||||
|
defp extract_locale(_), do: nil
|
||||||
|
|
||||||
defp parse_accept_language(accept_language) do
|
defp parse_accept_language(accept_language) do
|
||||||
accept_language
|
accept_language
|
||||||
|> String.split(",")
|
|> String.split(",")
|
||||||
|
|
|
||||||
|
|
@ -11,21 +11,11 @@ defmodule AprsmeWeb.WeatherUnits do
|
||||||
Returns :imperial for US, :metric for most other countries.
|
Returns :imperial for US, :metric for most other countries.
|
||||||
"""
|
"""
|
||||||
@spec unit_system(String.t() | nil | any()) :: :imperial | :metric
|
@spec unit_system(String.t() | nil | any()) :: :imperial | :metric
|
||||||
def unit_system(locale) when is_binary(locale) do
|
def unit_system("en"), do: :imperial
|
||||||
case locale do
|
def unit_system("es"), do: :metric
|
||||||
# Default to imperial for English
|
def unit_system("de"), do: :metric
|
||||||
"en" -> :imperial
|
def unit_system("fr"), do: :metric
|
||||||
# Spanish-speaking countries mostly use metric
|
def unit_system(locale) when is_binary(locale), do: :metric
|
||||||
"es" -> :metric
|
|
||||||
# Germany uses metric
|
|
||||||
"de" -> :metric
|
|
||||||
# France uses metric
|
|
||||||
"fr" -> :metric
|
|
||||||
# Default to metric for unknown locales
|
|
||||||
_ -> :metric
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def unit_system(nil), do: :imperial
|
def unit_system(nil), do: :imperial
|
||||||
def unit_system(_), do: :imperial
|
def unit_system(_), do: :imperial
|
||||||
|
|
||||||
|
|
@ -34,12 +24,8 @@ defmodule AprsmeWeb.WeatherUnits do
|
||||||
Returns the temperature in the appropriate unit for the locale.
|
Returns the temperature in the appropriate unit for the locale.
|
||||||
"""
|
"""
|
||||||
@spec format_temperature(number() | any(), String.t()) :: {number() | any(), String.t()}
|
@spec format_temperature(number() | any(), String.t()) :: {number() | any(), String.t()}
|
||||||
def format_temperature(temp, locale) when is_number(temp) do
|
def format_temperature(temp, locale) when is_number(temp),
|
||||||
case unit_system(locale) do
|
do: format_by_unit(temp, locale, &Convert.f_to_c/1, "°F", "°C")
|
||||||
:imperial -> {temp, "°F"}
|
|
||||||
:metric -> {Convert.f_to_c(temp), "°C"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_temperature(temp, _locale), do: {temp, "°F"}
|
def format_temperature(temp, _locale), do: {temp, "°F"}
|
||||||
|
|
||||||
|
|
@ -48,12 +34,8 @@ defmodule AprsmeWeb.WeatherUnits do
|
||||||
Returns the wind speed in the appropriate unit for the locale.
|
Returns the wind speed in the appropriate unit for the locale.
|
||||||
"""
|
"""
|
||||||
@spec format_wind_speed(number() | any(), String.t()) :: {number() | any(), String.t()}
|
@spec format_wind_speed(number() | any(), String.t()) :: {number() | any(), String.t()}
|
||||||
def format_wind_speed(speed, locale) when is_number(speed) do
|
def format_wind_speed(speed, locale) when is_number(speed),
|
||||||
case unit_system(locale) do
|
do: format_by_unit(speed, locale, &Convert.mph_to_kph/1, "mph", "km/h")
|
||||||
:imperial -> {speed, "mph"}
|
|
||||||
:metric -> {Convert.mph_to_kph(speed), "km/h"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_wind_speed(speed, _locale), do: {speed, "mph"}
|
def format_wind_speed(speed, _locale), do: {speed, "mph"}
|
||||||
|
|
||||||
|
|
@ -62,12 +44,8 @@ defmodule AprsmeWeb.WeatherUnits do
|
||||||
Returns the rain amount in the appropriate unit for the locale.
|
Returns the rain amount in the appropriate unit for the locale.
|
||||||
"""
|
"""
|
||||||
@spec format_rain(number() | any(), String.t()) :: {number() | any(), String.t()}
|
@spec format_rain(number() | any(), String.t()) :: {number() | any(), String.t()}
|
||||||
def format_rain(rain, locale) when is_number(rain) do
|
def format_rain(rain, locale) when is_number(rain),
|
||||||
case unit_system(locale) do
|
do: format_by_unit(rain, locale, &Convert.inches_to_mm/1, "in", "mm")
|
||||||
:imperial -> {rain, "in"}
|
|
||||||
:metric -> {Convert.inches_to_mm(rain), "mm"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_rain(rain, _locale), do: {rain, "in"}
|
def format_rain(rain, _locale), do: {rain, "in"}
|
||||||
|
|
||||||
|
|
@ -75,10 +53,7 @@ defmodule AprsmeWeb.WeatherUnits do
|
||||||
Formats pressure (keeps hPa for all locales as it's standard).
|
Formats pressure (keeps hPa for all locales as it's standard).
|
||||||
"""
|
"""
|
||||||
@spec format_pressure(number() | any(), any()) :: {number() | any(), String.t()}
|
@spec format_pressure(number() | any(), any()) :: {number() | any(), String.t()}
|
||||||
def format_pressure(pressure, _locale) when is_number(pressure) do
|
def format_pressure(pressure, _locale) when is_number(pressure), do: {pressure, "hPa"}
|
||||||
{pressure, "hPa"}
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_pressure(pressure, _locale), do: {pressure, "hPa"}
|
def format_pressure(pressure, _locale), do: {pressure, "hPa"}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|
@ -90,23 +65,30 @@ defmodule AprsmeWeb.WeatherUnits do
|
||||||
rain: String.t(),
|
rain: String.t(),
|
||||||
pressure: String.t()
|
pressure: String.t()
|
||||||
}
|
}
|
||||||
def unit_labels(locale) do
|
def unit_labels(locale), do: do_unit_labels(unit_system(locale))
|
||||||
case unit_system(locale) do
|
|
||||||
:imperial ->
|
|
||||||
%{
|
|
||||||
temperature: "°F",
|
|
||||||
wind_speed: "mph",
|
|
||||||
rain: "in",
|
|
||||||
pressure: "hPa"
|
|
||||||
}
|
|
||||||
|
|
||||||
:metric ->
|
defp do_unit_labels(:imperial) do
|
||||||
%{
|
%{
|
||||||
temperature: "°C",
|
temperature: "°F",
|
||||||
wind_speed: "km/h",
|
wind_speed: "mph",
|
||||||
rain: "mm",
|
rain: "in",
|
||||||
pressure: "hPa"
|
pressure: "hPa"
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_unit_labels(:metric) do
|
||||||
|
%{
|
||||||
|
temperature: "°C",
|
||||||
|
wind_speed: "km/h",
|
||||||
|
rain: "mm",
|
||||||
|
pressure: "hPa"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_by_unit(value, locale, converter, imperial_unit, metric_unit) do
|
||||||
|
case unit_system(locale) do
|
||||||
|
:imperial -> {value, imperial_unit}
|
||||||
|
:metric -> {converter.(value), metric_unit}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue