AI: real met.no implementation
This commit is contained in:
parent
75e5e88c31
commit
20ed4151ff
2 changed files with 83 additions and 14 deletions
|
|
@ -111,7 +111,10 @@ fn handleWeatherInternal(
|
|||
};
|
||||
|
||||
// Fetch weather using coordinates
|
||||
const weather = opts.provider.fetch(allocator, location.name) catch |err| {
|
||||
const coord_str = try std.fmt.allocPrint(allocator, "{d:.4},{d:.4}", .{ location.latitude, location.longitude });
|
||||
defer allocator.free(coord_str);
|
||||
|
||||
const weather = opts.provider.fetch(allocator, coord_str) catch |err| {
|
||||
switch (err) {
|
||||
error.LocationNotFound => {
|
||||
res.status = 404;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ pub const MetNo = struct {
|
|||
fn fetch(ptr: *anyopaque, allocator: std.mem.Allocator, location: []const u8) !types.WeatherData {
|
||||
const self: *MetNo = @ptrCast(@alignCast(ptr));
|
||||
|
||||
const coords = try parseLocation(location);
|
||||
// Parse location as "lat,lon" or use default
|
||||
const coords = parseLocation(location) catch Coords{ .lat = 51.5074, .lon = -0.1278 };
|
||||
|
||||
const url = try std.fmt.allocPrint(
|
||||
self.allocator,
|
||||
|
|
@ -33,6 +34,7 @@ pub const MetNo = struct {
|
|||
);
|
||||
defer self.allocator.free(url);
|
||||
|
||||
// Fetch weather data from met.no API
|
||||
var client = std.http.Client{ .allocator = self.allocator };
|
||||
defer client.deinit();
|
||||
|
||||
|
|
@ -45,7 +47,7 @@ pub const MetNo = struct {
|
|||
.method = .GET,
|
||||
.response_writer = &writer,
|
||||
.extra_headers = &.{
|
||||
.{ .name = "User-Agent", .value = "wttr.in/1.0" },
|
||||
.{ .name = "User-Agent", .value = "wttr.in-zig/1.0 github.com/chubin/wttr.in" },
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -55,15 +57,16 @@ pub const MetNo = struct {
|
|||
|
||||
const response_body = response_buf[0..writer.end];
|
||||
|
||||
const json_data = try std.json.parseFromSlice(
|
||||
// Parse JSON response
|
||||
const parsed = try std.json.parseFromSlice(
|
||||
std.json.Value,
|
||||
self.allocator,
|
||||
allocator,
|
||||
response_body,
|
||||
.{},
|
||||
);
|
||||
defer json_data.deinit();
|
||||
defer parsed.deinit();
|
||||
|
||||
return try parseMetNoResponse(allocator, location, json_data.value);
|
||||
return try parseMetNoResponse(allocator, location, parsed.value);
|
||||
}
|
||||
|
||||
fn deinitProvider(ptr: *anyopaque) void {
|
||||
|
|
@ -83,15 +86,14 @@ const Coords = struct {
|
|||
|
||||
fn parseLocation(location: []const u8) !Coords {
|
||||
if (std.mem.indexOf(u8, location, ",")) |comma_idx| {
|
||||
const lat_str = location[0..comma_idx];
|
||||
const lon_str = location[comma_idx + 1 ..];
|
||||
const lat_str = std.mem.trim(u8, location[0..comma_idx], " ");
|
||||
const lon_str = std.mem.trim(u8, location[comma_idx + 1 ..], " ");
|
||||
return Coords{
|
||||
.lat = try std.fmt.parseFloat(f64, lat_str),
|
||||
.lon = try std.fmt.parseFloat(f64, lon_str),
|
||||
};
|
||||
}
|
||||
|
||||
return Coords{ .lat = 51.5074, .lon = -0.1278 };
|
||||
return error.InvalidLocationFormat;
|
||||
}
|
||||
|
||||
fn parseMetNoResponse(allocator: std.mem.Allocator, location: []const u8, json: std.json.Value) !types.WeatherData {
|
||||
|
|
@ -107,25 +109,34 @@ fn parseMetNoResponse(allocator: std.mem.Allocator, location: []const u8, json:
|
|||
|
||||
const temp_c = @as(f32, @floatCast(details.object.get("air_temperature").?.float));
|
||||
const humidity = @as(u8, @intFromFloat(details.object.get("relative_humidity").?.float));
|
||||
const wind_kph = @as(f32, @floatCast(details.object.get("wind_speed").?.float * 3.6));
|
||||
const wind_speed_ms = details.object.get("wind_speed").?.float;
|
||||
const wind_kph = @as(f32, @floatCast(wind_speed_ms * 3.6));
|
||||
const pressure_mb = @as(f32, @floatCast(details.object.get("air_pressure_at_sea_level").?.float));
|
||||
|
||||
// Get weather symbol
|
||||
const next_1h = data.object.get("next_1_hours");
|
||||
const symbol_code = if (next_1h) |n1h|
|
||||
n1h.object.get("summary").?.object.get("symbol_code").?.string
|
||||
else
|
||||
"clearsky_day";
|
||||
|
||||
// Get wind direction
|
||||
const wind_from_deg = details.object.get("wind_from_direction");
|
||||
const wind_dir = if (wind_from_deg) |deg|
|
||||
degreeToDirection(@as(f32, @floatCast(deg.float)))
|
||||
else
|
||||
"N";
|
||||
|
||||
return types.WeatherData{
|
||||
.location = try allocator.dupe(u8, location),
|
||||
.current = .{
|
||||
.temp_c = temp_c,
|
||||
.temp_f = temp_c * 9.0 / 5.0 + 32.0,
|
||||
.condition = try allocator.dupe(u8, symbol_code),
|
||||
.condition = try allocator.dupe(u8, symbolCodeToCondition(symbol_code)),
|
||||
.weather_code = symbolCodeToWeatherCode(symbol_code),
|
||||
.humidity = humidity,
|
||||
.wind_kph = wind_kph,
|
||||
.wind_dir = try allocator.dupe(u8, "N"),
|
||||
.wind_dir = try allocator.dupe(u8, wind_dir),
|
||||
.pressure_mb = pressure_mb,
|
||||
.precip_mm = 0.0,
|
||||
},
|
||||
|
|
@ -146,3 +157,58 @@ fn symbolCodeToWeatherCode(symbol: []const u8) u16 {
|
|||
if (std.mem.indexOf(u8, symbol, "thunder")) |_| return 200;
|
||||
return 113;
|
||||
}
|
||||
|
||||
fn symbolCodeToCondition(symbol: []const u8) []const u8 {
|
||||
if (std.mem.indexOf(u8, symbol, "clearsky")) |_| return "Clear";
|
||||
if (std.mem.indexOf(u8, symbol, "fair")) |_| return "Fair";
|
||||
if (std.mem.indexOf(u8, symbol, "partlycloudy")) |_| return "Partly cloudy";
|
||||
if (std.mem.indexOf(u8, symbol, "cloudy")) |_| return "Cloudy";
|
||||
if (std.mem.indexOf(u8, symbol, "fog")) |_| return "Fog";
|
||||
if (std.mem.indexOf(u8, symbol, "rain")) |_| return "Rain";
|
||||
if (std.mem.indexOf(u8, symbol, "sleet")) |_| return "Sleet";
|
||||
if (std.mem.indexOf(u8, symbol, "snow")) |_| return "Snow";
|
||||
if (std.mem.indexOf(u8, symbol, "thunder")) |_| return "Thunderstorm";
|
||||
return "Clear";
|
||||
}
|
||||
|
||||
fn degreeToDirection(deg: f32) []const u8 {
|
||||
const normalized = @mod(deg + 22.5, 360.0);
|
||||
const idx = @as(usize, @intFromFloat(normalized / 45.0));
|
||||
const directions = [_][]const u8{ "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
|
||||
return directions[@min(idx, 7)];
|
||||
}
|
||||
|
||||
test "parseLocation with valid coordinates" {
|
||||
const coords = try parseLocation("51.5074,-0.1278");
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 51.5074), coords.lat, 0.0001);
|
||||
try std.testing.expectApproxEqAbs(@as(f64, -0.1278), coords.lon, 0.0001);
|
||||
}
|
||||
|
||||
test "parseLocation with whitespace" {
|
||||
const coords = try parseLocation(" 40.7128 , -74.0060 ");
|
||||
try std.testing.expectApproxEqAbs(@as(f64, 40.7128), coords.lat, 0.0001);
|
||||
try std.testing.expectApproxEqAbs(@as(f64, -74.0060), coords.lon, 0.0001);
|
||||
}
|
||||
|
||||
test "parseLocation with invalid format" {
|
||||
try std.testing.expectError(error.InvalidLocationFormat, parseLocation("London"));
|
||||
}
|
||||
|
||||
test "degreeToDirection" {
|
||||
try std.testing.expectEqualStrings("N", degreeToDirection(0));
|
||||
try std.testing.expectEqualStrings("NE", degreeToDirection(45));
|
||||
try std.testing.expectEqualStrings("E", degreeToDirection(90));
|
||||
try std.testing.expectEqualStrings("SE", degreeToDirection(135));
|
||||
try std.testing.expectEqualStrings("S", degreeToDirection(180));
|
||||
try std.testing.expectEqualStrings("SW", degreeToDirection(225));
|
||||
try std.testing.expectEqualStrings("W", degreeToDirection(270));
|
||||
try std.testing.expectEqualStrings("NW", degreeToDirection(315));
|
||||
}
|
||||
|
||||
test "symbolCodeToWeatherCode" {
|
||||
try std.testing.expectEqual(@as(u16, 113), symbolCodeToWeatherCode("clearsky_day"));
|
||||
try std.testing.expectEqual(@as(u16, 116), symbolCodeToWeatherCode("fair_night"));
|
||||
try std.testing.expectEqual(@as(u16, 119), symbolCodeToWeatherCode("cloudy"));
|
||||
try std.testing.expectEqual(@as(u16, 296), symbolCodeToWeatherCode("lightrain"));
|
||||
try std.testing.expectEqual(@as(u16, 338), symbolCodeToWeatherCode("snow"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue