Adds haversine_km/4 to Radio context and backfill_distances/1 which calculates distance_km from pos1/pos2 for QSOs missing it. Called by QsoWeatherEnqueueWorker before enqueueing weather fetches.
99 lines
2.4 KiB
Elixir
99 lines
2.4 KiB
Elixir
defmodule Microwaveprop.Radio do
|
|
@moduledoc false
|
|
|
|
import Ecto.Query
|
|
|
|
alias Microwaveprop.Radio.Qso
|
|
alias Microwaveprop.Repo
|
|
|
|
@per_page 20
|
|
@sortable_fields ~w(station1 station2 band mode distance_km qso_timestamp)a
|
|
|
|
def list_qsos(opts \\ []) do
|
|
page = max(Keyword.get(opts, :page, 1), 1)
|
|
offset = (page - 1) * @per_page
|
|
|
|
{sort_field, sort_dir} = sort_opts(opts)
|
|
|
|
total_entries = Repo.aggregate(Qso, :count)
|
|
total_pages = max(ceil(total_entries / @per_page), 1)
|
|
|
|
entries =
|
|
Qso
|
|
|> order_by([q], [{^sort_dir, field(q, ^sort_field)}])
|
|
|> limit(^@per_page)
|
|
|> offset(^offset)
|
|
|> Repo.all()
|
|
|
|
%{
|
|
entries: entries,
|
|
page: page,
|
|
total_pages: total_pages,
|
|
total_entries: total_entries
|
|
}
|
|
end
|
|
|
|
defp sort_opts(opts) do
|
|
sort_by = Keyword.get(opts, :sort_by, :qso_timestamp)
|
|
sort_order = Keyword.get(opts, :sort_order, :desc)
|
|
|
|
field = if sort_by in @sortable_fields, do: sort_by, else: :qso_timestamp
|
|
dir = if sort_order in [:asc, :desc], do: sort_order, else: :desc
|
|
|
|
{field, dir}
|
|
end
|
|
|
|
def unprocessed_qsos(limit \\ 500) do
|
|
Qso
|
|
|> where([q], q.weather_queued == false and not is_nil(q.pos1))
|
|
|> order_by([q], asc: q.qso_timestamp)
|
|
|> limit(^limit)
|
|
|> Repo.all()
|
|
end
|
|
|
|
def mark_weather_queued!(qso_ids) do
|
|
Qso
|
|
|> where([q], q.id in ^qso_ids)
|
|
|> Repo.update_all(set: [weather_queued: true])
|
|
end
|
|
|
|
@earth_radius_km 6371.0
|
|
|
|
def haversine_km(lat1, lon1, lat2, lon2) do
|
|
dlat = deg_to_rad(lat2 - lat1)
|
|
dlon = deg_to_rad(lon2 - lon1)
|
|
rlat1 = deg_to_rad(lat1)
|
|
rlat2 = deg_to_rad(lat2)
|
|
|
|
a =
|
|
:math.sin(dlat / 2) ** 2 +
|
|
:math.cos(rlat1) * :math.cos(rlat2) * :math.sin(dlon / 2) ** 2
|
|
|
|
2 * @earth_radius_km * :math.asin(:math.sqrt(a))
|
|
end
|
|
|
|
def backfill_distances(qsos) do
|
|
Enum.each(qsos, fn qso ->
|
|
if is_nil(qso.distance_km) && qso.pos1 && qso.pos2 do
|
|
lat1 = qso.pos1["lat"]
|
|
lon1 = qso.pos1["lon"] || qso.pos1["lng"]
|
|
lat2 = qso.pos2["lat"]
|
|
lon2 = qso.pos2["lon"] || qso.pos2["lng"]
|
|
|
|
if lat1 && lon1 && lat2 && lon2 do
|
|
dist = lat1 |> haversine_km(lon1, lat2, lon2) |> round() |> Decimal.new()
|
|
|
|
Qso
|
|
|> where([q], q.id == ^qso.id)
|
|
|> Repo.update_all(set: [distance_km: dist])
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
defp deg_to_rad(deg), do: deg * :math.pi() / 180
|
|
|
|
def get_qso!(id) do
|
|
Repo.get!(Qso, id)
|
|
end
|
|
end
|