prop/lib/microwaveprop/terrain/terrain_profile.ex
Graham McIntire 45e2e69361
Add SRTM terrain path profiles for QSOs
Fetch elevation data along the path between two stations via the
Open-Meteo Elevation API (with Open-Topo-Data fallback), compute
Fresnel zone clearance, earth bulge, and knife-edge diffraction loss,
and store the results per QSO.

- terrain_profiles table with migration and TerrainProfile schema
- ElevationClient with batched API calls and fallback
- TerrainAnalysis with Fresnel/diffraction physics (ITU-R P.526-15)
- TerrainProfileWorker on Oban :terrain queue
- QsoWeatherEnqueueWorker enqueues terrain jobs automatically
- QSO show page displays verdict badge and collapsible elevation table
- Reorder show page: terrain, soundings, solar, HRRR, surface obs
- Fix Dockerfile wgrib2 build (add cmake dependency)
2026-03-29 16:22:29 -05:00

34 lines
1,003 B
Elixir

defmodule Microwaveprop.Terrain.TerrainProfile do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "terrain_profiles" do
belongs_to :qso, Microwaveprop.Radio.Qso
field :sample_count, :integer
field :path_points, {:array, :map}
field :max_elevation_m, :float
field :min_clearance_m, :float
field :diffraction_db, :float
field :fresnel_hit_count, :integer
field :obstructed_count, :integer
field :verdict, :string
timestamps(type: :utc_datetime)
end
@required_fields ~w(qso_id sample_count path_points verdict)a
@optional_fields ~w(max_elevation_m min_clearance_m diffraction_db fresnel_hit_count obstructed_count)a
def changeset(profile, attrs) do
profile
|> cast(attrs, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:qso_id)
|> unique_constraint([:qso_id])
end
end