Remove dead PostGIS code, deduplicate utilities, and simplify module hierarchy
- Delete ~170 lines of commented-out PostGIS spatial query functions - Consolidate coordinate validation into CoordinateUtils - Replace private normalize_data_type with EncodingUtils delegation - Inline to_float/to_decimal wrappers at call sites - Fix has_weather_data? missing wind_gust field - Remove BoundsManager passthrough delegation module - Simplify MapLive.PacketUtils by removing pure-passthrough delegations
This commit is contained in:
parent
bb494bce65
commit
50544c8354
13 changed files with 64 additions and 334 deletions
|
|
@ -6,6 +6,7 @@ defmodule Aprsme.Packet do
|
|||
|
||||
alias Aprs.Types.MicE
|
||||
alias Aprsme.DataExtended
|
||||
alias AprsmeWeb.Live.Shared.CoordinateUtils
|
||||
|
||||
schema "packets" do
|
||||
field(:base_callsign, :string)
|
||||
|
|
@ -217,37 +218,14 @@ defmodule Aprsme.Packet do
|
|||
end
|
||||
|
||||
defp valid_coordinates?(lat, lon) do
|
||||
lat = normalize_coordinate(lat)
|
||||
lon = normalize_coordinate(lon)
|
||||
|
||||
is_number(lat) && is_number(lon) &&
|
||||
lat >= -90 && lat <= 90 &&
|
||||
lon >= -180 && lon <= 180
|
||||
CoordinateUtils.valid_coordinates_any_type?(lat, lon)
|
||||
end
|
||||
|
||||
defp normalize_coordinate(%Decimal{} = decimal), do: Decimal.to_float(decimal)
|
||||
defp normalize_coordinate(coord), do: coord
|
||||
|
||||
# Convert atom data_type to string for storage
|
||||
defp normalize_data_type(%{data_type: data_type} = attrs) when is_atom(data_type) do
|
||||
%{attrs | data_type: to_string(data_type)}
|
||||
defp normalize_coordinate(coord) do
|
||||
CoordinateUtils.normalize_coordinate(coord)
|
||||
end
|
||||
|
||||
defp normalize_data_type(%{"data_type" => data_type} = attrs) when is_atom(data_type) do
|
||||
%{attrs | "data_type" => to_string(data_type)}
|
||||
end
|
||||
|
||||
defp normalize_data_type(attrs) when is_map(attrs) do
|
||||
case {Map.has_key?(attrs, :data_type), Map.get(attrs, :data_type)} do
|
||||
{true, data_type} when is_atom(data_type) ->
|
||||
%{attrs | data_type: to_string(data_type)}
|
||||
|
||||
_ ->
|
||||
attrs
|
||||
end
|
||||
end
|
||||
|
||||
defp normalize_data_type(attrs), do: attrs
|
||||
defp normalize_data_type(attrs), do: Aprsme.EncodingUtils.normalize_data_type(attrs)
|
||||
|
||||
@doc """
|
||||
Extracts additional data from the raw packet and data_extended structure
|
||||
|
|
@ -613,88 +591,6 @@ defmodule Aprsme.Packet do
|
|||
defp maybe_put(map, _key, ""), do: map
|
||||
defp maybe_put(map, key, value), do: Map.put(map, key, value)
|
||||
|
||||
# @doc """
|
||||
# Spatial query functions for efficient location-based searches
|
||||
# """
|
||||
# Temporarily commented out until PostGIS is properly configured
|
||||
|
||||
# @doc """
|
||||
# Find packets within a given radius (in meters) of a point.
|
||||
# """
|
||||
# def within_radius(query \\ __MODULE__, lat, lon, radius_meters) do
|
||||
# point = %Geo.Point{coordinates: {lon, lat}, srid: 4326}
|
||||
|
||||
# from p in query,
|
||||
# where: st_dwithin_in_meters(p.location, ^point, ^radius_meters),
|
||||
# where: not is_nil(p.location)
|
||||
# end
|
||||
|
||||
# @doc """
|
||||
# Find packets within a bounding box defined by southwest and northeast corners.
|
||||
# """
|
||||
# def within_bbox(query \\ __MODULE__, sw_lat, sw_lon, ne_lat, ne_lon) do
|
||||
# # Create a polygon representing the bounding box
|
||||
# bbox = %Geo.Polygon{
|
||||
# coordinates: [[
|
||||
# {sw_lon, sw_lat},
|
||||
# {ne_lon, sw_lat},
|
||||
# {ne_lon, ne_lat},
|
||||
# {sw_lon, ne_lat},
|
||||
# {sw_lon, sw_lat}
|
||||
# ]],
|
||||
# srid: 4326
|
||||
# }
|
||||
|
||||
# from p in query,
|
||||
# where: st_within(p.location, ^bbox),
|
||||
# where: not is_nil(p.location)
|
||||
# end
|
||||
|
||||
# @doc """
|
||||
# Find packets ordered by distance from a given point.
|
||||
# """
|
||||
# def nearest_to(query \\ __MODULE__, lat, lon, limit \\ 100) do
|
||||
# point = %Geo.Point{coordinates: {lon, lat}, srid: 4326}
|
||||
|
||||
# from p in query,
|
||||
# where: not is_nil(p.location),
|
||||
# order_by: st_distance(p.location, ^point),
|
||||
# limit: ^limit,
|
||||
# select: %{p | distance: st_distance_in_meters(p.location, ^point)}
|
||||
# end
|
||||
|
||||
# @doc """
|
||||
# Find recent packets with location data within the last N hours.
|
||||
# """
|
||||
# def recent_with_location(query \\ __MODULE__, hours_back \\ 24) do
|
||||
# cutoff_time = DateTime.utc_now() |> DateTime.add(-hours_back, :hour)
|
||||
|
||||
# from p in query,
|
||||
# where: p.has_position == true,
|
||||
# where: not is_nil(p.location),
|
||||
# where: p.received_at > ^cutoff_time,
|
||||
# order_by: [desc: p.received_at]
|
||||
# end
|
||||
|
||||
# @doc """
|
||||
# Get statistics for packets in a geographic area.
|
||||
# """
|
||||
# def location_stats(query \\ __MODULE__, lat, lon, radius_meters) do
|
||||
# point = %Geo.Point{coordinates: {lon, lat}, srid: 4326}
|
||||
|
||||
# from p in query,
|
||||
# where: st_dwithin_in_meters(p.location, ^point, ^radius_meters),
|
||||
# where: not is_nil(p.location),
|
||||
# group_by: p.base_callsign,
|
||||
# select: %{
|
||||
# callsign: p.base_callsign,
|
||||
# packet_count: count(p.id),
|
||||
# latest_position: max(p.received_at),
|
||||
# avg_lat: avg(fragment("ST_Y(?)", p.location)),
|
||||
# avg_lon: avg(fragment("ST_X(?)", p.location))
|
||||
# }
|
||||
# end
|
||||
|
||||
@doc """
|
||||
Create a geometry point from lat/lon coordinates.
|
||||
"""
|
||||
|
|
@ -732,19 +628,6 @@ defmodule Aprsme.Packet do
|
|||
def lon(%__MODULE__{location: %Geo.Point{coordinates: {lon, _lat}}}), do: lon
|
||||
def lon(_), do: nil
|
||||
|
||||
# @doc """
|
||||
# Calculate distance between two packets in meters.
|
||||
# """
|
||||
# def distance_between(%__MODULE__{location: %Geo.Point{} = p1}, %__MODULE__{location: %Geo.Point{} = p2}) do
|
||||
# Repo.one(
|
||||
# from p in "packets",
|
||||
# select: fragment("ST_Distance_Sphere(?, ?)", ^p1, ^p2),
|
||||
# limit: 1
|
||||
# )
|
||||
# end
|
||||
|
||||
# def distance_between(_, _), do: nil
|
||||
|
||||
# Clean comment by removing altitude and PHG data
|
||||
defp clean_comment(nil), do: nil
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
alias Aprsme.Cluster.PacketDistributor
|
||||
alias Aprsme.LogSanitizer
|
||||
alias Aprsme.Repo
|
||||
alias AprsmeWeb.Live.Shared.CoordinateUtils
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -449,16 +450,12 @@ defmodule Aprsme.PacketConsumer do
|
|||
|
||||
# Helper functions for coordinate validation and point creation
|
||||
defp valid_coordinates?(lat, lon) do
|
||||
lat = normalize_coordinate(lat)
|
||||
lon = normalize_coordinate(lon)
|
||||
|
||||
is_number(lat) && is_number(lon) &&
|
||||
lat >= -90 && lat <= 90 &&
|
||||
lon >= -180 && lon <= 180
|
||||
CoordinateUtils.valid_coordinates_any_type?(lat, lon)
|
||||
end
|
||||
|
||||
defp normalize_coordinate(%Decimal{} = decimal), do: Decimal.to_float(decimal)
|
||||
defp normalize_coordinate(coord), do: coord
|
||||
defp normalize_coordinate(coord) do
|
||||
CoordinateUtils.normalize_coordinate(coord)
|
||||
end
|
||||
|
||||
defp create_point(lat, lon)
|
||||
when (is_number(lat) or is_struct(lat, Decimal)) and (is_number(lon) or is_struct(lon, Decimal)) do
|
||||
|
|
@ -615,7 +612,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
|
||||
defp extract_position(packet_data) do
|
||||
if not is_nil(packet_data[:lat]) and not is_nil(packet_data[:lon]) do
|
||||
{to_float(packet_data.lat), to_float(packet_data.lon)}
|
||||
{Aprsme.EncodingUtils.to_float(packet_data.lat), Aprsme.EncodingUtils.to_float(packet_data.lon)}
|
||||
else
|
||||
extract_position_from_data_extended(packet_data[:data_extended])
|
||||
end
|
||||
|
|
@ -640,7 +637,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
defp has_standard_position?(_), do: false
|
||||
|
||||
defp extract_standard_position(data_extended) when is_map(data_extended) and not is_struct(data_extended) do
|
||||
{to_float(data_extended[:latitude]), to_float(data_extended[:longitude])}
|
||||
{Aprsme.EncodingUtils.to_float(data_extended[:latitude]), Aprsme.EncodingUtils.to_float(data_extended[:longitude])}
|
||||
end
|
||||
|
||||
defp extract_standard_position(_), do: {nil, nil}
|
||||
|
|
@ -648,7 +645,7 @@ defmodule Aprsme.PacketConsumer do
|
|||
defp extract_position_from_data_extended_case(data_extended) do
|
||||
lat = extract_lat_from_ext_map(data_extended)
|
||||
lon = extract_lon_from_ext_map(data_extended)
|
||||
{to_float(lat), to_float(lon)}
|
||||
{Aprsme.EncodingUtils.to_float(lat), Aprsme.EncodingUtils.to_float(lon)}
|
||||
end
|
||||
|
||||
defp extract_lat_from_ext_map(ext_map) do
|
||||
|
|
@ -691,8 +688,6 @@ defmodule Aprsme.PacketConsumer do
|
|||
|
||||
defp sanitize_packet_strings(value), do: Aprsme.EncodingUtils.sanitize_packet_strings(value)
|
||||
|
||||
defp to_float(value), do: Aprsme.EncodingUtils.to_float(value)
|
||||
|
||||
defp normalize_data_type(attrs), do: Aprsme.EncodingUtils.normalize_data_type(attrs)
|
||||
|
||||
defp struct_to_map(%{__struct__: ParseError} = error) do
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ defmodule Aprsme.Packets do
|
|||
defp to_decimal_safe(nil), do: {:error, :nil_value}
|
||||
|
||||
defp to_decimal_safe(value) do
|
||||
case to_decimal(value) do
|
||||
case Aprsme.EncodingUtils.to_decimal(value) do
|
||||
nil -> {:error, :conversion_failed}
|
||||
decimal -> {:ok, decimal}
|
||||
end
|
||||
|
|
@ -329,7 +329,7 @@ defmodule Aprsme.Packets do
|
|||
defp extract_position(packet_data) do
|
||||
# Check for lat/lon at top level
|
||||
if not is_nil(packet_data[:lat]) and not is_nil(packet_data[:lon]) do
|
||||
{to_float(packet_data.lat), to_float(packet_data.lon)}
|
||||
{Aprsme.EncodingUtils.to_float(packet_data.lat), Aprsme.EncodingUtils.to_float(packet_data.lon)}
|
||||
else
|
||||
extract_position_from_data_extended(packet_data[:data_extended])
|
||||
end
|
||||
|
|
@ -354,7 +354,7 @@ defmodule Aprsme.Packets do
|
|||
defp has_standard_position?(_), do: false
|
||||
|
||||
defp extract_standard_position(data_extended) when is_map(data_extended) and not is_struct(data_extended) do
|
||||
{to_float(data_extended[:latitude]), to_float(data_extended[:longitude])}
|
||||
{Aprsme.EncodingUtils.to_float(data_extended[:latitude]), Aprsme.EncodingUtils.to_float(data_extended[:longitude])}
|
||||
end
|
||||
|
||||
defp extract_standard_position(_), do: {nil, nil}
|
||||
|
|
@ -761,12 +761,6 @@ defmodule Aprsme.Packets do
|
|||
{:ok, deleted_count}
|
||||
end
|
||||
|
||||
# Helper to convert various types to float
|
||||
defp to_float(value), do: Aprsme.EncodingUtils.to_float(value)
|
||||
|
||||
# Helper to convert various types to Decimal
|
||||
defp to_decimal(value), do: Aprsme.EncodingUtils.to_decimal(value)
|
||||
|
||||
# Get packets from last hour only - used to initialize the map
|
||||
@spec get_last_hour_packets() :: [struct()]
|
||||
def get_last_hour_packets do
|
||||
|
|
@ -780,82 +774,6 @@ defmodule Aprsme.Packets do
|
|||
[]
|
||||
end
|
||||
|
||||
# @doc """
|
||||
# Spatial query functions for efficient location-based searches using PostGIS
|
||||
# """
|
||||
|
||||
# Temporarily commented out PostGIS functions until PostGIS is properly configured
|
||||
|
||||
# @doc """
|
||||
# Find packets within a given radius (in meters) of a point.
|
||||
# Uses PostGIS spatial indexes for efficient querying.
|
||||
# """
|
||||
# def find_packets_within_radius(lat, lon, radius_meters, opts \\ %{}) do
|
||||
# point = %Geo.Point{coordinates: {lon, lat}, srid: 4326}
|
||||
|
||||
# base_query =
|
||||
# Packet
|
||||
# |> where([p], not is_nil(p.location))
|
||||
# |> where([p], st_dwithin_in_meters(p.location, ^point, ^radius_meters))
|
||||
# |> order_by([p], st_distance(p.location, ^point))
|
||||
|
||||
# base_query
|
||||
# |> apply_common_filters(opts)
|
||||
# |> maybe_limit(opts)
|
||||
# |> Repo.all()
|
||||
# end
|
||||
|
||||
# Additional PostGIS functions commented out for now...
|
||||
|
||||
# Helper functions for spatial queries - commented out for now
|
||||
|
||||
# defp apply_common_filters(query, opts) do
|
||||
# query
|
||||
# |> filter_by_time_range(opts)
|
||||
# |> filter_by_callsign(opts)
|
||||
# |> filter_by_data_type(opts)
|
||||
# end
|
||||
|
||||
# defp filter_by_time_range(query, %{start_time: start_time, end_time: end_time}) do
|
||||
# from p in query,
|
||||
# where: p.received_at >= ^start_time and p.received_at <= ^end_time
|
||||
# end
|
||||
|
||||
# defp filter_by_time_range(query, %{start_time: start_time}) do
|
||||
# from p in query, where: p.received_at >= ^start_time
|
||||
# end
|
||||
|
||||
# defp filter_by_time_range(query, %{end_time: end_time}) do
|
||||
# from p in query, where: p.received_at <= ^end_time
|
||||
# end
|
||||
|
||||
# defp filter_by_time_range(query, %{hours_back: hours}) do
|
||||
# cutoff_time = DateTime.utc_now() |> DateTime.add(-hours, :hour)
|
||||
# from p in query, where: p.received_at >= ^cutoff_time
|
||||
# end
|
||||
|
||||
# defp filter_by_time_range(query, _), do: query
|
||||
|
||||
# defp filter_by_data_type(query, %{data_type: data_type}) do
|
||||
# from p in query, where: p.data_type == ^data_type
|
||||
# end
|
||||
|
||||
# defp filter_by_data_type(query, _), do: query
|
||||
|
||||
# defp maybe_limit(query, %{limit: limit}) when is_integer(limit) and limit > 0 do
|
||||
# from p in query, limit: ^limit
|
||||
# end
|
||||
|
||||
# defp maybe_limit(query, _), do: query
|
||||
|
||||
# Calculate clustering distance based on zoom level
|
||||
# Higher zoom levels need smaller clustering distances
|
||||
# defp calculate_cluster_distance(zoom_level) when zoom_level >= 15, do: 100 # 100m
|
||||
# defp calculate_cluster_distance(zoom_level) when zoom_level >= 12, do: 500 # 500m
|
||||
# defp calculate_cluster_distance(zoom_level) when zoom_level >= 9, do: 2000 # 2km
|
||||
# defp calculate_cluster_distance(zoom_level) when zoom_level >= 6, do: 10000 # 10km
|
||||
# defp calculate_cluster_distance(_), do: 50000 # 50km
|
||||
|
||||
@doc """
|
||||
Gets the most recent packet for a callsign regardless of type or age.
|
||||
This is used for API endpoints that need the latest packet from a source.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ defmodule AprsmeWeb.InfoController do
|
|||
use AprsmeWeb, :controller
|
||||
|
||||
alias Aprsme.Packets
|
||||
alias AprsmeWeb.MapLive.PacketUtils
|
||||
alias AprsmeWeb.Live.Shared.PacketUtils
|
||||
|
||||
@neighbor_radius_km 10
|
||||
@neighbor_limit 10
|
||||
|
|
|
|||
|
|
@ -143,10 +143,10 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
|
||||
defp position_changed?(current_packet, new_packet) do
|
||||
# Compare lat/lon to see if position actually changed
|
||||
current_lat = to_float_safe(current_packet.lat)
|
||||
current_lon = to_float_safe(current_packet.lon)
|
||||
new_lat = to_float_safe(new_packet.lat)
|
||||
new_lon = to_float_safe(new_packet.lon)
|
||||
current_lat = EncodingUtils.to_float(current_packet.lat)
|
||||
current_lon = EncodingUtils.to_float(current_packet.lon)
|
||||
new_lat = EncodingUtils.to_float(new_packet.lat)
|
||||
new_lon = EncodingUtils.to_float(new_packet.lon)
|
||||
|
||||
# If any coordinate is invalid, consider it a change
|
||||
case {current_lat, current_lon, new_lat, new_lon} do
|
||||
|
|
@ -274,10 +274,6 @@ defmodule AprsmeWeb.InfoLive.Show do
|
|||
r * c
|
||||
end
|
||||
|
||||
# Safe float conversion that preserves nil for invalid coordinates
|
||||
defp to_float_safe(value), do: EncodingUtils.to_float(value)
|
||||
|
||||
# Legacy float conversion that defaults to 0.0 for backward compatibility
|
||||
defp to_float(value), do: EncodingUtils.to_float(value) || 0.0
|
||||
|
||||
defp format_timestamp_for_display(packet) do
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
defmodule AprsmeWeb.MapLive.BoundsManager do
|
||||
@moduledoc """
|
||||
Handles map bounds calculations, validation, and filtering operations.
|
||||
"""
|
||||
|
||||
alias AprsmeWeb.Live.Shared.BoundsUtils
|
||||
alias AprsmeWeb.Live.Shared.PacketUtils
|
||||
|
||||
# Delegate to shared utilities
|
||||
defdelegate calculate_bounds_from_center_and_zoom(center, zoom), to: BoundsUtils
|
||||
defdelegate valid_bounds?(map_bounds), to: BoundsUtils
|
||||
defdelegate compare_bounds(b1, b2), to: BoundsUtils
|
||||
defdelegate within_bounds?(packet, bounds), to: BoundsUtils
|
||||
defdelegate filter_packets_by_bounds(packets, bounds), to: BoundsUtils
|
||||
defdelegate reject_packets_by_bounds(packets_map, bounds), to: BoundsUtils
|
||||
|
||||
@doc """
|
||||
Filter packets by both time threshold and bounds.
|
||||
"""
|
||||
@spec filter_packets_by_time_and_bounds(map(), map(), DateTime.t()) :: map()
|
||||
def filter_packets_by_time_and_bounds(packets, bounds, time_threshold) do
|
||||
# Use shared packet utils for this functionality
|
||||
PacketUtils.filter_packets_by_time_and_bounds(packets, bounds, time_threshold)
|
||||
end
|
||||
end
|
||||
|
|
@ -4,7 +4,7 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
"""
|
||||
|
||||
alias Aprsme.Packets.Clustering
|
||||
alias AprsmeWeb.MapLive.BoundsManager
|
||||
alias AprsmeWeb.Live.Shared.BoundsUtils
|
||||
alias AprsmeWeb.MapLive.DataBuilder
|
||||
alias Phoenix.LiveView
|
||||
alias Phoenix.LiveView.Socket
|
||||
|
|
@ -50,7 +50,7 @@ defmodule AprsmeWeb.MapLive.DisplayManager do
|
|||
# Filter by bounds
|
||||
filtered_packets =
|
||||
all_packets
|
||||
|> BoundsManager.filter_packets_by_bounds(socket.assigns.map_bounds)
|
||||
|> BoundsUtils.filter_packets_by_bounds(socket.assigns.map_bounds)
|
||||
|> Enum.uniq_by(fn packet ->
|
||||
Map.get(packet, :id) || Map.get(packet, "id")
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
alias AprsmeWeb.Live.Shared.CoordinateUtils
|
||||
alias AprsmeWeb.Live.Shared.PacketUtils, as: SharedPacketUtils
|
||||
alias AprsmeWeb.Live.Shared.ParamUtils
|
||||
alias AprsmeWeb.MapLive.BoundsManager
|
||||
alias AprsmeWeb.MapLive.DataBuilder
|
||||
alias AprsmeWeb.MapLive.DisplayManager
|
||||
alias AprsmeWeb.MapLive.HistoricalLoader
|
||||
|
|
@ -83,7 +82,7 @@ defmodule AprsmeWeb.MapLive.Index do
|
|||
)
|
||||
|
||||
# Calculate initial bounds
|
||||
initial_bounds = BoundsManager.calculate_bounds_from_center_and_zoom(final_map_center, final_map_zoom)
|
||||
initial_bounds = BoundsUtils.calculate_bounds_from_center_and_zoom(final_map_center, final_map_zoom)
|
||||
|
||||
# Final socket assignment
|
||||
{:ok,
|
||||
|
|
|
|||
|
|
@ -1,66 +1,22 @@
|
|||
defmodule AprsmeWeb.MapLive.PacketUtils do
|
||||
@moduledoc """
|
||||
Shared utilities for extracting and processing packet data in map views.
|
||||
Map-specific packet utilities. Provides weather-check queries against the
|
||||
database and a convenience wrapper for weather field display.
|
||||
|
||||
For general packet field access, symbol info, timestamps, callsign
|
||||
generation, and weather-packet detection, use
|
||||
`AprsmeWeb.Live.Shared.PacketUtils` directly.
|
||||
|
||||
For building marker/popup data, use `AprsmeWeb.MapLive.DataBuilder` directly.
|
||||
"""
|
||||
|
||||
alias AprsmeWeb.Live.Shared.PacketUtils, as: SharedPacketUtils
|
||||
alias AprsmeWeb.Live.Shared.ParamUtils
|
||||
alias AprsmeWeb.MapLive.DataBuilder
|
||||
|
||||
@doc """
|
||||
Safely extracts a value from a packet or data_extended map with fallback support.
|
||||
Checks both atom and string keys, and provides a default value.
|
||||
"""
|
||||
@spec get_packet_field(map(), atom() | String.t(), any()) :: any()
|
||||
def get_packet_field(packet, field, default \\ nil) do
|
||||
SharedPacketUtils.get_packet_field(packet, field, default)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extracts symbol information from a packet with fallbacks.
|
||||
"""
|
||||
@spec get_symbol_info(map()) :: {String.t(), String.t()}
|
||||
def get_symbol_info(packet) do
|
||||
SharedPacketUtils.get_symbol_info(packet)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Extracts timestamp from a packet in ISO8601 format.
|
||||
"""
|
||||
@spec get_timestamp(map()) :: String.t()
|
||||
def get_timestamp(packet) do
|
||||
SharedPacketUtils.get_timestamp(packet)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Converts various numeric types to float for consistent display.
|
||||
"""
|
||||
@spec to_float(any()) :: float()
|
||||
def to_float(value) do
|
||||
ParamUtils.to_float(value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a callsign string with SSID if present.
|
||||
"""
|
||||
@spec generate_callsign(map()) :: String.t()
|
||||
def generate_callsign(packet) do
|
||||
SharedPacketUtils.generate_callsign(packet)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Determines if a packet is a weather packet.
|
||||
"""
|
||||
@spec weather_packet?(map()) :: boolean()
|
||||
def weather_packet?(packet) do
|
||||
SharedPacketUtils.weather_packet?(packet)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a station has sent weather packets by looking at recent packets.
|
||||
"""
|
||||
@spec has_weather_packets?(String.t()) :: boolean()
|
||||
# Get recent packets for this callsign and check if any are weather packets
|
||||
def has_weather_packets?(callsign) when is_binary(callsign) do
|
||||
query = build_weather_check_query(callsign)
|
||||
Aprsme.Repo.exists?(query)
|
||||
|
|
@ -95,7 +51,7 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
|
|||
def convert_tuples_to_strings(other), do: other
|
||||
|
||||
@doc """
|
||||
Extracts weather field data with fallback support.
|
||||
Extracts weather field data with "N/A" fallback for display.
|
||||
"""
|
||||
@spec get_weather_field(map(), atom()) :: String.t()
|
||||
def get_weather_field(packet, key) do
|
||||
|
|
@ -105,13 +61,11 @@ defmodule AprsmeWeb.MapLive.PacketUtils do
|
|||
end
|
||||
end
|
||||
|
||||
# These functions have been moved to AprsmeWeb.MapLive.DataBuilder
|
||||
# Keep build_packet_data delegations for backward compatibility with tests
|
||||
defdelegate build_packet_data(packet, is_most_recent_for_callsign, locale), to: DataBuilder
|
||||
defdelegate build_packet_data(packet, is_most_recent_for_callsign), to: DataBuilder
|
||||
defdelegate build_packet_data(packet), to: DataBuilder
|
||||
|
||||
# All packet data building functions have been moved to AprsmeWeb.MapLive.DataBuilder
|
||||
|
||||
defp build_weather_check_query(callsign) do
|
||||
import Ecto.Query
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,27 @@ defmodule AprsmeWeb.Live.Shared.CoordinateUtils do
|
|||
|
||||
def valid_coordinates?(_, _), do: false
|
||||
|
||||
@doc """
|
||||
Validate coordinates that may be Decimals, floats, or integers.
|
||||
Normalizes Decimals to floats before checking range.
|
||||
"""
|
||||
@spec valid_coordinates_any_type?(any(), any()) :: boolean()
|
||||
def valid_coordinates_any_type?(lat, lon) do
|
||||
lat = normalize_coordinate(lat)
|
||||
lon = normalize_coordinate(lon)
|
||||
|
||||
is_number(lat) && is_number(lon) &&
|
||||
lat >= -90 && lat <= 90 &&
|
||||
lon >= -180 && lon <= 180
|
||||
end
|
||||
|
||||
@doc """
|
||||
Normalize a coordinate value, converting Decimals to floats.
|
||||
"""
|
||||
@spec normalize_coordinate(any()) :: number() | any()
|
||||
def normalize_coordinate(%Decimal{} = decimal), do: Decimal.to_float(decimal)
|
||||
def normalize_coordinate(coord), do: coord
|
||||
|
||||
@doc """
|
||||
Calculate distance between two lat/lon points in meters using Haversine formula.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -94,19 +94,7 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
|
|||
"""
|
||||
@spec has_weather_data?(map()) :: boolean()
|
||||
def has_weather_data?(packet) do
|
||||
weather_fields = [
|
||||
:temperature,
|
||||
:humidity,
|
||||
:pressure,
|
||||
:wind_speed,
|
||||
:wind_direction,
|
||||
:rain_1h,
|
||||
:rain_24h,
|
||||
:rain_since_midnight,
|
||||
:snow
|
||||
]
|
||||
|
||||
Enum.any?(weather_fields, fn field ->
|
||||
Enum.any?(Aprsme.EncodingUtils.weather_fields(), fn field ->
|
||||
value = get_packet_field(packet, field, nil)
|
||||
not is_nil(value)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
{@callsign}
|
||||
</span>
|
||||
<% {symbol_table_id, symbol_code} =
|
||||
AprsmeWeb.MapLive.PacketUtils.get_symbol_info(@weather_packet) %>
|
||||
AprsmeWeb.Live.Shared.PacketUtils.get_symbol_info(@weather_packet) %>
|
||||
<% symbol_table =
|
||||
if symbol_table_id in ["/", "\\", "]"], do: symbol_table_id, else: "/" %>
|
||||
<% symbol_code = (symbol_code || ">") |> to_string() %>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias AprsmeWeb.Live.Shared.PacketUtils, as: SharedPacketUtils
|
||||
alias AprsmeWeb.MapLive.PacketUtils
|
||||
|
||||
describe "weather popup generation" do
|
||||
|
|
@ -282,7 +283,7 @@ defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|||
temperature: 75
|
||||
}
|
||||
|
||||
assert PacketUtils.weather_packet?(weather_packet)
|
||||
assert SharedPacketUtils.weather_packet?(weather_packet)
|
||||
end
|
||||
|
||||
test "identifies weather packets with humidity" do
|
||||
|
|
@ -293,7 +294,7 @@ defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|||
humidity: 80
|
||||
}
|
||||
|
||||
assert PacketUtils.weather_packet?(weather_packet)
|
||||
assert SharedPacketUtils.weather_packet?(weather_packet)
|
||||
end
|
||||
|
||||
test "does not identify non-weather packets" do
|
||||
|
|
@ -303,7 +304,7 @@ defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|||
symbol_code: ">"
|
||||
}
|
||||
|
||||
refute PacketUtils.weather_packet?(regular_packet)
|
||||
refute SharedPacketUtils.weather_packet?(regular_packet)
|
||||
end
|
||||
|
||||
test "has_weather_packets? returns false for non-existent callsign" do
|
||||
|
|
@ -383,7 +384,7 @@ defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|||
ssid: "1"
|
||||
}
|
||||
|
||||
assert PacketUtils.generate_callsign(packet) == "TEST-1"
|
||||
assert SharedPacketUtils.generate_callsign(packet) == "TEST-1"
|
||||
end
|
||||
|
||||
test "generates callsign without SSID" do
|
||||
|
|
@ -392,7 +393,7 @@ defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|||
ssid: nil
|
||||
}
|
||||
|
||||
assert PacketUtils.generate_callsign(packet) == "TEST"
|
||||
assert SharedPacketUtils.generate_callsign(packet) == "TEST"
|
||||
end
|
||||
|
||||
test "generates callsign with empty SSID" do
|
||||
|
|
@ -401,7 +402,7 @@ defmodule AprsmeWeb.MapLive.PacketUtilsTest do
|
|||
ssid: ""
|
||||
}
|
||||
|
||||
assert PacketUtils.generate_callsign(packet) == "TEST"
|
||||
assert SharedPacketUtils.generate_callsign(packet) == "TEST"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue