Swaps `if`/`cond` branches for multi-clause function heads or head-pattern matching, making the control flow visible in the function signatures: - Navigation.determine_map_location: resolve_location/4 now dispatches on the shape of the geolocation input and whether URL params are explicit. - Navigation.handle_callsign_tracking: per-case clauses for empty callsign and explicit-URL-params short-circuits, plus a final clause that does the DB lookup and rescues once. - Navigation.handle_callsign_search: dispatch_callsign_search/3 separates the valid/invalid callsign branches. - InsertOptimizer.optimize_based_on_performance: pattern-match on [_, _, _ | _] to require ≥3 samples without calling length/1. - InsertOptimizer.calculate_insert_options and throughput helpers split into guarded clauses instead of inline `if`. - maybe_emit_optimization uses same-var pattern matching to skip the telemetry hit when nothing changed. Adds unit tests covering every branch of both modules plus SignalHandler, LocaleHook, and the InsertOptimizer's :noproc fallback path. Coverage 64.69% → 65.72%.
113 lines
4 KiB
Elixir
113 lines
4 KiB
Elixir
defmodule AprsmeWeb.MapLive.Navigation do
|
|
@moduledoc """
|
|
Handles geolocation, callsign tracking, and map navigation functionality.
|
|
"""
|
|
|
|
import Phoenix.Component, only: [assign: 3]
|
|
|
|
alias Aprsme.Packets
|
|
alias AprsmeWeb.Live.Shared.ParamUtils
|
|
alias AprsmeWeb.MapLive.UrlParams
|
|
alias Phoenix.LiveView
|
|
alias Phoenix.LiveView.Socket
|
|
|
|
require Logger
|
|
|
|
@doc """
|
|
Determine map location based on URL parameters and IP geolocation.
|
|
Returns {map_center, zoom, should_skip_initial_url_update}.
|
|
"""
|
|
@spec determine_map_location(map(), map()) :: {map(), integer(), boolean()}
|
|
def determine_map_location(params, session) do
|
|
{url_center, url_zoom} = UrlParams.parse_map_params(params)
|
|
has_explicit_url_params = UrlParams.has_explicit_url_params?(params)
|
|
geo = session["ip_geolocation"]
|
|
|
|
Logger.info(
|
|
"determine_map_location: session ip_geolocation=#{inspect(geo)}, has_explicit_url_params=#{has_explicit_url_params}"
|
|
)
|
|
|
|
resolve_location(geo, has_explicit_url_params, url_center, url_zoom)
|
|
end
|
|
|
|
# Explicit URL params always win over geolocation.
|
|
defp resolve_location(_geo, true, url_center, url_zoom) do
|
|
Logger.info("Using URL params over geolocation: center=#{inspect(url_center)}, zoom=#{url_zoom}")
|
|
{url_center, url_zoom, false}
|
|
end
|
|
|
|
# Valid geolocation + no explicit URL params → use the geolocation.
|
|
defp resolve_location(%{"lat" => lat, "lng" => lng}, false, _url_center, _url_zoom)
|
|
when is_number(lat) and is_number(lng) do
|
|
Logger.info("Using IP geolocation: lat=#{lat}, lng=#{lng}, zoom=11")
|
|
{%{lat: lat, lng: lng}, 11, true}
|
|
end
|
|
|
|
# No usable geolocation → fall back to URL/defaults.
|
|
defp resolve_location(_geo, has_explicit_url_params, url_center, url_zoom) do
|
|
Logger.info("No IP geolocation in session, using defaults")
|
|
{url_center, url_zoom, !has_explicit_url_params}
|
|
end
|
|
|
|
@doc """
|
|
Handle callsign tracking by finding the latest packet for the callsign.
|
|
Returns {final_map_center, final_map_zoom}.
|
|
"""
|
|
@spec handle_callsign_tracking(binary(), map(), integer(), boolean()) :: {map(), integer()}
|
|
def handle_callsign_tracking("", map_center, map_zoom, _), do: {map_center, map_zoom}
|
|
def handle_callsign_tracking(_, map_center, map_zoom, true), do: {map_center, map_zoom}
|
|
|
|
def handle_callsign_tracking(tracked_callsign, map_center, map_zoom, false) do
|
|
case Packets.get_latest_packet_for_callsign(tracked_callsign) do
|
|
%{lat: lat, lon: lon} when is_number(lat) and is_number(lon) ->
|
|
{%{lat: lat, lng: lon}, 12}
|
|
|
|
_ ->
|
|
{map_center, map_zoom}
|
|
end
|
|
rescue
|
|
# Handle database connection errors gracefully (especially in tests).
|
|
_error -> {map_center, map_zoom}
|
|
end
|
|
|
|
@doc """
|
|
Handle callsign search with validation.
|
|
"""
|
|
@spec handle_callsign_search(binary(), Socket.t()) :: {:noreply, Socket.t()}
|
|
def handle_callsign_search("", socket), do: {:noreply, socket}
|
|
|
|
def handle_callsign_search(callsign, socket) do
|
|
dispatch_callsign_search(ParamUtils.valid_callsign?(callsign), callsign, socket)
|
|
end
|
|
|
|
defp dispatch_callsign_search(true, callsign, socket) do
|
|
{:noreply, LiveView.push_navigate(socket, to: "/#{callsign}")}
|
|
end
|
|
|
|
defp dispatch_callsign_search(false, _callsign, socket) do
|
|
{:noreply, LiveView.put_flash(socket, :error, "Invalid callsign format")}
|
|
end
|
|
|
|
@doc """
|
|
Update map center and zoom to specific location.
|
|
"""
|
|
@spec update_and_zoom_to_location(Socket.t(), float(), float(), integer()) :: Socket.t()
|
|
def update_and_zoom_to_location(socket, lat, lng, zoom) do
|
|
socket
|
|
|> assign(:map_center, %{lat: lat, lng: lng})
|
|
|> assign(:map_zoom, zoom)
|
|
|> LiveView.push_event("zoom_to_location", %{lat: lat, lng: lng, zoom: zoom})
|
|
end
|
|
|
|
@doc """
|
|
Zoom to current location stored in socket.
|
|
"""
|
|
@spec zoom_to_current_location(Socket.t()) :: Socket.t()
|
|
def zoom_to_current_location(socket) do
|
|
LiveView.push_event(socket, "zoom_to_location", %{
|
|
lat: socket.assigns.map_center.lat,
|
|
lng: socket.assigns.map_center.lng,
|
|
zoom: socket.assigns.map_zoom
|
|
})
|
|
end
|
|
end
|