Skip to content

Commit

Permalink
Fix undetermined cloud layer, add automatic CB detection (#131)
Browse files Browse the repository at this point in the history
* Fix undetermined cloud layer parsing

* Add parsing for "radar detected C-B clouds"

* Add text/voice formatting for automatic CB detection
  • Loading branch information
justinshannon authored Feb 14, 2025
1 parent 834564c commit 6d00fa6
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 84 deletions.
152 changes: 82 additions & 70 deletions vATIS.Desktop/Atis/Nodes/CloudNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,88 +101,100 @@ private string FormatCloudsText(CloudLayer layer)
{
ArgumentNullException.ThrowIfNull(Station);

if (Station.AtisFormat.Clouds.Types.TryGetValue(AmountToString(layer.Amount), out var value))
if (!Station.IsFaaAtis && layer is
{ Amount: CloudLayer.CloudAmount.None, BaseHeight: null, Type: CloudLayer.CloudType.Cumulonimbus })
{
var template = value.Text;

if (layer.Type == CloudLayer.CloudType.CannotMeasure || layer.BaseHeight == null)
{
template = Regex.Replace(template, "{altitude}",
$" {Station.AtisFormat.Clouds.UndeterminedLayerAltitude.Text} ",
RegexOptions.IgnoreCase);
}
else
{
var height = (int)layer.BaseHeight.ActualValue;

if (Station.AtisFormat.Clouds.ConvertToMetric)
height *= 30;
else if (Station.AtisFormat.Clouds.IsAltitudeInHundreds)
height *= 100;

// Match {altitude} or {altitude:N}, where N is the number of digits
template = Regex.Replace(
template,
@"\{altitude(?::(\d+))?\}",
match =>
return Station.AtisFormat.Clouds.AutomaticCbDetection.Text ?? "//////CB";
}

if (!Station.AtisFormat.Clouds.Types.TryGetValue(AmountToString(layer.Amount), out var value))
{
return "";
}

var template = value.Text;

if (layer.BaseHeight == null)
{
template = Regex.Replace(template, "{altitude}",
$"{Station.AtisFormat.Clouds.UndeterminedLayerAltitude.Text}",
RegexOptions.IgnoreCase);
}
else
{
var height = (int)layer.BaseHeight.ActualValue;

if (Station.AtisFormat.Clouds.ConvertToMetric)
height *= 30;
else if (Station.AtisFormat.Clouds.IsAltitudeInHundreds)
height *= 100;

// Match {altitude} or {altitude:N}, where N is the number of digits
template = Regex.Replace(
template,
@"\{altitude(?::(\d+))?\}",
match =>
{
var minDigits = 3;
if (int.TryParse(match.Groups[1].Value, out var specifiedDigits))
{
var minDigits = 3;
if (int.TryParse(match.Groups[1].Value, out var specifiedDigits))
{
minDigits = specifiedDigits;
}

return height.ToString(new string('0', minDigits));
},
RegexOptions.IgnoreCase
);
}

template = layer.Type != CloudLayer.CloudType.None
? Regex.Replace(template, "{convective}", TypeToString(layer.Type), RegexOptions.IgnoreCase)
: Regex.Replace(template, "{convective}", "", RegexOptions.IgnoreCase);

return template.Trim().ToUpperInvariant();
minDigits = specifiedDigits;
}

return height.ToString(new string('0', minDigits));
},
RegexOptions.IgnoreCase
);
}

return "";
template = layer.Type != CloudLayer.CloudType.None
? Regex.Replace(template, "{convective}", TypeToString(layer.Type), RegexOptions.IgnoreCase)
: Regex.Replace(template, "{convective}", "", RegexOptions.IgnoreCase);

return template.Trim().ToUpperInvariant();
}

