moonaerowx

    Pure MoonBit toolkit for parsing aviation weather reports and timelines

    aviation
    weather
    metar
    speci
    taf
    parser
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    31

    No dependencies

    #MoonAeroWx

    MoonBit 航空气象报文解析与趋势分析工具包

    MoonAeroWx is a pure MoonBit toolkit for parsing aviation weather reports. The current milestone focuses on common METAR/SPECI groups, RVR-aware TAF timelines, structured diagnostics, JSON/Markdown/CSV output, batch statistics, and configurable flight-category classification.

    Safety notice: MoonAeroWx is intended for software development, education, and offline data analysis. It is not certified and must not be used for real aviation operational decisions.

    #项目信息

    字段内容
    项目名称MoonAeroWx
    项目标识jshsj124/moonaerowx
    项目描述基于 MoonBit 的航空气象报文解析与趋势分析工具包,支持 METAR/SPECI/TAF 解析、RVR 识别、飞行类别判定、批量统计以及 JSON/Markdown/CSV 输出。
    仓库地址https://github.com/jshsj124/MoonAeroWx
    包名jshsj124/moonaerowx
    许可证Apache-2.0

    #文档导航

    #Current MVP

    • Whitespace tokenizer with source spans.
    • Tolerant METAR/SPECI parser.
    • Common groups:
      • report kind: METAR, SPECI, or omitted;
      • station code;
      • observation time DDHHMMZ;
      • wind groups such as 12008MPS, 25008KT, VRB03KT, 22015G25KT;
      • visibility groups such as 8000, 9999, 10SM, P6SM, CAVOK;
      • selected weather groups such as -RA, BR, FG, TSRA, SHRA, NSW;
      • clouds such as FEW020, SCT020, BKN040, OVC008, SKC, NSC;
      • temperature/dew point such as 18/16, M02/M05;
      • pressure Q1008 and A2992;
      • runway visual range such as R18/P1500U, R19/0400V0800D;
      • NOSIG trend;
      • raw remark preservation.
    • Compact JSON, Markdown, CSV and plain-text summary formatting.
    • Newline-delimited batch statistics with Markdown/CSV reports.
    • VFR/MVFR/IFR/LIFR classification with configurable thresholds.
    • TAF parser for issue time, valid period, initial forecast, FM, BECMG, TEMPO, PROB, and PROB TEMPO groups.
    • Visualization-friendly TAF timeline expansion and text timeline output.

    #Library example

    ///|
    test "decode a simple METAR" {
    let result = @moonaerowx.parse_metar(
    "ZBAA 171200Z 12008MPS 8000 -RA SCT020 BKN040 18/16 Q1008 NOSIG",
    )
    assert_false(result.has_errors())
    let report = result.unwrap()
    assert_eq(report.station, "ZBAA")
    assert_eq(report.wind.unwrap().speed, 8)
    assert_eq(report.weather[0].description, "light rain")
    }

    #Batch and export example

    ///| test "summarize a batch" { let source = "ZBAA 171200Z 12008MPS 9999 SCT020 18/16 Q1008 ZGGG 171300Z VRB03KT 0600 FG OVC004 26/25 Q1004" let batch = @moonaerowx.parse_metar_batch(source) assert_eq(batch.total_reports, 2) assert_eq(batch.lifr_count, 1) assert_true(@moonaerowx.batch_to_markdown(batch).contains("METAR batch summary")) assert_true(@moonaerowx.batch_to_csv(batch).contains("total_reports")) }

    #CLI examples

    moon run cmd/aerowx -- decode "ZBAA 171200Z 12008MPS 8000 -RA SCT020 18/16 Q1008" moon run cmd/aerowx -- decode-json "METAR KLAX 171253Z 25008KT 10SM FEW015 SCT250 21/16 A2992" moon run cmd/aerowx -- decode-md "ZBAA 171200Z 12008MPS 0600 R18/P1500U FG OVC004 16/15 Q1008" moon run cmd/aerowx -- decode-csv "ZBAA 171200Z 12008MPS 8000 -RA SCT020 18/16 Q1008" moon run cmd/aerowx -- validate "ZBAA 171200Z 99908MPS 8000" moon run cmd/aerowx -- timeline "TAF ZBAA 171100Z 1712/1812 12008MPS 8000 SCT020 TEMPO 1715/1718 0600 R18/0400V0800D FG OVC004 FM180000 20012MPS 9999 NSW SCT030" moon run cmd/aerowx -- batch-csv "ZBAA 171200Z 12008MPS 9999 SCT020 18/16 Q1008 ZGGG 171300Z VRB03KT 0600 FG OVC004 26/25 Q1004"

    #Roadmap

    1. Harden remaining METAR grammar and diagnostics.
    2. Add runway state, wind-shear, sea-state and richer remarks support.
    3. Harden TAF edge cases, nested regional groups, and month-boundary semantics.
    4. Add Wasm demo assets and richer batch input adapters.
    5. Add multi-backend CI and publishing metadata once the GitHub/GitLink repositories are created.

    #License

    Apache-2.0.

    BatchReport

    pub(all) struct BatchReport {
    total_reports : Int
    parsed_reports : Int
    error_reports : Int
    warning_reports : Int
    diagnostics_count : Int
    vfr_count : Int
    mvfr_count : Int
    ifr_count : Int
    lifr_count : Int
    unknown_category_count : Int
    low_visibility_events : Int
    low_ceiling_events : Int
    } derive(Eq,
    Debug
    )

    CloudCoverage

    pub(all) enum CloudCoverage {
    Few
    Scattered
    Broken
    Overcast
    SkyClear
    NoSignificantCloud
    NoCloudDetected
    } derive(Eq,
    Debug
    )

    CloudLayer

    pub(all) struct CloudLayer {
    coverage : CloudCoverage
    height_feet : Int?
    cumulonimbus : Bool
    towering_cumulus : Bool
    } derive(Eq,
    Debug
    )

    Diagnostic

    pub(all) struct Diagnostic {
    severity : Severity
    code : DiagnosticCode
    message : String
    span : Span
    token : String
    } derive(Eq,
    Debug
    )

    Structured diagnostic emitted by parsers and validators.

    Diagnostic::is_error

    fn Diagnostic::is_error(self : Diagnostic) -> Bool

    Diagnostic::new

    fn Diagnostic::new(severity : Severity, code : DiagnosticCode, message : String, span : Span, token? : String) -> Diagnostic

    Diagnostic::to_string

    fn Diagnostic::to_string(self : Diagnostic) -> String

    DiagnosticCode

    pub(all) enum DiagnosticCode {
    EmptyInput
    MissingStation
    MissingObservationTime
    InvalidStation
    InvalidTime
    InvalidWind
    InvalidVisibility
    InvalidWeather
    InvalidCloud
    InvalidTemperature
    InvalidPressure
    InvalidTafPeriod
    InvalidTafChange
    InvalidRunwayVisualRange
    UnknownToken
    } derive(Eq,
    Debug
    )

    FlightCategory

    pub(all) enum FlightCategory {
    VFR
    MVFR
    IFR
    LIFR
    UnknownCategory
    } derive(Eq,
    Debug
    )

    FlightCategoryRules

    pub(all) struct FlightCategoryRules {
    mvfr_visibility_meters : Int
    ifr_visibility_meters : Int
    lifr_visibility_meters : Int
    mvfr_ceiling_feet : Int
    ifr_ceiling_feet : Int
    lifr_ceiling_feet : Int
    } derive(Eq,
    Debug
    )

    MetarReport

    pub(all) struct MetarReport {
    kind : ReportKind
    station : String
    observation_time : ObservationTime
    automated : Bool
    correction : Bool
    wind : Wind?
    variable_wind : VariableWindDirection?
    visibility : Visibility?
    rvr : Array[RunwayVisualRange]
    cavok : Bool
    weather : Array[Weather]
    clouds : Array[CloudLayer]
    temperature : Temperature?
    pressure : Pressure?
    trend : Trend?
    remarks : Array[String]
    } derive(Eq,
    Debug
    )

    ObservationTime

    pub(all) struct ObservationTime {
    day : Int
    hour : Int
    minute : Int
    } derive(Eq,
    Debug
    )

    ObservationTime::to_string

    fn ObservationTime::to_string(self : ObservationTime) -> String

    ParseResult

    pub(all) struct ParseResult[T] {
    value : T?
    diagnostics : Array[Diagnostic]
    } derive(Eq,
    Debug
    )

    Parser result that can carry a partial value plus warnings/errors.

    ParseResult::has_errors

    fn[T] ParseResult::has_errors(self : ParseResult[T]) -> Bool

    ParseResult::unwrap

    fn[T] ParseResult::unwrap(self : ParseResult[T]) -> T

    Pressure

    pub(all) struct Pressure {
    value : Int
    unit : PressureUnit
    } derive(Eq,
    Debug
    )

    PressureUnit

    pub(all) enum PressureUnit {
    HectoPascal
    InchesHgHundredths
    } derive(Eq,
    Debug
    )

    ReportKind

    pub(all) enum ReportKind {
    Metar
    Speci
    UnspecifiedReport
    } derive(Eq,
    Debug
    )

    RunwayVisualRange

    pub(all) struct RunwayVisualRange {
    runway : String
    minimum_meters : Int
    maximum_meters : Int?
    above_maximum : Bool
    below_minimum : Bool
    trend : RvrTrend?
    } derive(Eq,
    Debug
    )

    RvrTrend

    pub(all) enum RvrTrend {
    RvrIncreasing
    RvrDecreasing
    RvrNoChange
    } derive(Eq,
    Debug
    )

    Severity

    pub(all) enum Severity {
    Error
    Warning
    Info
    } derive(Eq,
    Debug
    )

    Span

    pub(all) struct Span {
    start : Int
    end : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    Half-open byte span in a source report. Lines are one-based and columns are one-based for user-facing diagnostics.

    Span::new

    fn Span::new(start : Int, end : Int, line? : Int, column? : Int) -> Span

    TafChangeGroup

    pub(all) struct TafChangeGroup {
    kind : TafChangeKind
    period : TafPeriod?
    from_time : TafTime?
    probability_percent : Int?
    conditions : TafConditions
    } derive(Eq,
    Debug
    )

    TafChangeKind

    pub(all) enum TafChangeKind {
    From
    Becoming
    Temporary
    Probability
    ProbabilityTemporary
    } derive(Eq,
    Debug
    )

    TafConditions

    pub(all) struct TafConditions {
    wind : Wind?
    variable_wind : VariableWindDirection?
    visibility : Visibility?
    rvr : Array[RunwayVisualRange]
    cavok : Bool
    weather : Array[Weather]
    clouds : Array[CloudLayer]
    raw_tokens : Array[String]
    } derive(Eq,
    Debug
    )

    TafPeriod

    pub(all) struct TafPeriod {
    start : TafTime
    end : TafTime
    } derive(Eq,
    Debug
    )

    TafPeriod::to_string

    fn TafPeriod::to_string(self : TafPeriod) -> String

    TafReport

    pub(all) struct TafReport {
    station : String
    issue_time : ObservationTime?
    valid_period : TafPeriod
    initial : TafConditions
    changes : Array[TafChangeGroup]
    raw : String
    } derive(Eq,
    Debug
    )

    TafTime

    pub(all) struct TafTime {
    day : Int
    hour : Int
    minute : Int
    } derive(Compare, Eq,
    Debug
    )

    TafTime::to_string

    fn TafTime::to_string(self : TafTime) -> String

    TafTimelineEntry

    pub(all) struct TafTimelineEntry {
    label : String
    period : TafPeriod?
    conditions : TafConditions
    probability_percent : Int?
    } derive(Eq,
    Debug
    )

    Temperature

    pub(all) struct Temperature {
    air_celsius : Int
    dew_point_celsius : Int
    } derive(Eq,
    Debug
    )

    Token

    pub(all) struct Token {
    lexeme : String
    span : Span
    } derive(Eq,
    Debug
    )

    A whitespace-delimited token with source position. METAR/TAF syntax is mostly token based, so the first lexer keeps raw groups while preserving spans for diagnostics.

    Token::to_string

    fn Token::to_string(self : Token) -> String

    Trend

    pub(all) enum Trend {
    NoSignificantChange
    } derive(Eq,
    Debug
    )

    VariableWindDirection

    pub(all) struct VariableWindDirection {
    from_degrees : Int
    to_degrees : Int
    } derive(Eq,
    Debug
    )

    Visibility

    pub(all) struct Visibility {
    value : Int
    unit : VisibilityUnit
    greater_than : Bool
    } derive(Eq,
    Debug
    )

    VisibilityUnit

    pub(all) enum VisibilityUnit {
    Meters
    StatuteMiles
    } derive(Eq,
    Debug
    )

    Weather

    pub(all) struct Weather {
    raw : String
    intensity : WeatherIntensity
    description : String
    } derive(Eq,
    Debug
    )

    WeatherIntensity

    pub(all) enum WeatherIntensity {
    Light
    Moderate
    Heavy
    } derive(Eq,
    Debug
    )

    Wind

    pub(all) struct Wind {
    direction_degrees : Int?
    variable_direction : Bool
    speed : Int
    gust : Int?
    unit : WindUnit
    } derive(Eq,
    Debug
    )

    WindUnit

    pub(all) enum WindUnit {
    Knots
    MetersPerSecond
    } derive(Eq,
    Debug
    )

    WindUnit::to_string

    fn WindUnit::to_string(self : WindUnit) -> String

    batch_to_csv

    fn batch_to_csv(batch : BatchReport) -> String

    Render aggregate batch statistics as a single-row CSV document.

    batch_to_markdown

    fn batch_to_markdown(batch : BatchReport) -> String

    Render aggregate batch statistics as a Markdown table.

    classify

    fn classify(report : MetarReport, rules? : FlightCategoryRules) -> FlightCategory

    Classify a report using configurable visibility and ceiling thresholds.

    flight_category_to_string

    fn flight_category_to_string(category : FlightCategory) -> String

    metar_batch_to_csv

    fn metar_batch_to_csv(source : String, rules? : FlightCategoryRules) -> String

    Summarize newline-delimited METAR/SPECI text directly as CSV.

    metar_batch_to_markdown

    fn metar_batch_to_markdown(source : String, rules? : FlightCategoryRules) -> String

    Summarize newline-delimited METAR/SPECI text directly as Markdown.

    metar_to_csv

    fn metar_to_csv(report : MetarReport) -> String

    Convert one parsed METAR/SPECI report into a one-row CSV document.

    metar_to_json

    fn metar_to_json(report : MetarReport) -> String

    Convert a parsed METAR/SPECI report to compact JSON without external runtime dependencies. This keeps the MVP portable across native, JS and Wasm targets.

    metar_to_markdown

    fn metar_to_markdown(report : MetarReport) -> String

    Convert one parsed METAR/SPECI report into a Markdown summary card.

    metar_to_summary

    fn metar_to_summary(report : MetarReport) -> String

    parse_metar

    fn parse_metar(source : String) -> ParseResult[MetarReport]

    Parse a common METAR or SPECI report. The parser is deliberately tolerant: unknown regional/remark tokens are preserved as remarks with warnings.

    parse_metar_batch

    fn parse_metar_batch(source : String, rules? : FlightCategoryRules) -> BatchReport

    Parse newline-delimited METAR/SPECI text and return aggregate statistics.

    parse_speci

    fn parse_speci(source : String) -> ParseResult[MetarReport]

    parse_taf

    fn parse_taf(source : String) -> ParseResult[TafReport]

    Parse a common TAF report with initial conditions and first-class FM, BECMG, TEMPO, PROB and PROB TEMPO change groups.

    standard_rules

    fn standard_rules() -> FlightCategoryRules

    taf_timeline

    fn taf_timeline(report : TafReport) -> Array[TafTimelineEntry]

    Expand a parsed TAF into visualization-friendly timeline entries. FM groups become replacement intervals ending at the next FM or the forecast end; BECMG/TEMPO/PROB groups keep their explicit periods as overlays.

    taf_timeline_to_summary

    fn taf_timeline_to_summary(report : TafReport) -> String

    Render a parsed TAF timeline as a compact text block for CLI demos and docs.

    tokenize

    fn tokenize(source : String) -> Array[Token]