visibility handling

This commit is contained in:
Emil Lerch 2026-01-03 16:54:05 -08:00
parent 52a5bf2543
commit 9d3667b8ec
Signed by: lobo
GPG key ID: A7B62D657EF764F8
7 changed files with 70 additions and 18 deletions

View file

@ -91,6 +91,7 @@ test "render custom format with location and temp" {
.wind_deg = 22.5, .wind_deg = 22.5,
.pressure_mb = 1019.0, .pressure_mb = 1019.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,
@ -118,6 +119,7 @@ test "render custom format with newline" {
.wind_deg = 90.0, .wind_deg = 90.0,
.pressure_mb = 1020.0, .pressure_mb = 1020.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,
@ -144,6 +146,7 @@ test "render custom format with humidity and pressure" {
.wind_deg = 270.0, .wind_deg = 270.0,
.pressure_mb = 1012.0, .pressure_mb = 1012.0,
.precip_mm = 0.2, .precip_mm = 0.2,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,
@ -171,6 +174,7 @@ test "render custom format with imperial units" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 2.5, .precip_mm = 2.5,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,

View file

@ -1,6 +1,12 @@
const std = @import("std"); const std = @import("std");
const types = @import("../weather/types.zig"); const types = @import("../weather/types.zig");
const utils = @import("utils.zig");
fn degreeToArrow(deg: f32) []const u8 {
const normalized = @mod(deg + 22.5, 360.0);
const idx: usize = @intFromFloat(normalized / 45.0);
const arrows = [_][]const u8{ "", "", "", "", "", "", "", "" };
return arrows[@min(idx, 7)];
}
pub const Format = enum { pub const Format = enum {
plain_text, plain_text,
@ -45,7 +51,6 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re
const wind_speed = if (options.use_imperial) current.wind_kph * 0.621371 else current.wind_kph; const wind_speed = if (options.use_imperial) current.wind_kph * 0.621371 else current.wind_kph;
const precip_unit = if (options.use_imperial) "in" else "mm"; const precip_unit = if (options.use_imperial) "in" else "mm";
const precip = if (options.use_imperial) current.precip_mm * 0.0393701 else current.precip_mm; const precip = if (options.use_imperial) current.precip_mm * 0.0393701 else current.precip_mm;
const visibility = if (options.use_imperial) "6 mi" else "10 km";
const art = getWeatherArt(current.weather_code); const art = getWeatherArt(current.weather_code);
const sign: u8 = if (temp >= 0) '+' else '-'; const sign: u8 = if (temp >= 0) '+' else '-';
@ -56,8 +61,14 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re
if (options.format == .plain_text) { if (options.format == .plain_text) {
try w.print("{s} {s}\n", .{ art[0], current.condition }); try w.print("{s} {s}\n", .{ art[0], current.condition });
try w.print("{s} {c}{d:.0}({c}{d:.0}) {s}\n", .{ art[1], sign, abs_temp, fl_sign, abs_fl, temp_unit }); try w.print("{s} {c}{d:.0}({c}{d:.0}) {s}\n", .{ art[1], sign, abs_temp, fl_sign, abs_fl, temp_unit });
try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], utils.degreeToDirection(current.wind_deg), wind_speed, wind_unit }); try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], degreeToArrow(current.wind_deg), wind_speed, wind_unit });
try w.print("{s} {s}\n", .{ art[3], visibility }); if (current.visibility_km) |vis_km| {
const visibility = if (options.use_imperial) vis_km * 0.621371 else vis_km;
const vis_unit = if (options.use_imperial) "mi" else "km";
try w.print("{s} {d:.0} {s}\n", .{ art[3], visibility, vis_unit });
} else {
try w.print("{s}\n", .{std.mem.trimRight(u8, art[3], " ")});
}
try w.print("{s} {d:.1} {s}\n", .{ art[4], precip, precip_unit }); try w.print("{s} {d:.1} {s}\n", .{ art[4], precip, precip_unit });
} else { } else {
const temp_color_code = tempColor(current.temp_c); const temp_color_code = tempColor(current.temp_c);
@ -67,8 +78,14 @@ fn renderCurrent(w: *std.Io.Writer, current: types.CurrentCondition, options: Re
try w.print("{s}{s}{s} {s}\n", .{ cloud_color, art[0], reset, current.condition }); try w.print("{s}{s}{s} {s}\n", .{ cloud_color, art[0], reset, current.condition });
try w.print("{s}{s}{s} \x1b[38;5;{d}m{c}{d:.0}({c}{d:.0}){s} {s}\n", .{ cloud_color, art[1], reset, temp_color_code, sign, abs_temp, fl_sign, abs_fl, reset, temp_unit }); try w.print("{s}{s}{s} \x1b[38;5;{d}m{c}{d:.0}({c}{d:.0}){s} {s}\n", .{ cloud_color, art[1], reset, temp_color_code, sign, abs_temp, fl_sign, abs_fl, reset, temp_unit });
try w.print("{s}{s}{s} {s} \x1b[38;5;{d}m{d:.0}{s} {s}\n", .{ cloud_color, art[2], reset, utils.degreeToDirection(current.wind_deg), wind_color_code, wind_speed, reset, wind_unit }); try w.print("{s}{s}{s} {s} \x1b[38;5;{d}m{d:.0}{s} {s}\n", .{ cloud_color, art[2], reset, degreeToArrow(current.wind_deg), wind_color_code, wind_speed, reset, wind_unit });
try w.print("{s}{s}{s} {s}\n", .{ cloud_color, art[3], reset, visibility }); if (current.visibility_km) |vis_km| {
const visibility = if (options.use_imperial) vis_km * 0.621371 else vis_km;
const vis_unit = if (options.use_imperial) "mi" else "km";
try w.print("{s}{s}{s} {d:.0} {s}\n", .{ cloud_color, art[3], reset, visibility, vis_unit });
} else {
try w.print("{s}{s}{s}\n", .{ cloud_color, std.mem.trimRight(u8, art[3], " "), reset });
}
try w.print("{s}{s}{s} {d:.1} {s}\n", .{ cloud_color, art[4], reset, precip, precip_unit }); try w.print("{s}{s}{s} {d:.1} {s}\n", .{ cloud_color, art[4], reset, precip, precip_unit });
} }
} }
@ -155,14 +172,16 @@ fn renderHourlyCell(w: *std.Io.Writer, hour: types.HourlyForecast, line: usize,
.wind => { .wind => {
const wind_speed = if (options.use_imperial) hour.wind_kph * 0.621371192237 else hour.wind_kph; const wind_speed = if (options.use_imperial) hour.wind_kph * 0.621371192237 else hour.wind_kph;
const wind_unit = if (options.use_imperial) "mph" else "km/h"; const wind_unit = if (options.use_imperial) "mph" else "km/h";
// Wind direction is two bytes, so we need to subtract one const arrow = degreeToArrow(hour.wind_deg);
// Somehow the actual answer here is two, though? try cell_writer.print("{s} {d:.0} {s}", .{ arrow, wind_speed, wind_unit });
try cell_writer.print("↑ {d:.0} {s}", .{ wind_speed, wind_unit }); display_width_byte_length_offset = arrow.len - 1;
display_width_byte_length_offset = 2;
}, },
.visibility => { .visibility => {
const visibility = if (options.use_imperial) "6 mi" else "10 km"; if (hour.visibility_km) |vis_km| {
try cell_writer.print("{s}", .{visibility}); const visibility = if (options.use_imperial) vis_km * 0.621371 else vis_km;
const vis_unit = if (options.use_imperial) "mi" else "km";
try cell_writer.print("{d:.0} {s}", .{ visibility, vis_unit });
}
}, },
.precipitation => { .precipitation => {
const precip = if (options.use_imperial) hour.precip_mm * 0.0393701 else hour.precip_mm; const precip = if (options.use_imperial) hour.precip_mm * 0.0393701 else hour.precip_mm;
@ -331,6 +350,7 @@ test "render with imperial units" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -358,6 +378,7 @@ test "clear weather art" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -383,6 +404,7 @@ test "partly cloudy weather art" {
.wind_deg = 45.0, .wind_deg = 45.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -408,6 +430,7 @@ test "cloudy weather art" {
.wind_deg = 90.0, .wind_deg = 90.0,
.pressure_mb = 1010.0, .pressure_mb = 1010.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -433,6 +456,7 @@ test "rain weather art" {
.wind_deg = 135.0, .wind_deg = 135.0,
.pressure_mb = 1005.0, .pressure_mb = 1005.0,
.precip_mm = 5.0, .precip_mm = 5.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -457,6 +481,7 @@ test "thunderstorm weather art" {
.wind_deg = 180.0, .wind_deg = 180.0,
.pressure_mb = 1000.0, .pressure_mb = 1000.0,
.precip_mm = 10.0, .precip_mm = 10.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -481,6 +506,7 @@ test "snow weather art" {
.wind_deg = 225.0, .wind_deg = 225.0,
.pressure_mb = 1008.0, .pressure_mb = 1008.0,
.precip_mm = 3.0, .precip_mm = 3.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -505,6 +531,7 @@ test "sleet weather art" {
.wind_deg = 270.0, .wind_deg = 270.0,
.pressure_mb = 1007.0, .pressure_mb = 1007.0,
.precip_mm = 2.0, .precip_mm = 2.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -529,6 +556,7 @@ test "fog weather art" {
.wind_deg = 315.0, .wind_deg = 315.0,
.pressure_mb = 1012.0, .pressure_mb = 1012.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -553,6 +581,7 @@ test "unknown weather code art" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -580,6 +609,7 @@ test "temperature matches between ansi and custom format" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -686,8 +716,8 @@ test "plain text format - MetNo real data" {
\\ \\
\\ .-. Light rain \\ .-. Light rain
\\ ( ). +7(+7) °C \\ ( ). +7(+7) °C
\\ (___(__) E 6 km/h \\ (___(__) 6 km/h
\\ ʻ ʻ ʻ ʻ 10 km \\ ʻ ʻ ʻ ʻ
\\ ʻ ʻ ʻ ʻ 0.0 mm \\ ʻ ʻ ʻ ʻ 0.0 mm
\\ \\
\\ \\
@ -703,8 +733,8 @@ test "plain text format - MetNo real data" {
\\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤ \\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
\\│ .-. Rain │ Cloudy │ .-. Heavy rain │ \ / Partly cloudy │ \\│ .-. Rain │ Cloudy │ .-. Heavy rain │ \ / Partly cloudy │
\\│ ( ). +7(+7) °C │ .--. +6(+6) °C │ ( ). +7(+7) °C │ _ /"".-. +8(+8) °C │ \\│ ( ). +7(+7) °C │ .--. +6(+6) °C │ ( ). +7(+7) °C │ _ /"".-. +8(+8) °C │
\\│ (___(__) ↑ 5 km/h │ .-( ). ↑ 9 km/h │ (___(__) ↑ 14 km/h │ \_( ). ↑ 12 km/h │ \\│ (___(__) ↖ 5 km/h │ .-( ). ↓ 9 km/h │ (___(__) ↖ 14 km/h │ \_( ). ↑ 12 km/h │
\\│ ʻ ʻ ʻ ʻ 10 km │ (___.__)__) 10 km │ ʻ ʻ ʻ ʻ 10 km │ /(___(__) 10 km \\│ ʻ ʻ ʻ ʻ │ (___.__)__) │ ʻ ʻ ʻ ʻ │ /(___(__)
\\│ ʻ ʻ ʻ ʻ 0.3 mm | 0% │ 0.0 mm | 0% │ ʻ ʻ ʻ ʻ 1.2 mm | 0% │ 0.0 mm | 0% │ \\│ ʻ ʻ ʻ ʻ 0.3 mm | 0% │ 0.0 mm | 0% │ ʻ ʻ ʻ ʻ 1.2 mm | 0% │ 0.0 mm | 0% │
\\└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ \\└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
\\ ┌─────────────┐ \\ ┌─────────────┐
@ -713,8 +743,8 @@ test "plain text format - MetNo real data" {
\\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤ \\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
\\│ Cloudy │ Cloudy │ Cloudy │ .-. Light rain │ \\│ Cloudy │ Cloudy │ Cloudy │ .-. Light rain │
\\│ .--. +10(+10) °C │ .--. +8(+8) °C │ .--. +10(+10) °C │ ( ). +9(+9) °C │ \\│ .--. +10(+10) °C │ .--. +8(+8) °C │ .--. +10(+10) °C │ ( ). +9(+9) °C │
\\│ .-( ). 7 km/h │ .-( ). ↑ 14 km/h │ .-( ). ↑ 31 km/h │ (___(__) ↑ 24 km/h │ \\│ .-( ). 7 km/h │ .-( ). ↑ 14 km/h │ .-( ). ↑ 31 km/h │ (___(__) ↑ 24 km/h │
\\│ (___.__)__) 10 km │ (___.__)__) 10 km │ (___.__)__) 10 km │ ʻ ʻ ʻ ʻ 10 km \\│ (___.__)__) │ (___.__)__) │ (___.__)__) │ ʻ ʻ ʻ ʻ
\\│ 0.0 mm | 0% │ 0.0 mm | 0% │ 0.0 mm | 0% │ ʻ ʻ ʻ ʻ 0.2 mm | 0% │ \\│ 0.0 mm | 0% │ 0.0 mm | 0% │ 0.0 mm | 0% │ ʻ ʻ ʻ ʻ 0.2 mm | 0% │
\\└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘ \\└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
\\ \\

View file

@ -34,6 +34,7 @@ test "render json format" {
.wind_deg = 225.0, .wind_deg = 225.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,

View file

@ -131,6 +131,7 @@ test "format 1" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -155,6 +156,7 @@ test "custom format" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,
@ -179,6 +181,7 @@ test "format 2 with imperial units" {
.wind_deg = 135.0, .wind_deg = 135.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &.{}, .forecast = &.{},
.allocator = std.testing.allocator, .allocator = std.testing.allocator,

View file

@ -81,6 +81,7 @@ test "render v2 format" {
.wind_deg = 315.0, .wind_deg = 315.0,
.pressure_mb = 1015.0, .pressure_mb = 1015.0,
.precip_mm = 0.5, .precip_mm = 0.5,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,
@ -110,6 +111,7 @@ test "render v2 format with imperial units" {
.wind_deg = 0.0, .wind_deg = 0.0,
.pressure_mb = 1013.0, .pressure_mb = 1013.0,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = &[_]types.ForecastDay{}, .forecast = &[_]types.ForecastDay{},
.allocator = allocator, .allocator = allocator,

View file

@ -194,6 +194,7 @@ fn parseMetNoResponse(allocator: std.mem.Allocator, coords: Coordinates, json: s
.wind_deg = wind_deg, .wind_deg = wind_deg,
.pressure_mb = pressure_mb, .pressure_mb = pressure_mb,
.precip_mm = 0.0, .precip_mm = 0.0,
.visibility_km = null,
}, },
.forecast = forecast, .forecast = forecast,
.allocator = allocator, .allocator = allocator,
@ -244,6 +245,10 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value)
const temp: f32 = @floatCast(details_obj.object.get("air_temperature").?.float); const temp: f32 = @floatCast(details_obj.object.get("air_temperature").?.float);
const wind_ms = details_obj.object.get("wind_speed") orelse continue; const wind_ms = details_obj.object.get("wind_speed") orelse continue;
const wind_kph: f32 = @floatCast(wind_ms.float * 3.6); const wind_kph: f32 = @floatCast(wind_ms.float * 3.6);
const wind_deg: f32 = if (details_obj.object.get("wind_from_direction")) |deg|
@floatCast(deg.float)
else
0.0;
if (current_date == null or !std.mem.eql(u8, current_date.?, date)) { if (current_date == null or !std.mem.eql(u8, current_date.?, date)) {
// Save previous day if exists // Save previous day if exists
@ -330,7 +335,9 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value)
.condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)), .condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)),
.weather_code = symbolCodeToWeatherCode(symbol_code), .weather_code = symbolCodeToWeatherCode(symbol_code),
.wind_kph = wind_kph, .wind_kph = wind_kph,
.wind_deg = wind_deg,
.precip_mm = precip, .precip_mm = precip,
.visibility_km = null,
}); });
} }
@ -353,7 +360,9 @@ fn parseForecastDays(allocator: std.mem.Allocator, timeseries: []std.json.Value)
.condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)), .condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)),
.weather_code = symbolCodeToWeatherCode(symbol_code), .weather_code = symbolCodeToWeatherCode(symbol_code),
.wind_kph = wind_kph, .wind_kph = wind_kph,
.wind_deg = wind_deg,
.precip_mm = precip, .precip_mm = precip,
.visibility_km = null,
}); });
} }
} }

View file

@ -116,6 +116,7 @@ pub const CurrentCondition = struct {
wind_deg: f32, wind_deg: f32,
pressure_mb: f32, pressure_mb: f32,
precip_mm: f32, precip_mm: f32,
visibility_km: ?f32,
pub fn tempFahrenheit(self: CurrentCondition) f32 { pub fn tempFahrenheit(self: CurrentCondition) f32 {
return celsiusToFahrenheit(self.temp_c); return celsiusToFahrenheit(self.temp_c);
@ -148,5 +149,7 @@ pub const HourlyForecast = struct {
condition: []const u8, condition: []const u8,
weather_code: WeatherCode, weather_code: WeatherCode,
wind_kph: f32, wind_kph: f32,
wind_deg: f32,
precip_mm: f32, precip_mm: f32,
visibility_km: ?f32,
}; };