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

View file

@ -1,6 +1,12 @@
const std = @import("std");
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 {
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 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 visibility = if (options.use_imperial) "6 mi" else "10 km";
const art = getWeatherArt(current.weather_code);
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) {
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} {s} {d:.0} {s}\n", .{ art[2], utils.degreeToDirection(current.wind_deg), wind_speed, wind_unit });
try w.print("{s} {s}\n", .{ art[3], visibility });
try w.print("{s} {s} {d:.0} {s}\n", .{ art[2], degreeToArrow(current.wind_deg), wind_speed, wind_unit });
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 });
} else {
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} \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}\n", .{ cloud_color, art[3], reset, visibility });
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 });
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 });
}
}
@ -155,14 +172,16 @@ fn renderHourlyCell(w: *std.Io.Writer, hour: types.HourlyForecast, line: usize,
.wind => {
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";
// Wind direction is two bytes, so we need to subtract one
// Somehow the actual answer here is two, though?
try cell_writer.print("↑ {d:.0} {s}", .{ wind_speed, wind_unit });
display_width_byte_length_offset = 2;
const arrow = degreeToArrow(hour.wind_deg);
try cell_writer.print("{s} {d:.0} {s}", .{ arrow, wind_speed, wind_unit });
display_width_byte_length_offset = arrow.len - 1;
},
.visibility => {
const visibility = if (options.use_imperial) "6 mi" else "10 km";
try cell_writer.print("{s}", .{visibility});
if (hour.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 cell_writer.print("{d:.0} {s}", .{ visibility, vis_unit });
}
},
.precipitation => {
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,
.pressure_mb = 1013.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -358,6 +378,7 @@ test "clear weather art" {
.wind_deg = 0.0,
.pressure_mb = 1013.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -383,6 +404,7 @@ test "partly cloudy weather art" {
.wind_deg = 45.0,
.pressure_mb = 1013.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -408,6 +430,7 @@ test "cloudy weather art" {
.wind_deg = 90.0,
.pressure_mb = 1010.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -433,6 +456,7 @@ test "rain weather art" {
.wind_deg = 135.0,
.pressure_mb = 1005.0,
.precip_mm = 5.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -457,6 +481,7 @@ test "thunderstorm weather art" {
.wind_deg = 180.0,
.pressure_mb = 1000.0,
.precip_mm = 10.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -481,6 +506,7 @@ test "snow weather art" {
.wind_deg = 225.0,
.pressure_mb = 1008.0,
.precip_mm = 3.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -505,6 +531,7 @@ test "sleet weather art" {
.wind_deg = 270.0,
.pressure_mb = 1007.0,
.precip_mm = 2.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -529,6 +556,7 @@ test "fog weather art" {
.wind_deg = 315.0,
.pressure_mb = 1012.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -553,6 +581,7 @@ test "unknown weather code art" {
.wind_deg = 0.0,
.pressure_mb = 1013.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -580,6 +609,7 @@ test "temperature matches between ansi and custom format" {
.wind_deg = 0.0,
.pressure_mb = 1013.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &.{},
.allocator = std.testing.allocator,
@ -686,8 +716,8 @@ test "plain text format - MetNo real data" {
\\
\\ .-. Light rain
\\ ( ). +7(+7) °C
\\ (___(__) E 6 km/h
\\ ʻ ʻ ʻ ʻ 10 km
\\ (___(__) 6 km/h
\\ ʻ ʻ ʻ ʻ
\\ ʻ ʻ ʻ ʻ 0.0 mm
\\
\\
@ -703,8 +733,8 @@ test "plain text format - MetNo real data" {
\\├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
\\│ .-. Rain │ Cloudy │ .-. Heavy rain │ \ / Partly cloudy │
\\│ ( ). +7(+7) °C │ .--. +6(+6) °C │ ( ). +7(+7) °C │ _ /"".-. +8(+8) °C │
\\│ (___(__) ↑ 5 km/h │ .-( ). ↑ 9 km/h │ (___(__) ↑ 14 km/h │ \_( ). ↑ 12 km/h │
\\│ ʻ ʻ ʻ ʻ 10 km │ (___.__)__) 10 km │ ʻ ʻ ʻ ʻ 10 km │ /(___(__) 10 km
\\│ (___(__) ↖ 5 km/h │ .-( ). ↓ 9 km/h │ (___(__) ↖ 14 km/h │ \_( ). ↑ 12 km/h │
\\│ ʻ ʻ ʻ ʻ │ (___.__)__) │ ʻ ʻ ʻ ʻ │ /(___(__)
\\│ ʻ ʻ ʻ ʻ 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 │
\\│ .--. +10(+10) °C │ .--. +8(+8) °C │ .--. +10(+10) °C │ ( ). +9(+9) °C │
\\│ .-( ). 7 km/h │ .-( ). ↑ 14 km/h │ .-( ). ↑ 31 km/h │ (___(__) ↑ 24 km/h │
\\│ (___.__)__) 10 km │ (___.__)__) 10 km │ (___.__)__) 10 km │ ʻ ʻ ʻ ʻ 10 km
\\│ .-( ). 7 km/h │ .-( ). ↑ 14 km/h │ .-( ). ↑ 31 km/h │ (___(__) ↑ 24 km/h │
\\│ (___.__)__) │ (___.__)__) │ (___.__)__) │ ʻ ʻ ʻ ʻ
\\│ 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,
.pressure_mb = 1013.0,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = &[_]types.ForecastDay{},
.allocator = allocator,

View file

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

View file

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

View file

@ -194,6 +194,7 @@ fn parseMetNoResponse(allocator: std.mem.Allocator, coords: Coordinates, json: s
.wind_deg = wind_deg,
.pressure_mb = pressure_mb,
.precip_mm = 0.0,
.visibility_km = null,
},
.forecast = forecast,
.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 wind_ms = details_obj.object.get("wind_speed") orelse continue;
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)) {
// 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)),
.weather_code = symbolCodeToWeatherCode(symbol_code),
.wind_kph = wind_kph,
.wind_deg = wind_deg,
.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)),
.weather_code = symbolCodeToWeatherCode(symbol_code),
.wind_kph = wind_kph,
.wind_deg = wind_deg,
.precip_mm = precip,
.visibility_km = null,
});
}
}

View file

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