Aliases: add module aliases for 9 nested module references Apply: replace apply/3 with direct module attribute calls Line length: break 1 long spec line Refactoring: extract helpers to reduce complexity and nesting in show.ex, radio.ex, weather workers, terrain, duct detection, backfill dashboard, contact map, and mix tasks
54 lines
1.9 KiB
Elixir
54 lines
1.9 KiB
Elixir
defmodule Microwaveprop.Geo do
|
|
@moduledoc "Geodetic helper functions: distances, bearings, coordinate math."
|
|
|
|
alias Microwaveprop.Radio.Maidenhead
|
|
|
|
@earth_radius_km 6371.0
|
|
|
|
@doc "Great-circle distance between two points in km."
|
|
@spec haversine_km(number(), number(), number(), number()) :: float()
|
|
def haversine_km(lat1, lon1, lat2, lon2) do
|
|
dlat = deg_to_rad(lat2 - lat1)
|
|
dlon = deg_to_rad(lon2 - lon1)
|
|
|
|
a =
|
|
:math.sin(dlat / 2) ** 2 +
|
|
:math.cos(deg_to_rad(lat1)) * :math.cos(deg_to_rad(lat2)) * :math.sin(dlon / 2) ** 2
|
|
|
|
2 * @earth_radius_km * :math.asin(:math.sqrt(a))
|
|
end
|
|
|
|
@doc "Initial bearing from point 1 to point 2 in degrees (0-360)."
|
|
@spec bearing_deg(number(), number(), number(), number()) :: float()
|
|
def bearing_deg(lat1, lon1, lat2, lon2) do
|
|
lat1r = deg_to_rad(lat1)
|
|
lat2r = deg_to_rad(lat2)
|
|
dlonr = deg_to_rad(lon2 - lon1)
|
|
y = :math.sin(dlonr) * :math.cos(lat2r)
|
|
x = :math.cos(lat1r) * :math.sin(lat2r) - :math.sin(lat1r) * :math.cos(lat2r) * :math.cos(dlonr)
|
|
b = :math.atan2(y, x) * 180 / :math.pi()
|
|
Float.round(:math.fmod(b + 360, 360), 1)
|
|
end
|
|
|
|
@doc "Center of a 4-character Maidenhead grid square."
|
|
@spec maidenhead_center(String.t()) :: {float(), float()} | nil
|
|
def maidenhead_center(grid) when is_binary(grid) and byte_size(grid) >= 4 do
|
|
case Maidenhead.to_latlon(grid) do
|
|
{:ok, {lat, lon}} -> {lat, lon}
|
|
:error -> nil
|
|
end
|
|
end
|
|
|
|
@doc "4-character Maidenhead grid for a lat/lon."
|
|
@spec latlon_to_grid4(number(), number()) :: String.t()
|
|
def latlon_to_grid4(lat, lon) do
|
|
lon_field = trunc((lon + 180) / 20)
|
|
lat_field = trunc((lat + 90) / 10)
|
|
lon_sq = trunc(rem(trunc(lon + 180), 20) / 2)
|
|
lat_sq = trunc(rem(trunc(lat + 90), 10) / 1)
|
|
|
|
<<?A + lon_field, ?A + lat_field, ?0 + lon_sq, ?0 + lat_sq>>
|
|
end
|
|
|
|
defp deg_to_rad(d), do: d * :math.pi() / 180
|
|
end
|