Current section

Files

Jump to
igc_parser lib igc fix.ex
Raw

lib/igc/fix.ex

defmodule IGC.Fix do
@moduledoc """
Represents a single fix record in the IGC file.
All the extensions defined in the B record of the IGC file are stored in the
`extensions` field.
The "well known" extensions are also stored in their own fields, if provided
by the dataset.
"""
defstruct [
:time,
:position,
:valid?,
:pressure_altitude,
:gnss_altitude,
:extensions,
:magnetic_heading,
:true_heading,
:air_speed,
:satellite_in_use,
:horizontal_accuracy,
:vertical_accuracy,
:true_air_speed,
:wind_direction,
:wind_speed,
:compensated_variometer,
:uncompensated_variometer
]
@type t :: %__MODULE__{
time: Time.t(),
position: IGC.Position.t(),
valid?: boolean,
pressure_altitude: integer,
gnss_altitude: integer,
extensions: IGC.Extensions.values(),
magnetic_heading: integer | nil,
true_heading: integer | nil,
air_speed: integer | nil,
satellite_in_use: integer | nil,
horizontal_accuracy: integer | nil,
vertical_accuracy: integer | nil,
true_air_speed: integer | nil,
wind_direction: integer | nil,
wind_speed: integer | nil,
compensated_variometer: integer | nil,
uncompensated_variometer: integer | nil
}
@type extensions :: %{String.t() => String.t()}
@spec parse_line(String.t(), IGC.Headers.t()) :: {:ok, t()} | {:error, atom}
def parse_line(line, headers) do
with {:ok, hours, rest} <- IGC.Parser.take(line, 2, :int),
{:ok, minutes, rest} <- IGC.Parser.take(rest, 2, :int),
{:ok, seconds, rest} <- IGC.Parser.take(rest, 2, :int),
{:ok, position, rest} <- IGC.Position.parse(rest),
{:ok, valid?, rest} <- IGC.Parser.take(rest, 1),
{:ok, pressure_altitude, rest} <- IGC.Parser.take(rest, 5, :int),
{:ok, gnss_altitude, _rest} <- IGC.Parser.take(rest, 5, :int),
{:ok, time} <- Time.new(hours, minutes, seconds),
{:ok, extensions} <-
IGC.Extensions.parse_values(rest, headers.fix_extensions.extensions),
fix <- set_well_known_data(extensions) do
{:ok,
%{
fix
| time: time,
position: position,
valid?: valid? == "A",
gnss_altitude: gnss_altitude,
pressure_altitude: pressure_altitude,
extensions: extensions
}}
end
end
@well_known_extensions [
{"HDM", :magnetic_heading, :int},
{"HDT", :true_heading, :int},
{"IAS", :air_speed, :int},
{"SIU", :satellite_in_use, :int},
{"WDI", :wind_direction, :int},
{"WSP", :wind_speed, :int},
{"FXA", :horizontal_accuracy, :int},
{"VXA", :vertical_accuracy, :int},
{"VAT", :compensated_variometer, :vario},
{"VAR", :uncompensated_variometer, :vario}
]
@spec set_well_known_data(extensions :: IGC.Extensions.values(), fix :: t()) :: t()
defp set_well_known_data(extensions, fix \\ %__MODULE__{}) do
Enum.reduce(@well_known_extensions, fix, fn {extension_key, fix_key, type}, fix ->
if Map.has_key?(extensions, extension_key) do
value = Map.get(extensions, extension_key)
value =
case type do
:int -> parse_int(value)
:vario -> parse_variometer(value)
end
Map.put(fix, fix_key, value)
else
fix
end
end)
end
@spec parse_int(String.t()) :: integer | nil
defp parse_int(value) do
case Integer.parse(value) do
{int, ""} -> int
_ -> nil
end
end
@spec parse_variometer(String.t()) :: integer | nil
defp parse_variometer(value) do
case Integer.parse(value) do
{int, ""} -> int / 100
_ -> nil
end
end
end