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.
86 lines
2.2 KiB
Elixir
86 lines
2.2 KiB
Elixir
defmodule Microwaveprop.Weather.Grib2.LambertConformal do
|
|
@moduledoc false
|
|
|
|
@r_earth 6_371_229.0
|
|
|
|
@doc """
|
|
Convert a lat/lon to grid indices (i, j) on a Lambert Conformal Conic grid.
|
|
|
|
Returns `{:ok, {i, j}}` or `{:error, :outside_grid}`.
|
|
"""
|
|
@spec to_grid_index(map(), float(), float()) ::
|
|
{:ok, {non_neg_integer(), non_neg_integer()}} | {:error, :outside_grid}
|
|
def to_grid_index(grid, lat, lon) do
|
|
%{
|
|
nx: nx,
|
|
ny: ny,
|
|
la1: la1,
|
|
lo1: lo1,
|
|
dx: dx,
|
|
dy: dy,
|
|
latin1: latin1,
|
|
lov: lov
|
|
} = grid
|
|
|
|
# Normalize longitudes to 0..360
|
|
lo1 = normalize_lon(lo1)
|
|
lon = normalize_lon(lon)
|
|
lov = normalize_lon(lov)
|
|
|
|
latin1_rad = deg_to_rad(latin1)
|
|
la1_rad = deg_to_rad(la1)
|
|
lat_rad = deg_to_rad(lat)
|
|
|
|
# Cone constant (for HRRR, latin1 == latin2 == 38.5, so n = sin(latin1))
|
|
n = :math.sin(latin1_rad)
|
|
|
|
# F factor
|
|
f =
|
|
:math.cos(latin1_rad) *
|
|
:math.pow(:math.tan(:math.pi() / 4 + latin1_rad / 2), n) / n
|
|
|
|
# rho for a given latitude
|
|
rho_origin = rho(la1_rad, n, f)
|
|
rho_target = rho(lat_rad, n, f)
|
|
|
|
# Theta angles (difference from central meridian, scaled by cone constant)
|
|
theta_origin = n * deg_to_rad(lo1 - lov)
|
|
theta_target = n * deg_to_rad(adjusted_lon_diff(lon, lov))
|
|
|
|
# Grid coordinates
|
|
x_origin = rho_origin * :math.sin(theta_origin)
|
|
y_origin = rho_origin * :math.cos(theta_origin)
|
|
|
|
x_target = rho_target * :math.sin(theta_target)
|
|
y_target = rho_target * :math.cos(theta_target)
|
|
|
|
i = round((x_target - x_origin) / dx)
|
|
j = round((y_origin - y_target) / dy)
|
|
|
|
if i >= 0 and i < nx and j >= 0 and j < ny do
|
|
{:ok, {i, j}}
|
|
else
|
|
{:error, :outside_grid}
|
|
end
|
|
end
|
|
|
|
defp rho(lat_rad, n, f) do
|
|
@r_earth * f / :math.pow(:math.tan(:math.pi() / 4 + lat_rad / 2), n)
|
|
end
|
|
|
|
defp deg_to_rad(deg), do: deg * :math.pi() / 180.0
|
|
|
|
defp normalize_lon(lon) when lon < 0, do: lon + 360.0
|
|
defp normalize_lon(lon), do: lon + 0.0
|
|
|
|
# Compute longitude difference handling wrap-around
|
|
defp adjusted_lon_diff(lon, lov) do
|
|
diff = lon - lov
|
|
|
|
cond do
|
|
diff > 180.0 -> diff - 360.0
|
|
diff < -180.0 -> diff + 360.0
|
|
true -> diff
|
|
end
|
|
end
|
|
end
|