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.
45 lines
1.3 KiB
Elixir
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
|