prop/lib/microwaveprop/weather/grib2/simple_packing.ex
Graham McIntire 51e390959c Add dialyzer specs and types across the codebase
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.
2026-04-12 08:55:04 -05:00

82 lines
2.3 KiB
Elixir

defmodule Microwaveprop.Weather.Grib2.SimplePacking do
@moduledoc false
@doc """
Extract a single value from GRIB2 simple-packed data at the given grid index.
Formula: value = (R + X * 2^E) * 10^(-D)
Where R = reference_value, E = binary_scale, D = decimal_scale,
X = raw packed integer at the given index.
"""
@spec extract_value(map(), binary(), non_neg_integer()) :: {:ok, float()} | {:error, :index_out_of_range}
def extract_value(%{bits_per_value: 0} = params, _data, _index) do
{:ok, params.reference_value * :math.pow(10, -params.decimal_scale)}
end
def extract_value(params, data, index) do
%{
reference_value: r,
binary_scale: e,
decimal_scale: d,
bits_per_value: n,
num_data_points: num
} = params
if index < 0 or index >= num do
{:error, :index_out_of_range}
else
x = extract_bits(data, index, n)
value = (r + x * :math.pow(2, e)) * :math.pow(10, -d)
{:ok, value}
end
end
@doc """
Extract multiple values from GRIB2 simple-packed data at the given grid indices.
Returns `{:ok, %{index => value}}`. Out-of-range indices are silently skipped.
"""
@spec extract_values(map(), binary(), [non_neg_integer()]) :: {:ok, %{non_neg_integer() => float()}}
def extract_values(%{bits_per_value: 0} = params, _data, indices) do
value = params.reference_value * :math.pow(10, -params.decimal_scale)
{:ok, Map.new(indices, fn i -> {i, value} end)}
end
def extract_values(params, data, indices) do
%{
reference_value: r,
binary_scale: e,
decimal_scale: d,
bits_per_value: n,
num_data_points: num
} = params
factor_2e = :math.pow(2, e)
factor_10d = :math.pow(10, -d)
results =
Enum.reduce(indices, %{}, fn index, acc ->
if index >= 0 and index < num do
x = extract_bits(data, index, n)
value = (r + x * factor_2e) * factor_10d
Map.put(acc, index, value)
else
acc
end
end)
{:ok, results}
end
defp extract_bits(data, index, bits_per_value) do
bit_offset = index * bits_per_value
byte_offset = div(bit_offset, 8)
remainder = rem(bit_offset, 8)
<<_skip::binary-size(byte_offset), _pad::size(remainder), x::size(bits_per_value)-unsigned-big, _rest::bitstring>> =
data
x
end
end