- IS GenServer: add missing :failure_started_at to test build_state/1 map
- IS GenServer: fix stale status server assertion and reconnection test
- IS GenServer: set Logger level to :debug for dispatch parse-error tests
- PacketReplay: remove conflicting Registry start_supervised! from setup
- PacketReplay: update assertion from {:continue_replay} to :start_replay
- Doctests: fix float precision, stale sprite coords, quoting escapes
- API controllers: use string keys for JSON error/postion details
- PacketUtils: fix operator precedence (not is_nil(result).field)
- ThemeManager: update expected dark theme text color
- StatusLive: remove/update stale :loading assign assertions
- Movement: remove {:ok, _v} wrapper from render_hook/3 (returns HTML)
- AprsIsMock: update packet_stats assertion for populated default shape
55 lines
1.8 KiB
Elixir
55 lines
1.8 KiB
Elixir
defmodule Aprsme.GeoUtils do
|
|
@moduledoc """
|
|
Geographic utility functions for calculating distances and related operations.
|
|
"""
|
|
|
|
@earth_radius_meters 6_371_000
|
|
|
|
@doc """
|
|
Calculate the Haversine distance between two points in meters.
|
|
|
|
Returns `nil` when any coordinate is not a number.
|
|
|
|
## Examples
|
|
|
|
iex> Aprsme.GeoUtils.haversine_distance(33.16961, -96.4921, 33.16962, -96.4921) |> Float.round(2)
|
|
1.11
|
|
"""
|
|
@spec haversine_distance(number(), number(), number(), number()) :: float()
|
|
@spec haversine_distance(any(), any(), any(), any()) :: nil
|
|
def haversine_distance(lat1, lon1, lat2, lon2)
|
|
when is_number(lat1) and is_number(lon1) and is_number(lat2) and is_number(lon2) do
|
|
# Convert to radians
|
|
lat1_rad = lat1 * :math.pi() / 180
|
|
lat2_rad = lat2 * :math.pi() / 180
|
|
dlat_rad = (lat2 - lat1) * :math.pi() / 180
|
|
dlon_rad = (lon2 - lon1) * :math.pi() / 180
|
|
|
|
# Haversine formula
|
|
a =
|
|
:math.sin(dlat_rad / 2) * :math.sin(dlat_rad / 2) +
|
|
:math.cos(lat1_rad) * :math.cos(lat2_rad) *
|
|
:math.sin(dlon_rad / 2) * :math.sin(dlon_rad / 2)
|
|
|
|
c = 2 * :math.atan2(:math.sqrt(a), :math.sqrt(1 - a))
|
|
|
|
# Distance in meters
|
|
@earth_radius_meters * c
|
|
end
|
|
|
|
def haversine_distance(_, _, _, _), do: nil
|
|
|
|
@doc """
|
|
Check if the distance between two points exceeds a minimum threshold.
|
|
This helps filter out GPS drift and insignificant movements.
|
|
|
|
Default threshold is 50 meters to account for typical GPS accuracy variations.
|
|
"""
|
|
@spec significant_movement?(any(), any(), any(), any(), number()) :: boolean()
|
|
def significant_movement?(lat1, lon1, lat2, lon2, threshold_meters \\ 50) do
|
|
case haversine_distance(lat1, lon1, lat2, lon2) do
|
|
nil -> false
|
|
distance -> distance > threshold_meters
|
|
end
|
|
end
|
|
end
|