private string FormatCloudsVoice(CloudLayer layer, CloudLayer? ceiling)
{
ArgumentNullException.ThrowIfNull(Station);

if (Station.AtisFormat.Clouds.Types.TryGetValue(AmountToString(layer.Amount), out var value))
if (!Station.IsFaaAtis && layer is
{ Amount: CloudLayer.CloudAmount.None, BaseHeight: null, Type: CloudLayer.CloudType.Cumulonimbus })
{
var template = value.Voice;

if (layer.Type == CloudLayer.CloudType.CannotMeasure || layer.BaseHeight == null)
{
template = Regex.Replace(template, "{altitude}",
$" {Station.AtisFormat.Clouds.UndeterminedLayerAltitude.Voice} ", RegexOptions.IgnoreCase);
}
else
{
var height = (int)layer.BaseHeight.ActualValue;
height *= Station.AtisFormat.Clouds.ConvertToMetric ? 30 : 100;
template = Regex.Replace(template, "{altitude}",
Station.AtisFormat.Clouds.ConvertToMetric
? (height < 1000 ? $" {height.ToGroupForm()} " : $" {height.ToWordString()} ") + " meters "
: $" {height.ToWordString()} ", RegexOptions.IgnoreCase);
}

template = Regex.Replace(template, "{convective}",
layer.Type != CloudLayer.CloudType.None
? Station.AtisFormat.Clouds.ConvectiveTypes.GetValueOrDefault(TypeToString(layer.Type), "")
: "", RegexOptions.IgnoreCase);

return Station.AtisFormat.Clouds.IdentifyCeilingLayer &&
layer.Amount != CloudLayer.CloudAmount.VerticalVisibility &&
layer == ceiling
? "ceiling " + template.Trim().ToUpperInvariant()
: template.Trim().ToUpperInvariant();
return Station.AtisFormat.Clouds.AutomaticCbDetection.Voice ?? "RADAR DETECTED C-B CLOUDS.";
}

return "";
if (!Station.AtisFormat.Clouds.Types.TryGetValue(AmountToString(layer.Amount), out var value))
{
return "";
}

var template = value.Voice;

if (layer.BaseHeight == null)
{
template = Regex.Replace(template, "{altitude}",
$" {Station.AtisFormat.Clouds.UndeterminedLayerAltitude.Voice} ", RegexOptions.IgnoreCase);
}
else
{
var height = (int)layer.BaseHeight.ActualValue;
height *= Station.AtisFormat.Clouds.ConvertToMetric ? 30 : 100;
template = Regex.Replace(template, "{altitude}",
Station.AtisFormat.Clouds.ConvertToMetric
? (height < 1000 ? $" {height.ToGroupForm()} " : $" {height.ToWordString()} ") + " meters "
: $" {height.ToWordString()} ", RegexOptions.IgnoreCase);
}

template = Regex.Replace(template, "{convective}",
layer.Type != CloudLayer.CloudType.None
? Station.AtisFormat.Clouds.ConvectiveTypes.GetValueOrDefault(TypeToString(layer.Type), "")
: "", RegexOptions.IgnoreCase);

return Station.AtisFormat.Clouds.IdentifyCeilingLayer &&
layer.Amount != CloudLayer.CloudAmount.VerticalVisibility &&
layer == ceiling
? "ceiling " + template.Trim().ToUpperInvariant()
: template.Trim().ToUpperInvariant();
}
}
33 changes: 33 additions & 0 deletions vATIS.Desktop/Profiles/AtisFormat/Nodes/AutomaticCbDetection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// <copyright file="AutomaticCbDetection.cs" company="Justin Shannon">
// Copyright (c) Justin Shannon. All rights reserved.
// Licensed under the GPLv3 license. See LICENSE file in the project root for full license information.
// </copyright>

namespace Vatsim.Vatis.Profiles.AtisFormat.Nodes;

