diff --git a/lib/aprs/packets.ex b/lib/aprs/packets.ex index 56eb43c..d88e611 100644 --- a/lib/aprs/packets.ex +++ b/lib/aprs/packets.ex @@ -24,126 +24,13 @@ defmodule Aprs.Packets do require Logger try do - # Convert to map if it's a struct, or use as is if already a map - packet_attrs = - case packet_data do - %Packet{} = packet -> - packet - |> Map.from_struct() - |> Map.delete(:__meta__) - - %{} -> - packet_data - end - - # Sanitize all string fields to prevent UTF-8 encoding errors - packet_attrs = sanitize_packet_strings(packet_attrs) - - # Convert data_type to string if it's an atom - packet_attrs = - case packet_attrs do - %{data_type: data_type} when is_atom(data_type) -> - Map.put(packet_attrs, :data_type, to_string(data_type)) - - _ -> - packet_attrs - end - - # Make sure received_at is set with explicit UTC DateTime - current_time = DateTime.truncate(DateTime.utc_now(), :microsecond) - packet_attrs = Map.put(packet_attrs, :received_at, current_time) - - # Patch: If data_extended has latitude/longitude, set them as lat/lon as Decimal - packet_attrs = - case packet_attrs[:data_extended] do - %{} = ext -> - # Convert struct to map if needed - ext_map = if Map.has_key?(ext, :__struct__), do: Map.from_struct(ext), else: ext - - lat = - ext_map[:latitude] || ext_map["latitude"] || - (Map.has_key?(ext_map, :position) && - (ext_map[:position][:latitude] || ext_map[:position]["latitude"])) || - (Map.has_key?(ext_map, "position") && - (ext_map["position"][:latitude] || ext_map["position"]["latitude"])) - - lon = - ext_map[:longitude] || ext_map["longitude"] || - (Map.has_key?(ext_map, :position) && - (ext_map[:position][:longitude] || ext_map[:position]["longitude"])) || - (Map.has_key?(ext_map, "position") && - (ext_map["position"][:longitude] || ext_map["position"]["longitude"])) - - latd = to_decimal(lat) - lond = to_decimal(lon) - - if not is_nil(latd) and not is_nil(lond) do - packet_attrs - |> Map.put(:lat, latd) - |> Map.put(:lon, lond) - else - packet_attrs - end - - _ -> - packet_attrs - end - - # Extract position data with error handling + packet_attrs = normalize_packet_attrs(packet_data) + packet_attrs = set_received_at(packet_attrs) + packet_attrs = patch_lat_lon_from_data_extended(packet_attrs) {lat, lon} = extract_position(packet_attrs) - - # Helper to round to 6 decimal places - round6 = fn - nil -> - nil - - %Decimal{} = d -> - Decimal.round(d, 6) - - n when is_float(n) -> - Float.round(n, 6) - - n when is_integer(n) -> - n * 1.0 - - n when is_binary(n) -> - case Float.parse(n) do - {f, _} -> Float.round(f, 6) - :error -> nil - end - - _ -> - nil - end - - # Set position fields if found (this will overwrite above if extract_position finds something different) - packet_attrs = - packet_attrs - |> Map.put(:lat, round6.(lat)) - |> Map.put(:lon, round6.(lon)) - - # Normalize ssid to string if present and not nil - packet_attrs = - case Map.get(packet_attrs, :ssid) do - nil -> packet_attrs - ssid -> Map.put(packet_attrs, :ssid, to_string(ssid)) - end - - # Insert the packet - case %Packet{} - |> Packet.changeset(packet_attrs) - |> Repo.insert() do - {:ok, packet} -> - {:ok, packet} - - {:error, changeset} -> - # Store validation errors as bad packets - error_message = - Enum.map_join(changeset.errors, ", ", fn {field, {msg, _}} -> "#{field}: #{msg}" end) - - store_bad_packet(packet_data, %{message: error_message, type: "ValidationError"}) - {:error, :validation_error} - end + packet_attrs = set_lat_lon(packet_attrs, lat, lon) + packet_attrs = normalize_ssid(packet_attrs) + insert_packet(packet_attrs, packet_data) rescue error -> Logger.error("Exception in store_packet for #{inspect(packet_data[:sender])}: #{inspect(error)}") @@ -153,6 +40,126 @@ defmodule Aprs.Packets do end end + defp normalize_packet_attrs(packet_data) do + case_result = + case packet_data do + %Packet{} = packet -> + packet + |> Map.from_struct() + |> Map.delete(:__meta__) + + %{} -> + packet_data + end + + case_result + |> sanitize_packet_strings() + |> normalize_data_type() + end + + defp normalize_data_type(attrs) do + case attrs do + %{data_type: data_type} when is_atom(data_type) -> + Map.put(attrs, :data_type, to_string(data_type)) + + _ -> + attrs + end + end + + defp set_received_at(attrs) do + current_time = DateTime.truncate(DateTime.utc_now(), :microsecond) + Map.put(attrs, :received_at, current_time) + end + + defp patch_lat_lon_from_data_extended(attrs) do + case attrs[:data_extended] do + %{} = ext -> + ext_map = if Map.has_key?(ext, :__struct__), do: Map.from_struct(ext), else: ext + lat = extract_lat_from_ext_map(ext_map) + lon = extract_lon_from_ext_map(ext_map) + latd = to_decimal(lat) + lond = to_decimal(lon) + + if not is_nil(latd) and not is_nil(lond) do + attrs + |> Map.put(:lat, latd) + |> Map.put(:lon, lond) + else + attrs + end + + _ -> + attrs + end + end + + defp extract_lat_from_ext_map(ext_map) do + ext_map[:latitude] || ext_map["latitude"] || + (Map.has_key?(ext_map, :position) && + (ext_map[:position][:latitude] || ext_map[:position]["latitude"])) || + (Map.has_key?(ext_map, "position") && + (ext_map["position"][:latitude] || ext_map["position"]["latitude"])) + end + + defp extract_lon_from_ext_map(ext_map) do + ext_map[:longitude] || ext_map["longitude"] || + (Map.has_key?(ext_map, :position) && + (ext_map[:position][:longitude] || ext_map[:position]["longitude"])) || + (Map.has_key?(ext_map, "position") && + (ext_map["position"][:longitude] || ext_map["position"]["longitude"])) + end + + defp set_lat_lon(attrs, lat, lon) do + round6 = fn + nil -> + nil + + %Decimal{} = d -> + Decimal.round(d, 6) + + n when is_float(n) -> + Float.round(n, 6) + + n when is_integer(n) -> + n * 1.0 + + n when is_binary(n) -> + case Float.parse(n) do + {f, _} -> Float.round(f, 6) + :error -> nil + end + + _ -> + nil + end + + attrs + |> Map.put(:lat, round6.(lat)) + |> Map.put(:lon, round6.(lon)) + end + + defp normalize_ssid(attrs) do + case Map.get(attrs, :ssid) do + nil -> attrs + ssid -> Map.put(attrs, :ssid, to_string(ssid)) + end + end + + defp insert_packet(attrs, packet_data) do + case %Packet{} |> Packet.changeset(attrs) |> Repo.insert() do + {:ok, packet} -> + {:ok, packet} + + {:error, changeset} -> + error_message = + Enum.map_join(changeset.errors, ", ", fn {field, {msg, _}} -> "#{field}: #{msg}" end) + + store_bad_packet(packet_data, %{message: error_message, type: "ValidationError"}) + {:error, :validation_error} + end + end + @doc """ Stores a bad packet in the database. @@ -200,46 +207,58 @@ defmodule Aprs.Packets do defp extract_position_from_data_extended(nil), do: {nil, nil} defp extract_position_from_data_extended(data_extended) when is_map(data_extended) do - # Standard position format - if not is_nil(data_extended[:latitude]) and not is_nil(data_extended[:longitude]) do - {to_float(data_extended[:latitude]), to_float(data_extended[:longitude])} - # MicE packet format with components + if has_standard_position?(data_extended) do + extract_standard_position(data_extended) else - case data_extended do - %{__struct__: MicE} = mic_e -> - lat = mic_e[:latitude] - lon = mic_e[:longitude] - - if is_number(lat) and is_number(lon) do - {lat, lon} - else - {nil, nil} - end - - _ -> - extract_position_from_mic_e(data_extended) - end + extract_position_from_data_extended_case(data_extended) end end defp extract_position_from_data_extended(_), do: {nil, nil} - defp extract_position_from_mic_e(%{__struct__: MicE} = mic_e) do - if is_number(mic_e.lat_degrees) and is_number(mic_e.lat_minutes) and - is_number(mic_e.lon_degrees) and is_number(mic_e.lon_minutes) do - lat = mic_e.lat_degrees + mic_e.lat_minutes / 60.0 - lat = if mic_e.lat_direction == :south, do: -lat, else: lat + defp has_standard_position?(data_extended) do + not is_nil(data_extended[:latitude]) and not is_nil(data_extended[:longitude]) + end - lon = mic_e.lon_degrees + mic_e.lon_minutes / 60.0 - lon = if mic_e.lon_direction == :west, do: -lon, else: lon + defp extract_standard_position(data_extended) do + {to_float(data_extended[:latitude]), to_float(data_extended[:longitude])} + end + defp extract_position_from_data_extended_case(data_extended) do + case data_extended do + %{__struct__: MicE} = mic_e -> + extract_position_from_mic_e_struct(mic_e) + + _ -> + extract_position_from_mic_e(data_extended) + end + end + + defp extract_position_from_mic_e_struct(mic_e) do + lat = mic_e[:latitude] + lon = mic_e[:longitude] + + if is_number(lat) and is_number(lon) do {lat, lon} else {nil, nil} end end - defp extract_position_from_mic_e(_), do: {nil, nil} + defp extract_position_from_mic_e(data_extended) do + if is_number(data_extended[:lat_degrees]) and is_number(data_extended[:lat_minutes]) and + is_number(data_extended[:lon_degrees]) and is_number(data_extended[:lon_minutes]) do + lat = data_extended[:lat_degrees] + data_extended[:lat_minutes] / 60.0 + lat = if data_extended[:lat_direction] == :south, do: -lat, else: lat + + lon = data_extended[:lon_degrees] + data_extended[:lon_minutes] / 60.0 + lon = if data_extended[:lon_direction] == :west, do: -lon, else: lon + + {lat, lon} + else + {nil, nil} + end + end @doc """ Gets packets for replay. diff --git a/lib/aprs_web/live/map_live/index.ex b/lib/aprs_web/live/map_live/index.ex index 5193422..a1f6df2 100644 --- a/lib/aprs_web/live/map_live/index.ex +++ b/lib/aprs_web/live/map_live/index.ex @@ -1049,119 +1049,107 @@ defmodule AprsWeb.MapLive.Index do # Helper function to start historical replay @spec start_historical_replay(Socket.t()) :: Socket.t() defp start_historical_replay(socket) do - # Only fetch historical packets if map is ready and bounds are available if socket.assigns.map_ready and socket.assigns.map_bounds do - # Get time range for historical data - now = DateTime.utc_now() - # Use the user's selected historical hours setting - historical_hours = String.to_integer(socket.assigns.historical_hours) - start_time = DateTime.add(now, -historical_hours * 3600, :second) - - # Convert map bounds to the format expected by the database query - bounds = [ - socket.assigns.map_bounds.west, - socket.assigns.map_bounds.south, - socket.assigns.map_bounds.east, - socket.assigns.map_bounds.north - ] - - # Fetch historical packets with position data within the current map bounds - historical_packets = fetch_historical_packets(bounds, start_time, now) - - if Enum.empty?(historical_packets) do - # No historical packets found - silently continue - socket - else - # Clear any previous historical packets from the map - socket = push_event(socket, "clear_historical_packets", %{}) - - # Group packets by callsign-ssid and process all positions - packet_data_list = - historical_packets - |> Enum.group_by(&generate_callsign/1) - |> Enum.flat_map(fn {callsign, packets} -> - # Sort packets by inserted_at to identify the most recent - # Convert NaiveDateTime to DateTime if needed for proper comparison - sorted_packets = - Enum.sort_by( - packets, - fn packet -> - case packet.inserted_at do - %NaiveDateTime{} = naive_dt -> - DateTime.from_naive!(naive_dt, "Etc/UTC") - - %DateTime{} = dt -> - dt - - _other -> - DateTime.utc_now() - end - end, - {:desc, DateTime} - ) - - # Filter out packets with unchanged positions (only keep if lat/lon changed) - unique_position_packets = filter_unique_positions(sorted_packets) - - # Process all packets with unique positions, marking which is the most recent - unique_position_packets - |> Enum.with_index() - |> Enum.map(fn {packet, index} -> - case build_packet_data(packet) do - nil -> - nil - - packet_data -> - # Generate a unique ID for this historical packet - packet_id = - "hist_#{if Map.has_key?(packet, :id), do: packet.id, else: System.unique_integer([:positive])}_#{index}" - - is_most_recent = index == 0 - - packet_data - |> Map.put("id", packet_id) - |> Map.put("is_historical", true) - |> Map.put("is_most_recent_for_callsign", is_most_recent) - |> Map.put("callsign_group", callsign) - |> Map.put( - "timestamp", - case packet.inserted_at do - %NaiveDateTime{} = naive_dt -> - DateTime.to_unix(DateTime.from_naive!(naive_dt, "Etc/UTC"), :millisecond) - - %DateTime{} = dt -> - DateTime.to_unix(dt, :millisecond) - - _other -> - DateTime.to_unix(DateTime.utc_now(), :millisecond) - end - ) - end - end) - |> Enum.filter(& &1) - end) - - # Send all historical packets at once - socket = push_event(socket, "add_historical_packets", %{packets: packet_data_list}) - - # Store historical packets in assigns for reference - historical_packets_map = - packet_data_list - |> Enum.zip(historical_packets) - |> Enum.reduce(%{}, fn {packet_data, packet}, acc -> - Map.put(acc, packet_data["id"], packet) - end) - - assign(socket, - historical_packets: historical_packets_map, - historical_loaded: true - ) - end + start_historical_replay_with_bounds(socket) else socket end end + defp start_historical_replay_with_bounds(socket) do + now = DateTime.utc_now() + historical_hours = String.to_integer(socket.assigns.historical_hours) + start_time = DateTime.add(now, -historical_hours * 3600, :second) + + bounds = [ + socket.assigns.map_bounds.west, + socket.assigns.map_bounds.south, + socket.assigns.map_bounds.east, + socket.assigns.map_bounds.north + ] + + historical_packets = fetch_historical_packets(bounds, start_time, now) + + if Enum.empty?(historical_packets) do + socket + else + process_historical_packets(socket, historical_packets) + end + end + + defp process_historical_packets(socket, historical_packets) do + socket = push_event(socket, "clear_historical_packets", %{}) + + packet_data_list = + historical_packets + |> Enum.group_by(&generate_callsign/1) + |> Enum.flat_map(fn {callsign, packets} -> + sorted_packets = + Enum.sort_by( + packets, + fn packet -> + case packet.inserted_at do + %NaiveDateTime{} = naive_dt -> DateTime.from_naive!(naive_dt, "Etc/UTC") + %DateTime{} = dt -> dt + _other -> DateTime.utc_now() + end + end, + {:desc, DateTime} + ) + + unique_position_packets = filter_unique_positions(sorted_packets) + + unique_position_packets + |> Enum.with_index() + |> Enum.map(fn {packet, index} -> + case build_packet_data(packet) do + nil -> + nil + + packet_data -> + packet_id = + "hist_#{if Map.has_key?(packet, :id), do: packet.id, else: System.unique_integer([:positive])}_#{index}" + + is_most_recent = index == 0 + + packet_data + |> Map.put("id", packet_id) + |> Map.put("is_historical", true) + |> Map.put("is_most_recent_for_callsign", is_most_recent) + |> Map.put("callsign_group", callsign) + |> Map.put( + "timestamp", + case packet.inserted_at do + %NaiveDateTime{} = naive_dt -> + DateTime.to_unix(DateTime.from_naive!(naive_dt, "Etc/UTC"), :millisecond) + + %DateTime{} = dt -> + DateTime.to_unix(dt, :millisecond) + + _other -> + DateTime.to_unix(DateTime.utc_now(), :millisecond) + end + ) + end + end) + |> Enum.filter(& &1) + end) + + socket = push_event(socket, "add_historical_packets", %{packets: packet_data_list}) + + historical_packets_map = + packet_data_list + |> Enum.zip(historical_packets) + |> Enum.reduce(%{}, fn {packet_data, packet}, acc -> + Map.put(acc, packet_data["id"], packet) + end) + + assign(socket, + historical_packets: historical_packets_map, + historical_loaded: true + ) + end + # Filter packets to only include those with unique positions (lat/lon changed) @spec filter_unique_positions([struct()]) :: [struct()] defp filter_unique_positions(packets) do @@ -1223,13 +1211,10 @@ defmodule AprsWeb.MapLive.Index do @spec load_historical_packets_for_bounds(Socket.t(), map()) :: Socket.t() defp load_historical_packets_for_bounds(socket, map_bounds) do - # Get time range for historical data now = DateTime.utc_now() - # Use the user's selected historical hours setting historical_hours = String.to_integer(socket.assigns.historical_hours) start_time = DateTime.add(now, -historical_hours * 3600, :second) - # Convert map bounds to the format expected by the database query bounds = [ map_bounds.west, map_bounds.south, @@ -1237,91 +1222,81 @@ defmodule AprsWeb.MapLive.Index do map_bounds.north ] - # Fetch historical packets with position data within the current map bounds historical_packets = fetch_historical_packets(bounds, start_time, now) if Enum.empty?(historical_packets) do - # No historical packets found socket else - # Group packets by callsign-ssid and process all positions - packet_data_list = - historical_packets - |> Enum.group_by(&generate_callsign/1) - |> Enum.flat_map(fn {callsign, packets} -> - # Sort packets by inserted_at to identify the most recent - sorted_packets = - Enum.sort_by( - packets, - fn packet -> + process_historical_packets_for_bounds(socket, historical_packets) + end + end + + defp process_historical_packets_for_bounds(socket, historical_packets) do + packet_data_list = + historical_packets + |> Enum.group_by(&generate_callsign/1) + |> Enum.flat_map(fn {callsign, packets} -> + sorted_packets = + Enum.sort_by( + packets, + fn packet -> + case packet.inserted_at do + %NaiveDateTime{} = naive_dt -> DateTime.from_naive!(naive_dt, "Etc/UTC") + %DateTime{} = dt -> dt + _other -> DateTime.utc_now() + end + end, + {:desc, DateTime} + ) + + unique_position_packets = filter_unique_positions(sorted_packets) + + unique_position_packets + |> Enum.with_index() + |> Enum.map(fn {packet, index} -> + case build_packet_data(packet) do + nil -> + nil + + packet_data -> + packet_id = + "hist_#{if Map.has_key?(packet, :id), do: packet.id, else: System.unique_integer([:positive])}_#{index}" + + is_most_recent = index == 0 + + packet_data + |> Map.put("id", packet_id) + |> Map.put("is_historical", true) + |> Map.put("is_most_recent_for_callsign", is_most_recent) + |> Map.put("callsign_group", callsign) + |> Map.put( + "timestamp", case packet.inserted_at do %NaiveDateTime{} = naive_dt -> - DateTime.from_naive!(naive_dt, "Etc/UTC") + DateTime.to_unix(DateTime.from_naive!(naive_dt, "Etc/UTC"), :millisecond) %DateTime{} = dt -> - dt + DateTime.to_unix(dt, :millisecond) _other -> - DateTime.utc_now() + DateTime.to_unix(DateTime.utc_now(), :millisecond) end - end, - {:desc, DateTime} - ) - - # Filter out packets with unchanged positions - unique_position_packets = filter_unique_positions(sorted_packets) - - # Process all packets with unique positions, marking which is the most recent - unique_position_packets - |> Enum.with_index() - |> Enum.map(fn {packet, index} -> - case build_packet_data(packet) do - nil -> - nil - - packet_data -> - # Generate a unique ID for this historical packet - packet_id = - "hist_#{if Map.has_key?(packet, :id), do: packet.id, else: System.unique_integer([:positive])}_#{index}" - - is_most_recent = index == 0 - - packet_data - |> Map.put("id", packet_id) - |> Map.put("is_historical", true) - |> Map.put("is_most_recent_for_callsign", is_most_recent) - |> Map.put("callsign_group", callsign) - |> Map.put( - "timestamp", - case packet.inserted_at do - %NaiveDateTime{} = naive_dt -> - DateTime.to_unix(DateTime.from_naive!(naive_dt, "Etc/UTC"), :millisecond) - - %DateTime{} = dt -> - DateTime.to_unix(dt, :millisecond) - - _other -> - DateTime.to_unix(DateTime.utc_now(), :millisecond) - end - ) - end - end) - |> Enum.filter(& &1) + ) + end end) + |> Enum.filter(& &1) + end) - # Send all historical packets at once - socket = push_event(socket, "add_historical_packets", %{packets: packet_data_list}) + socket = push_event(socket, "add_historical_packets", %{packets: packet_data_list}) - # Store historical packets in assigns for reference - historical_packets_map = - packet_data_list - |> Enum.zip(historical_packets) - |> Enum.reduce(%{}, fn {packet_data, packet}, acc -> - Map.put(acc, packet_data["id"], packet) - end) + historical_packets_map = + packet_data_list + |> Enum.zip(historical_packets) + |> Enum.reduce(%{}, fn {packet_data, packet}, acc -> + Map.put(acc, packet_data["id"], packet) + end) - assign(socket, historical_packets: historical_packets_map) - end + assign(socket, historical_packets: historical_packets_map) end @spec within_bounds?(map() | struct(), map()) :: boolean() @@ -1377,7 +1352,7 @@ defmodule AprsWeb.MapLive.Index do timestamp: PacketUtils.get_timestamp(packet), comment: PacketUtils.get_packet_field(packet, :comment, ""), safe_data_extended: PacketUtils.convert_tuples_to_strings(data_extended), - is_weather_packet: PacketUtils.is_weather_packet?(packet) + is_weather_packet: PacketUtils.weather_packet?(packet) } end diff --git a/lib/aprs_web/live/map_live/map_helpers.ex b/lib/aprs_web/live/map_live/map_helpers.ex index 29b3bdc..d90e42d 100644 --- a/lib/aprs_web/live/map_live/map_helpers.ex +++ b/lib/aprs_web/live/map_live/map_helpers.ex @@ -52,61 +52,20 @@ defmodule AprsWeb.MapLive.MapHelpers do @spec within_bounds?(map() | tuple(), map()) :: boolean() def within_bounds?(packet_or_coords, bounds) do - # Handle nil bounds if is_nil(bounds) do false else - to_float = fn - n when is_float(n) -> - n - - n when is_integer(n) -> - n * 1.0 - - %Decimal{} = d -> - Decimal.to_float(d) - - n when is_binary(n) -> - case Float.parse(n) do - {f, _} -> f - :error -> 0.0 - end - - _ -> - 0.0 - end - - {lat, lon} = - cond do - is_map(packet_or_coords) and Map.has_key?(packet_or_coords, :lat) and - Map.has_key?(packet_or_coords, :lon) -> - {packet_or_coords.lat, packet_or_coords.lon} - - is_map(packet_or_coords) and Map.has_key?(packet_or_coords, "lat") and - Map.has_key?(packet_or_coords, "lon") -> - {packet_or_coords["lat"], packet_or_coords["lon"]} - - is_map(packet_or_coords) and Map.has_key?(packet_or_coords, :latitude) and - Map.has_key?(packet_or_coords, :longitude) -> - {packet_or_coords.latitude, packet_or_coords.longitude} - - is_tuple(packet_or_coords) and tuple_size(packet_or_coords) == 2 -> - packet_or_coords - - true -> - {nil, nil} - end + {lat, lon} = extract_lat_lon(packet_or_coords) if is_nil(lat) or is_nil(lon) do false else - lat = to_float.(lat) - lon = to_float.(lon) - south = to_float.(bounds.south) - north = to_float.(bounds.north) - west = to_float.(bounds.west) - east = to_float.(bounds.east) - + lat = to_float(lat) + lon = to_float(lon) + south = to_float(bounds.south) + north = to_float(bounds.north) + west = to_float(bounds.west) + east = to_float(bounds.east) lat_in_bounds = lat >= south && lat <= north lng_in_bounds = @@ -120,4 +79,43 @@ defmodule AprsWeb.MapLive.MapHelpers do end end end + + defp extract_lat_lon(packet_or_coords) do + cond do + is_map(packet_or_coords) and Map.has_key?(packet_or_coords, :lat) and + Map.has_key?(packet_or_coords, :lon) -> + extract_lat_lon_atom(packet_or_coords) + + is_map(packet_or_coords) and Map.has_key?(packet_or_coords, "lat") and + Map.has_key?(packet_or_coords, "lon") -> + extract_lat_lon_string(packet_or_coords) + + is_map(packet_or_coords) and Map.has_key?(packet_or_coords, :latitude) and + Map.has_key?(packet_or_coords, :longitude) -> + extract_lat_lon_atom_alt(packet_or_coords) + + is_tuple(packet_or_coords) and tuple_size(packet_or_coords) == 2 -> + packet_or_coords + + true -> + {nil, nil} + end + end + + defp extract_lat_lon_atom(packet), do: {packet.lat, packet.lon} + defp extract_lat_lon_string(packet), do: {packet["lat"], packet["lon"]} + defp extract_lat_lon_atom_alt(packet), do: {packet.latitude, packet.longitude} + + defp to_float(n) when is_float(n), do: n + defp to_float(n) when is_integer(n), do: n * 1.0 + defp to_float(%Decimal{} = d), do: Decimal.to_float(d) + + defp to_float(n) when is_binary(n) do + case Float.parse(n) do + {f, _} -> f + :error -> 0.0 + end + end + + defp to_float(_), do: 0.0 end diff --git a/lib/aprs_web/live/map_live/packet_utils.ex b/lib/aprs_web/live/map_live/packet_utils.ex index 040c472..bee56e7 100644 --- a/lib/aprs_web/live/map_live/packet_utils.ex +++ b/lib/aprs_web/live/map_live/packet_utils.ex @@ -90,8 +90,8 @@ defmodule AprsWeb.MapLive.PacketUtils do @doc """ Determines if a packet is a weather packet. """ - @spec is_weather_packet?(map()) :: boolean() - def is_weather_packet?(packet) do + @spec weather_packet?(map()) :: boolean() + def weather_packet?(packet) do data_type = get_packet_field(packet, :data_type, "") {symbol_table_id, symbol_code} = get_symbol_info(packet) diff --git a/lib/aprs_web/live/user_forgot_password_live.ex b/lib/aprs_web/live/user_forgot_password_live.ex index 5c26e48..7705ee7 100644 --- a/lib/aprs_web/live/user_forgot_password_live.ex +++ b/lib/aprs_web/live/user_forgot_password_live.ex @@ -9,10 +9,8 @@ defmodule AprsWeb.UserForgotPasswordLive do
We'll send a password reset link to your inbox
<.simple_form :let={f} id="reset_password_form" for={%{}} as={:user} phx-submit="send_email"> <.input field={{f, :email}} type="email" placeholder="Email" required /> diff --git a/lib/aprs_web/live/user_login_live.ex b/lib/aprs_web/live/user_login_live.ex index 2f4ae18..64d0efa 100644 --- a/lib/aprs_web/live/user_login_live.ex +++ b/lib/aprs_web/live/user_login_live.ex @@ -10,16 +10,14 @@ defmodule AprsWeb.UserLoginLive do+ Don't have an account? + <.link navigate={~p"/users/register"} class="font-semibold text-brand hover:underline"> + Sign up + + for an account now. +
<.simple_form :let={f} diff --git a/lib/aprs_web/live/user_registration_live.ex b/lib/aprs_web/live/user_registration_live.ex index 78209ec..3729a39 100644 --- a/lib/aprs_web/live/user_registration_live.ex +++ b/lib/aprs_web/live/user_registration_live.ex @@ -10,16 +10,14 @@ defmodule AprsWeb.UserRegistrationLive do+ Already registered? + <.link navigate={~p"/users/log_in"} class="font-semibold text-brand hover:underline"> + Sign in + + to your account now. +
<.simple_form :let={f} diff --git a/lib/aprs_web/live/weather_live/callsign_view.ex b/lib/aprs_web/live/weather_live/callsign_view.ex index acdcb16..bfab6ac 100644 --- a/lib/aprs_web/live/weather_live/callsign_view.ex +++ b/lib/aprs_web/live/weather_live/callsign_view.ex @@ -22,6 +22,6 @@ defmodule AprsWeb.WeatherLive.CallsignView do # Get the most recent packet for this callsign that is a weather report %{callsign: callsign, limit: 10} |> Packets.get_recent_packets() - |> Enum.find(&PacketUtils.is_weather_packet?/1) + |> Enum.find(&PacketUtils.weather_packet?/1) end end diff --git a/lib/parser.ex b/lib/parser.ex index b6923a5..4c8b78d 100644 --- a/lib/parser.ex +++ b/lib/parser.ex @@ -226,7 +226,6 @@ defmodule Parser do end def parse_data(:timestamped_position_with_message, _destination, data) do - # Correct binary pattern match for APRS position with weather case data do <