prop/lib/microwaveprop/radio/callsign_client.ex
Graham McIntire 51e390959c Add dialyzer specs and types across the codebase
277 @spec/@type annotations added to 58 files covering all public
APIs: contexts (propagation, radio, weather, terrain, beacons,
commercial), GRIB2 decoders, terrain analysis, duct detection,
rain scatter, CSV/ADIF import, weather clients, and all Ecto
schemas. Dialyzer passes with 0 errors.
2026-04-12 08:55:04 -05:00

45 lines
1.3 KiB
Elixir

defmodule Microwaveprop.Radio.CallsignClient do
@moduledoc "Looks up amateur radio callsign locations via gridmap.org."
@doc """
Fetch location for a callsign. Returns {:ok, %{callsign, gridsquare, lat, lon}} or {:error, reason}.
"""
@type location :: %{
callsign: String.t(),
gridsquare: String.t() | nil,
lat: number(),
lon: number()
}
@spec locate(String.t()) :: {:ok, location()} | {:error, String.t()}
def locate(callsign) do
url = "https://gridmap.org/locate/#{URI.encode(callsign)}"
case Req.get(url, req_options() ++ [receive_timeout: 10_000]) do
{:ok, %{status: 200, body: %{"latitude" => lat, "longitude" => lon} = body}} when is_number(lat) ->
{:ok,
%{
callsign: body["callsign"] || callsign,
gridsquare: body["gridsquare"],
lat: lat,
lon: lon
}}
{:ok, %{status: 200, body: _}} ->
{:error, "callsign not found"}
{:ok, %{status: 404}} ->
{:error, "callsign not found"}
{:ok, %{status: status}} ->
{:error, "gridmap.org HTTP #{status}"}
{:error, reason} ->
{:error, "gridmap.org failed: #{inspect(reason)}"}
end
end
defp req_options do
Application.get_env(:microwaveprop, :callsign_req_options, [])
end
end