/// <summary>
/// Represents the automatic CB detection values.
/// </summary>
public class AutomaticCbDetection
{
/// <summary>
/// Initializes a new instance of the <see cref="AutomaticCbDetection"/> class.
/// </summary>
/// <param name="text">The text ATIS value.</param>
/// <param name="voice">The voice ATIS value.</param>
public AutomaticCbDetection(string? text, string? voice)
{
Text = text;
Voice = voice;
}

/// <summary>
/// Gets or sets the text ATIS value.
/// </summary>
public string? Text { get; set; }

/// <summary>
/// Gets or sets the voice ATIS value.
/// </summary>
public string? Voice { get; set; }
}
5 changes: 5 additions & 0 deletions vATIS.Desktop/Profiles/AtisFormat/Nodes/Clouds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ public Clouds()
/// </summary>
public UndeterminedLayer UndeterminedLayerAltitude { get; set; } = new("undetermined", "undetermined");

/// <summary>
/// Gets or sets the automatic CB detection text and voice ATIS values.
/// </summary>
public AutomaticCbDetection AutomaticCbDetection { get; set; } = new("//////CB", "RADAR DETECTED C-B CLOUDS");

/// <summary>
/// Gets or sets the dictionary of cloud types.
/// </summary>
Expand Down
37 changes: 23 additions & 14 deletions vATIS.Desktop/Ui/AtisConfiguration/FormattingView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -565,20 +565,29 @@
</DataGrid>
</TabItem>
<TabItem Header="Convective Cloud Types">
<DataGrid GridLinesVisibility="All" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" BorderThickness="1" BorderBrush="#646464" ItemsSource="{Binding ConvectiveCloudTypes, DataType=vm:FormattingViewModel}">
<Interaction.Behaviors>
<behaviors:DataGridCellEndEditBehavior Command="{Binding CellEditEndingCommand, DataType=vm:FormattingViewModel}"/>
</Interaction.Behaviors>
<DataGrid.Styles>
<Style Selector="DataGrid:focus DataGridCell:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTextColumn Header="Acronym" Width="1*" IsReadOnly="True" Binding="{Binding Key, DataType=models:ConvectiveCloudTypeMeta}"/>
<DataGridTextColumn Header="Spoken" Width="1*" IsReadOnly="False" Binding="{Binding Value, DataType=models:ConvectiveCloudTypeMeta}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Vertical" Spacing="10">
<DataGrid GridLinesVisibility="All" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" BorderThickness="1" BorderBrush="#646464" ItemsSource="{Binding ConvectiveCloudTypes, DataType=vm:FormattingViewModel}">
<Interaction.Behaviors>
<behaviors:DataGridCellEndEditBehavior Command="{Binding CellEditEndingCommand, DataType=vm:FormattingViewModel}"/>
</Interaction.Behaviors>
<DataGrid.Styles>
<Style Selector="DataGrid:focus DataGridCell:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTextColumn Header="Acronym" Width="1*" IsReadOnly="True" Binding="{Binding Key, DataType=models:ConvectiveCloudTypeMeta}"/>
<DataGridTextColumn Header="Spoken" Width="1*" IsReadOnly="False" Binding="{Binding Value, DataType=models:ConvectiveCloudTypeMeta}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Vertical" Spacing="5" IsVisible="{Binding !SelectedStation.IsFaaAtis, DataType=vm:FormattingViewModel, FallbackValue=False}">
<TextBlock Text="Automatic CB Detection" FontSize="16" Margin="0,10,0,0" ToolTip.Tip="When //////CB is presented in the METAR"/>
<TextBlock ToolTip.Tip="The value presented in the text ATIS">Text:</TextBlock>
<TextBox Text="{Binding AutomaticCbDetectionText, DataType=vm:FormattingViewModel}" Theme="{StaticResource DarkTextBox}" Watermark="The value presented in the text ATIS" FontFamily="{StaticResource Monospace}"/>
<TextBlock ToolTip.Tip="The spoken value in the voice ATIS">Voice:</TextBlock>
<TextBox Text="{Binding AutomaticCbDetectionVoice, DataType=vm:FormattingViewModel}" Theme="{StaticResource DarkTextBox}" Watermark="The spoken value in the voice ATIS" FontFamily="{StaticResource Monospace}"/>
</StackPanel>
</StackPanel>
</TabItem>
</TabControl>
</StackPanel>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public class FormattingViewModel : ReactiveViewModelBase, IDisposable
private bool _cloudsConvertToMetric;
private string? _undeterminedLayerAltitudeText;
private string? _undeterminedLayerAltitudeVoice;
private string? _automaticCbDetectionText;
private string? _automaticCbDetectionVoice;
private bool _cloudHeightAltitudeInHundreds;
private bool _temperatureUsePlusPrefix;
private bool _temperatureSpeakLeadingZero;
Expand Down Expand Up @@ -1059,6 +1061,38 @@ public string? UndeterminedLayerAltitudeVoice
}
}

