Add weather grid query functions for /weather map page
This commit is contained in:
parent
411951147a
commit
0dd695c574
2 changed files with 245 additions and 5 deletions
|
|
@ -3,17 +3,17 @@ defmodule Microwaveprop.Weather do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
alias Microwaveprop.Repo
|
||||
alias Microwaveprop.Weather.IemClient
|
||||
alias Microwaveprop.Weather.HrrrProfile
|
||||
alias Microwaveprop.Weather.IemClient
|
||||
alias Microwaveprop.Weather.IemreObservation
|
||||
alias Microwaveprop.Weather.SolarIndex
|
||||
alias Microwaveprop.Weather.Sounding
|
||||
alias Microwaveprop.Weather.Station
|
||||
alias Microwaveprop.Weather.SurfaceObservation
|
||||
|
||||
require Logger
|
||||
|
||||
# Approximate km per degree latitude
|
||||
@km_per_deg_lat 111.0
|
||||
|
||||
|
|
@ -97,7 +97,10 @@ defmodule Microwaveprop.Weather do
|
|||
end
|
||||
|
||||
def sync_stations! do
|
||||
asos = for s <- ~w(AK AL AR AZ CA CO CT DE FL GA HI IA ID IL IN KS KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY), do: "#{s}_ASOS"
|
||||
asos =
|
||||
for s <-
|
||||
~w(AK AL AR AZ CA CO CT DE FL GA HI IA ID IL IN KS KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY),
|
||||
do: "#{s}_ASOS"
|
||||
|
||||
for network <- asos ++ ["RAOB"] do
|
||||
type = if String.contains?(network, "ASOS"), do: "asos", else: "sounding"
|
||||
|
|
@ -198,6 +201,72 @@ defmodule Microwaveprop.Weather do
|
|||
%{surface_observations: surface_observations, soundings: soundings}
|
||||
end
|
||||
|
||||
def latest_grid_valid_time do
|
||||
Repo.one(
|
||||
from(h in HrrrProfile,
|
||||
where:
|
||||
fragment("(? * 1000)::int % 125 = 0", h.lat) and
|
||||
fragment("(? * 1000)::int % 125 = 0", h.lon),
|
||||
select: max(h.valid_time)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def latest_weather_grid(bounds) do
|
||||
case latest_grid_valid_time() do
|
||||
nil ->
|
||||
[]
|
||||
|
||||
latest_vt ->
|
||||
Repo.all(
|
||||
from(h in HrrrProfile,
|
||||
where:
|
||||
h.valid_time == ^latest_vt and
|
||||
h.lat >= ^bounds["south"] and h.lat <= ^bounds["north"] and
|
||||
h.lon >= ^bounds["west"] and h.lon <= ^bounds["east"] and
|
||||
fragment("(? * 1000)::int % 125 = 0", h.lat) and
|
||||
fragment("(? * 1000)::int % 125 = 0", h.lon),
|
||||
select: %{
|
||||
lat: h.lat,
|
||||
lon: h.lon,
|
||||
valid_time: h.valid_time,
|
||||
temperature: h.surface_temp_c,
|
||||
dewpoint_depression: fragment("? - ?", h.surface_temp_c, h.surface_dewpoint_c),
|
||||
bl_height: h.hpbl_m,
|
||||
pwat: h.pwat_mm,
|
||||
refractivity_gradient: h.min_refractivity_gradient,
|
||||
ducting: h.ducting_detected,
|
||||
surface_pressure_mb: h.surface_pressure_mb
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def weather_point_detail(lat, lon, valid_time) do
|
||||
step = 0.125
|
||||
snapped_lat = Float.round(Float.round(lat / step) * step, 3)
|
||||
snapped_lon = Float.round(Float.round(lon / step) * step, 3)
|
||||
|
||||
Repo.one(
|
||||
from(h in HrrrProfile,
|
||||
where: h.lat == ^snapped_lat and h.lon == ^snapped_lon and h.valid_time == ^valid_time,
|
||||
select: %{
|
||||
lat: h.lat,
|
||||
lon: h.lon,
|
||||
valid_time: h.valid_time,
|
||||
temperature: h.surface_temp_c,
|
||||
dewpoint_depression: fragment("? - ?", h.surface_temp_c, h.surface_dewpoint_c),
|
||||
bl_height: h.hpbl_m,
|
||||
pwat: h.pwat_mm,
|
||||
refractivity_gradient: h.min_refractivity_gradient,
|
||||
ducting: h.ducting_detected,
|
||||
surface_pressure_mb: h.surface_pressure_mb
|
||||
}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def upsert_hrrr_profile(attrs) do
|
||||
%HrrrProfile{}
|
||||
|> HrrrProfile.changeset(attrs)
|
||||
|
|
@ -210,7 +279,9 @@ defmodule Microwaveprop.Weather do
|
|||
|
||||
def upsert_hrrr_profiles_batch(profiles, opts \\ []) do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
on_conflict = if Keyword.get(opts, :skip_existing, false), do: :nothing, else: {:replace_all_except, [:id, :inserted_at]}
|
||||
|
||||
on_conflict =
|
||||
if Keyword.get(opts, :skip_existing, false), do: :nothing, else: {:replace_all_except, [:id, :inserted_at]}
|
||||
|
||||
profiles
|
||||
|> Enum.chunk_every(500)
|
||||
|
|
|
|||
169
test/microwaveprop/weather_grid_test.exs
Normal file
169
test/microwaveprop/weather_grid_test.exs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
defmodule Microwaveprop.WeatherGridTest do
|
||||
use Microwaveprop.DataCase, async: true
|
||||
|
||||
alias Microwaveprop.Repo
|
||||
alias Microwaveprop.Weather
|
||||
|
||||
describe "latest_weather_grid/1" do
|
||||
test "returns grid data for latest valid_time within bounds" do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.125,
|
||||
lon: -97.375,
|
||||
valid_time: now,
|
||||
surface_temp_c: 25.0,
|
||||
surface_dewpoint_c: 18.0,
|
||||
surface_pressure_mb: 1013.0,
|
||||
hpbl_m: 500.0,
|
||||
pwat_mm: 30.0,
|
||||
min_refractivity_gradient: -280.0,
|
||||
ducting_detected: true
|
||||
})
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.250,
|
||||
lon: -97.375,
|
||||
valid_time: now,
|
||||
surface_temp_c: 26.0,
|
||||
surface_dewpoint_c: 19.0,
|
||||
surface_pressure_mb: 1012.0,
|
||||
hpbl_m: 600.0,
|
||||
pwat_mm: 32.0,
|
||||
min_refractivity_gradient: -150.0,
|
||||
ducting_detected: false
|
||||
})
|
||||
|
||||
bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0}
|
||||
result = Weather.latest_weather_grid(bounds)
|
||||
|
||||
assert length(result) == 2
|
||||
point = Enum.find(result, &(&1.lat == 32.125))
|
||||
assert point.temperature == 25.0
|
||||
assert point.dewpoint_depression == 7.0
|
||||
assert point.bl_height == 500.0
|
||||
assert point.pwat == 30.0
|
||||
assert point.refractivity_gradient == -280.0
|
||||
assert point.ducting == true
|
||||
end
|
||||
|
||||
test "returns empty list when no data exists" do
|
||||
bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0}
|
||||
assert Weather.latest_weather_grid(bounds) == []
|
||||
end
|
||||
|
||||
test "only returns points on 0.125 grid" do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.125,
|
||||
lon: -97.375,
|
||||
valid_time: now,
|
||||
surface_temp_c: 25.0,
|
||||
surface_dewpoint_c: 18.0,
|
||||
surface_pressure_mb: 1013.0,
|
||||
hpbl_m: 500.0,
|
||||
pwat_mm: 30.0
|
||||
})
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.1337,
|
||||
lon: -97.3821,
|
||||
valid_time: now,
|
||||
surface_temp_c: 25.0,
|
||||
surface_dewpoint_c: 18.0,
|
||||
surface_pressure_mb: 1013.0,
|
||||
hpbl_m: 500.0,
|
||||
pwat_mm: 30.0
|
||||
})
|
||||
|
||||
bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0}
|
||||
result = Weather.latest_weather_grid(bounds)
|
||||
|
||||
assert length(result) == 1
|
||||
assert hd(result).lat == 32.125
|
||||
end
|
||||
|
||||
test "uses most recent valid_time only" do
|
||||
old = DateTime.utc_now() |> DateTime.add(-7200, :second) |> DateTime.truncate(:second)
|
||||
new = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.125,
|
||||
lon: -97.375,
|
||||
valid_time: old,
|
||||
surface_temp_c: 20.0,
|
||||
surface_dewpoint_c: 15.0,
|
||||
surface_pressure_mb: 1010.0,
|
||||
hpbl_m: 400.0,
|
||||
pwat_mm: 25.0
|
||||
})
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.125,
|
||||
lon: -97.375,
|
||||
valid_time: new,
|
||||
surface_temp_c: 28.0,
|
||||
surface_dewpoint_c: 20.0,
|
||||
surface_pressure_mb: 1015.0,
|
||||
hpbl_m: 700.0,
|
||||
pwat_mm: 35.0
|
||||
})
|
||||
|
||||
bounds = %{"south" => 32.0, "north" => 33.0, "west" => -98.0, "east" => -97.0}
|
||||
result = Weather.latest_weather_grid(bounds)
|
||||
|
||||
assert length(result) == 1
|
||||
assert hd(result).temperature == 28.0
|
||||
end
|
||||
end
|
||||
|
||||
describe "weather_point_detail/3" do
|
||||
test "returns all fields for a single grid point" do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
insert_hrrr_profile(%{
|
||||
lat: 32.125,
|
||||
lon: -97.375,
|
||||
valid_time: now,
|
||||
surface_temp_c: 25.0,
|
||||
surface_dewpoint_c: 18.0,
|
||||
surface_pressure_mb: 1013.0,
|
||||
hpbl_m: 500.0,
|
||||
pwat_mm: 30.0,
|
||||
min_refractivity_gradient: -280.0,
|
||||
ducting_detected: true
|
||||
})
|
||||
|
||||
result = Weather.weather_point_detail(32.15, -97.40, now)
|
||||
|
||||
assert result.lat == 32.125
|
||||
assert result.temperature == 25.0
|
||||
assert result.surface_pressure_mb == 1013.0
|
||||
end
|
||||
|
||||
test "returns nil when no data at point" do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
assert Weather.weather_point_detail(32.15, -97.40, now) == nil
|
||||
end
|
||||
end
|
||||
|
||||
defp insert_hrrr_profile(attrs) do
|
||||
now = DateTime.truncate(DateTime.utc_now(), :second)
|
||||
|
||||
defaults = %{
|
||||
id: Ecto.UUID.bingenerate(),
|
||||
run_time: DateTime.add(attrs.valid_time, -3600, :second),
|
||||
profile: [],
|
||||
surface_refractivity: nil,
|
||||
min_refractivity_gradient: nil,
|
||||
ducting_detected: nil,
|
||||
duct_characteristics: nil,
|
||||
inserted_at: now,
|
||||
updated_at: now
|
||||
}
|
||||
|
||||
entry = Map.merge(defaults, attrs)
|
||||
Repo.insert_all("hrrr_profiles", [entry])
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue