125 lines
4.4 KiB
Zig
125 lines
4.4 KiB
Zig
const std = @import("std");
|
|
const Coordinates = @import("../Coordinates.zig");
|
|
|
|
pub const Airport = struct {
|
|
iata: []const u8,
|
|
name: []const u8,
|
|
coords: Coordinates,
|
|
};
|
|
|
|
const Airports = @This();
|
|
|
|
arena: std.heap.ArenaAllocator,
|
|
airports: std.StringHashMap(Airport),
|
|
|
|
pub fn init(allocator: std.mem.Allocator) !Airports {
|
|
const csv_data = @embedFile("airports.dat");
|
|
return try initFromData(allocator, csv_data);
|
|
}
|
|
|
|
pub fn initFromData(allocator: std.mem.Allocator, csv_data: []const u8) !Airports {
|
|
var arena = std.heap.ArenaAllocator.init(allocator);
|
|
const alloc = arena.allocator();
|
|
var airports = std.StringHashMap(Airport).init(alloc);
|
|
|
|
var lines = std.mem.splitScalar(u8, csv_data, '\n');
|
|
while (lines.next()) |line| {
|
|
if (line.len == 0) continue;
|
|
|
|
const airport = parseAirportLine(alloc, line) catch continue;
|
|
if (airport.iata.len == 3 and airports.get(airport.iata) == null) {
|
|
try airports.put(airport.iata, airport);
|
|
}
|
|
}
|
|
|
|
return Airports{
|
|
.arena = arena,
|
|
.airports = airports,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Airports) void {
|
|
self.arena.deinit();
|
|
}
|
|
|
|
pub fn lookup(self: *Airports, iata_code: []const u8) ?Airport {
|
|
return self.airports.get(iata_code);
|
|
}
|
|
|
|
fn parseAirportLine(allocator: std.mem.Allocator, line: []const u8) !Airport {
|
|
// CSV format: ID,Name,City,Country,IATA,ICAO,Lat,Lon,...
|
|
var fields = std.mem.splitScalar(u8, line, ',');
|
|
|
|
_ = fields.next() orelse return error.InvalidFormat; // ID
|
|
const name_quoted = fields.next() orelse return error.InvalidFormat; // Name
|
|
_ = fields.next() orelse return error.InvalidFormat; // City
|
|
_ = fields.next() orelse return error.InvalidFormat; // Country
|
|
const iata_quoted = fields.next() orelse return error.InvalidFormat; // IATA
|
|
_ = fields.next() orelse return error.InvalidFormat; // ICAO
|
|
const lat_str = fields.next() orelse return error.InvalidFormat; // Lat
|
|
const lon_str = fields.next() orelse return error.InvalidFormat; // Lon
|
|
|
|
// Remove quotes from fields
|
|
const name = try unquote(allocator, name_quoted);
|
|
const iata = try unquote(allocator, iata_quoted);
|
|
|
|
// Skip if IATA is "\\N" (null)
|
|
if (std.mem.eql(u8, iata, "\\N")) {
|
|
allocator.free(name);
|
|
allocator.free(iata);
|
|
return error.NoIATA;
|
|
}
|
|
|
|
const lat = try std.fmt.parseFloat(f64, lat_str);
|
|
const lon = try std.fmt.parseFloat(f64, lon_str);
|
|
|
|
return Airport{
|
|
.iata = iata,
|
|
.name = name,
|
|
.coords = .{
|
|
.latitude = lat,
|
|
.longitude = lon,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn unquote(allocator: std.mem.Allocator, quoted: []const u8) ![]const u8 {
|
|
if (quoted.len >= 2 and quoted[0] == '"' and quoted[quoted.len - 1] == '"') {
|
|
return allocator.dupe(u8, quoted[1 .. quoted.len - 1]);
|
|
}
|
|
return allocator.dupe(u8, quoted);
|
|
}
|
|
|
|
test "parseAirportLine valid" {
|
|
const allocator = std.testing.allocator;
|
|
const line = "1,\"Goroka Airport\",\"Goroka\",\"Papua New Guinea\",\"GKA\",\"AYGA\",-6.081689834590001,145.391998291,5282,10,\"U\",\"Pacific/Port_Moresby\",\"airport\",\"OurAirports\"";
|
|
|
|
const airport = try Airports.parseAirportLine(allocator, line);
|
|
defer allocator.free(airport.iata);
|
|
defer allocator.free(airport.name);
|
|
|
|
try std.testing.expectEqualStrings("GKA", airport.iata);
|
|
try std.testing.expectEqualStrings("Goroka Airport", airport.name);
|
|
try std.testing.expectApproxEqAbs(@as(f64, -6.081689834590001), airport.coords.latitude, 0.0001);
|
|
try std.testing.expectApproxEqAbs(@as(f64, 145.391998291), airport.coords.longitude, 0.0001);
|
|
}
|
|
|
|
test "parseAirportLine with null IATA" {
|
|
const allocator = std.testing.allocator;
|
|
const line = "1,\"Test Airport\",\"City\",\"Country\",\"\\N\",\"ICAO\",0.0,0.0";
|
|
|
|
try std.testing.expectError(error.NoIATA, Airports.parseAirportLine(allocator, line));
|
|
}
|
|
|
|
test "AirportDB lookup" {
|
|
const allocator = std.testing.allocator;
|
|
const csv = "1,\"Munich Airport\",\"Munich\",\"Germany\",\"MUC\",\"EDDM\",48.353802,11.7861,1487,1,\"E\",\"Europe/Berlin\",\"airport\",\"OurAirports\"";
|
|
|
|
var db = try Airports.initFromData(allocator, csv);
|
|
defer db.deinit();
|
|
|
|
const result = db.lookup("MUC");
|
|
try std.testing.expect(result != null);
|
|
try std.testing.expectEqualStrings("Munich Airport", result.?.name);
|
|
try std.testing.expectApproxEqAbs(@as(f64, 48.353802), result.?.coords.latitude, 0.0001);
|
|
}
|