/// <summary>
/// Gets or sets the "automatic CB detection" text ATIS value.
/// </summary>
public string? AutomaticCbDetectionText
{
get => _automaticCbDetectionText;
set
{
this.RaiseAndSetIfChanged(ref _automaticCbDetectionText, value);
if (!_initializedProperties.Add(nameof(AutomaticCbDetectionText)))
{
HasUnsavedChanges = true;
}
}
}

/// <summary>
/// Gets or sets the "automatic CB detection" voice ATIS value.
/// </summary>
public string? AutomaticCbDetectionVoice
{
get => _automaticCbDetectionVoice;
set
{
this.RaiseAndSetIfChanged(ref _automaticCbDetectionVoice, value);
if (!_initializedProperties.Add(nameof(AutomaticCbDetectionVoice)))
{
HasUnsavedChanges = true;
}
}
}

/// <summary>
/// Gets or sets a value indicating whether cloud height altitude is in hundreds.
/// </summary>
Expand Down Expand Up @@ -1662,6 +1696,16 @@ public bool ApplyChanges()
UndeterminedLayerAltitudeVoice ?? string.Empty;
}

if (SelectedStation.AtisFormat.Clouds.AutomaticCbDetection.Text != AutomaticCbDetectionText)
{
SelectedStation.AtisFormat.Clouds.AutomaticCbDetection.Text = AutomaticCbDetectionText;
}

if (SelectedStation.AtisFormat.Clouds.AutomaticCbDetection.Voice != AutomaticCbDetectionVoice)
{
SelectedStation.AtisFormat.Clouds.AutomaticCbDetection.Voice = AutomaticCbDetectionVoice;
}

if (CloudTypes != null && SelectedStation.AtisFormat.Clouds.Types != CloudTypes.ToDictionary(
x => x.Acronym,
meta => new CloudType(meta.Text, meta.Spoken)))
Expand Down Expand Up @@ -1841,6 +1885,8 @@ private void HandleAtisStationChanged(AtisStation? station)
CloudHeightAltitudeInHundreds = station.AtisFormat.Clouds.IsAltitudeInHundreds;
UndeterminedLayerAltitudeText = station.AtisFormat.Clouds.UndeterminedLayerAltitude.Text;
UndeterminedLayerAltitudeVoice = station.AtisFormat.Clouds.UndeterminedLayerAltitude.Voice;
AutomaticCbDetectionText = station.AtisFormat.Clouds.AutomaticCbDetection.Text;
AutomaticCbDetectionVoice = station.AtisFormat.Clouds.AutomaticCbDetection.Voice;
TemperatureUsePlusPrefix = station.AtisFormat.Temperature.UsePlusPrefix;
TemperatureSpeakLeadingZero = station.AtisFormat.Temperature.SpeakLeadingZero;
DewpointUsePlusPrefix = station.AtisFormat.Dewpoint.UsePlusPrefix;
Expand Down

0 comments on commit 6d00fa6

Please sign in to comment.