diff --git a/REUSE.toml b/REUSE.toml index e717c57..04bf7cf 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -10,5 +10,5 @@ SPDX-License-Identifier = "CC0-1.0" [[annotations]] path = "src/proto/OSMPBF.pb.zig" -SPDX-FileCopyrightText = "OpenStreetmap Developers" +SPDX-FileCopyrightText = "OpenStreetmap developers" SPDX-License-Identifier = "MIT" diff --git a/src/DenseNodeIterator.zig b/src/DenseNodeIterator.zig new file mode 100644 index 0000000..6532459 --- /dev/null +++ b/src/DenseNodeIterator.zig @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const DenseNodeIterator = @This(); + +const std = @import("std"); + +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +const Node = @import("Node.zig"); + +block: *const OSMPBF.PrimitiveBlock, +group: *const OSMPBF.PrimitiveGroup, +index: usize, +kv_index: usize, +delta: struct { + id: i64, + lat: i64, + lon: i64, + version: i32, + timestamp: i64, + changeset: i64, + uid: i32, + user_sid: i32, + + pub inline fn acc( + self: *@This(), + comptime field: std.meta.FieldEnum(@This()), + delta: @FieldType(@This(), @tagName(field)), + ) @FieldType(@This(), @tagName(field)) { + @field(self, @tagName(field)) += delta; + return @field(self, @tagName(field)); + } +}, + +pub fn init(block: *const OSMPBF.PrimitiveBlock, group: *const OSMPBF.PrimitiveGroup) DenseNodeIterator { + return .{ + .block = block, + .group = group, + .index = 0, + .kv_index = 0, + .delta = .{ + .id = 0, + .lat = 0, + .lon = 0, + .version = 0, + .timestamp = 0, + .changeset = 0, + .uid = 0, + .user_sid = 0, + }, + }; +} + +pub fn next(self: *DenseNodeIterator, alloc: std.mem.Allocator) !?*Node { + const dense = &(self.group.dense orelse return null); + if (self.index >= dense.id.items.len) return null; + const tmp = try alloc.create(Node); + tmp.* = .{ + .id = self.delta.acc(.id, dense.id.items[self.index]), + .latitude = .init( + self.delta.acc(.lat, dense.lat.items[self.index]), + self.block.lat_offset orelse 0, + self.block.granularity orelse 100, + ), + .longitude = .init( + self.delta.acc(.lon, dense.lon.items[self.index]), + self.block.lon_offset orelse 0, + self.block.granularity orelse 100, + ), + .version = if (dense.denseinfo) |*di| self.delta.acc(.version, di.version.items[self.index]) else null, + .timestamp = .init( + if (dense.denseinfo) |*di| self.delta.acc(.timestamp, di.timestamp.items[self.index]) else null, + self.block.date_granularity orelse 1000, + ), + .changeset = if (dense.denseinfo) |*di| self.delta.acc(.changeset, di.changeset.items[self.index]) else null, + .uid = if (dense.denseinfo) |*di| self.delta.acc(.uid, di.uid.items[self.index]) else null, + .user_sid = user_sid: { + const di = &(dense.denseinfo orelse break :user_sid null); + const st = &(self.block.stringtable orelse break :user_sid null); + const user_sid_ = self.delta.acc(.user_sid, di.user_sid.items[self.index]); + if (user_sid_ <= 0) return error.StringIndexOutOfRange; + break :user_sid try alloc.dupe(u8, st.s.items[@intCast(user_sid_)]); + }, + .visible = visible: { + const di = &(dense.denseinfo orelse break :visible null); + if (self.index >= di.visible.items.len) break :visible null; + break :visible di.visible.items[self.index]; + }, + .kv = kv: { + var kv: std.StringArrayHashMapUnmanaged([]const u8) = .empty; + const st = &(self.block.stringtable orelse break :kv kv); + const kvl = dense.keys_vals.items; + while (self.kv_index < kvl.len) { + const key_index_ = kvl[self.kv_index]; + if (key_index_ < 0) return error.InvalidStringIndex; + const key_index: usize = @intCast(key_index_); + self.kv_index += 1; + if (key_index == 0) break; + if (key_index >= st.s.items.len) return error.StringIndexOutOfRange; + const key = st.s.items[key_index]; + + const value_index_ = kvl[self.kv_index]; + if (value_index_ < 0) return error.StringIndexOutOfRange; + const value_index: usize = @intCast(value_index_); + self.kv_index += 1; + if (value_index >= st.s.items.len) return error.StringIndexOutOfRange; + const value = st.s.items[value_index]; + + const gop = try kv.getOrPut(alloc, key); + if (gop.found_existing) { + alloc.free(gop.value_ptr.*); + } else { + gop.key_ptr.* = try alloc.dupe(u8, key); + } + gop.value_ptr.* = try alloc.dupe(u8, value); + } + break :kv kv; + }, + }; + self.index += 1; + + return tmp; +} diff --git a/src/Node.zig b/src/Node.zig index 29e15bc..580a8c6 100644 --- a/src/Node.zig +++ b/src/Node.zig @@ -6,36 +6,12 @@ const Node = @This(); const std = @import("std"); const LatLon = @import("latlon.zig").LatLon; - -pub const KV = struct { - key: []const u8, - value: []const u8, -}; +const Timestamp = @import("Timestamp.zig"); id: i64, latitude: LatLon(.latitude), longitude: LatLon(.longitude), -timestamp: struct { - timestamp: ?i64, - granularity: i32, - - pub fn init(timestamp: ?i64, granularity: i32) @This() { - return .{ - .timestamp = timestamp, - .granularity = granularity, - }; - } - - pub fn format(self: *const @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { - const timestamp = timestamp: { - if (self.timestamp) |ts| break :timestamp ts; - try writer.print("null", .{}); - return; - }; - const millisecond_stamp = timestamp * self.granularity; - try writer.print("{d}", .{millisecond_stamp}); - } -}, +timestamp: Timestamp, version: ?i32, changeset: ?i64, uid: ?i32, diff --git a/src/PBF.zig b/src/PBF.zig index 5974ccf..a4e260e 100644 --- a/src/PBF.zig +++ b/src/PBF.zig @@ -5,9 +5,12 @@ const PBF = @This(); const std = @import("std"); +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +const DenseNodeIterator = @import("DenseNodeIterator.zig"); const LatLon = @import("latlon.zig").LatLon; const Node = @import("Node.zig"); -const OSMPBF = @import("proto/OSMPBF.pb.zig"); +const PrimitiveBlock = @import("PrimitiveBlock.zig"); reader: *std.Io.Reader, @@ -21,7 +24,7 @@ pub const Block = struct { arena: std.heap.ArenaAllocator, block: union(enum) { header: OSMPBF.HeaderBlock, - primitive: OSMPBF.PrimitiveBlock, + primitive: PrimitiveBlock, }, pub fn deinit(self: *const Block) void { @@ -29,124 +32,6 @@ pub const Block = struct { } }; -pub const DenseNodeIterator = struct { - block: *const OSMPBF.PrimitiveBlock, - group: *const OSMPBF.PrimitiveGroup, - dense: *const OSMPBF.DenseNodes, - index: usize, - kv_index: usize, - delta: struct { - id: i64, - lat: i64, - lon: i64, - version: i32, - timestamp: i64, - changeset: i64, - uid: i32, - user_sid: i32, - - pub inline fn acc( - self: *@This(), - comptime field: std.meta.FieldEnum(@This()), - delta: @FieldType(@This(), @tagName(field)), - ) @FieldType(@This(), @tagName(field)) { - @field(self, @tagName(field)) += delta; - return @field(self, @tagName(field)); - } - }, - - pub fn init(block: *const OSMPBF.PrimitiveBlock, group: *const OSMPBF.PrimitiveGroup) !DenseNodeIterator { - const dense = &(group.dense orelse return error.NoDenseNodes); - return .{ - .block = block, - .group = group, - .dense = dense, - .index = 0, - .kv_index = 0, - .delta = .{ - .id = 0, - .lat = 0, - .lon = 0, - .version = 0, - .timestamp = 0, - .changeset = 0, - .uid = 0, - .user_sid = 0, - }, - }; - } - - pub fn next(self: *DenseNodeIterator, alloc: std.mem.Allocator) !?*Node { - if (self.index >= self.dense.id.items.len) return null; - const tmp = try alloc.create(Node); - tmp.* = .{ - .id = self.delta.acc(.id, self.dense.id.items[self.index]), - .latitude = .init( - self.delta.acc(.lat, self.dense.lat.items[self.index]), - self.block.lat_offset orelse 0, - self.block.granularity orelse 100, - ), - .longitude = .init( - self.delta.acc(.lon, self.dense.lon.items[self.index]), - self.block.lon_offset orelse 0, - self.block.granularity orelse 100, - ), - .version = if (self.dense.denseinfo) |*di| self.delta.acc(.version, di.version.items[self.index]) else null, - .timestamp = .init( - if (self.dense.denseinfo) |*di| self.delta.acc(.timestamp, di.timestamp.items[self.index]) else null, - self.block.date_granularity orelse 1000, - ), - .changeset = if (self.dense.denseinfo) |*di| self.delta.acc(.changeset, di.changeset.items[self.index]) else null, - .uid = if (self.dense.denseinfo) |*di| self.delta.acc(.uid, di.uid.items[self.index]) else null, - .user_sid = user_sid: { - const di = &(self.dense.denseinfo orelse break :user_sid null); - const st = &(self.block.stringtable orelse break :user_sid null); - const user_sid_ = self.delta.acc(.user_sid, di.user_sid.items[self.index]); - if (user_sid_ <= 0) return error.StringIndexOutOfRange; - break :user_sid try alloc.dupe(u8, st.s.items[@intCast(user_sid_)]); - }, - .visible = visible: { - const di = &(self.dense.denseinfo orelse break :visible null); - if (self.index >= di.visible.items.len) break :visible null; - break :visible di.visible.items[self.index]; - }, - .kv = kv: { - var kv: std.StringArrayHashMapUnmanaged([]const u8) = .empty; - const st = &(self.block.stringtable orelse break :kv kv); - const kvl = self.dense.keys_vals.items; - while (self.kv_index < kvl.len) { - const key_index_ = kvl[self.kv_index]; - if (key_index_ < 0) return error.InvalidStringIndex; - const key_index: usize = @intCast(key_index_); - self.kv_index += 1; - if (key_index == 0) break; - if (key_index >= st.s.items.len) return error.StringIndexOutOfRange; - const key = st.s.items[key_index]; - - const value_index_ = kvl[self.kv_index]; - if (value_index_ < 0) return error.StringIndexOutOfRange; - const value_index: usize = @intCast(value_index_); - self.kv_index += 1; - if (value_index >= st.s.items.len) return error.StringIndexOutOfRange; - const value = st.s.items[value_index]; - - const gop = try kv.getOrPut(alloc, key); - if (gop.found_existing) { - alloc.free(gop.value_ptr.*); - } else { - gop.key_ptr.* = try alloc.dupe(u8, key); - } - gop.value_ptr.* = try alloc.dupe(u8, value); - } - break :kv kv; - }, - }; - self.index += 1; - - return tmp; - } -}; - const BlobType = enum { OSMHeader, OSMData, @@ -233,7 +118,7 @@ pub fn next(self: *PBF, gpa: std.mem.Allocator) !?Block { return .{ .arena = arena, .block = .{ - .primitive = primitive, + .primitive = .init(primitive), }, }; }, @@ -264,48 +149,64 @@ test "sample" { std.log.warn("{d} {d} {d} {d}", .{ bbox.top, bbox.left, bbox.bottom, bbox.right }); } }, - .primitive => |*primitive| { + .primitive => |*primitive_block| { std.log.warn("primitive", .{}); - std.log.warn(" date granularity: {?d}", .{primitive.date_granularity}); - std.log.warn(" granularity: {?d}", .{primitive.granularity}); - std.log.warn(" lat_offset: {?d}", .{primitive.lat_offset}); - std.log.warn(" lon_offset: {?d}", .{primitive.lon_offset}); - for (primitive.primitivegroup.items) |*pg| { - for (pg.nodes.items) |*node| { - _ = node; - std.log.warn(" node", .{}); - } - if (pg.dense) |*dense| { - std.log.warn(" dense", .{}); - std.log.warn(" id len {d}", .{dense.id.items.len}); - std.log.warn(" lat len {d}", .{dense.lat.items.len}); - std.log.warn(" lon len {d}", .{dense.lon.items.len}); - std.log.warn(" keys_vals {d}", .{dense.keys_vals.items.len}); - if (dense.denseinfo) |*denseinfo| { - std.log.warn(" dense info changeset {d}", .{denseinfo.changeset.items.len}); - std.log.warn(" dense info timestamp {d}", .{denseinfo.timestamp.items.len}); - std.log.warn(" dense info uid {d}", .{denseinfo.uid.items.len}); - std.log.warn(" dense info user_sid {d}", .{denseinfo.user_sid.items.len}); - std.log.warn(" dense info version {d}", .{denseinfo.version.items.len}); - std.log.warn(" dense info visible {d}", .{denseinfo.visible.items.len}); - } - var it: DenseNodeIterator = try .init(primitive, pg); - while (try it.next(std.testing.allocator)) |n| { - defer n.deinit(std.testing.allocator); - std.log.warn(" id: {f} lat: {d} lon: {d}", .{ n, n.latitude.latlon, n.longitude.latlon }); - std.log.warn(" {f} {f}", .{ n.latitude, n.longitude }); - std.log.warn(" timestamp: {f}", .{n.timestamp}); - std.log.warn(" version: {?d}", .{n.version}); - std.log.warn(" uid: {?d}", .{n.uid}); - std.log.warn(" user_sid: {?s}", .{n.user_sid}); - std.log.warn(" visible: {?}", .{n.visible}); - - var kvi = n.kv.iterator(); - while (kvi.next()) |kv| { - std.log.warn(" '{s}'='{s}'", .{ kv.key_ptr.*, kv.value_ptr.* }); + std.log.warn(" date granularity: {?d}", .{primitive_block.block.date_granularity}); + std.log.warn(" granularity: {?d}", .{primitive_block.block.granularity}); + std.log.warn(" lat_offset: {?d}", .{primitive_block.block.lat_offset}); + std.log.warn(" lon_offset: {?d}", .{primitive_block.block.lon_offset}); + var pg_it = primitive_block.iterator(); + while (pg_it.next()) |pg| { + switch (pg.contains()) { + .nodes => { + std.log.warn(" node", .{}); + }, + .dense_nodes => { + if (pg.group.dense) |*dense| { + std.log.warn(" dense", .{}); + std.log.warn(" id len {d}", .{dense.id.items.len}); + std.log.warn(" lat len {d}", .{dense.lat.items.len}); + std.log.warn(" lon len {d}", .{dense.lon.items.len}); + std.log.warn(" keys_vals {d}", .{dense.keys_vals.items.len}); + if (dense.denseinfo) |*denseinfo| { + std.log.warn(" dense info changeset {d}", .{denseinfo.changeset.items.len}); + std.log.warn(" dense info timestamp {d}", .{denseinfo.timestamp.items.len}); + std.log.warn(" dense info uid {d}", .{denseinfo.uid.items.len}); + std.log.warn(" dense info user_sid {d}", .{denseinfo.user_sid.items.len}); + std.log.warn(" dense info version {d}", .{denseinfo.version.items.len}); + std.log.warn(" dense info visible {d}", .{denseinfo.visible.items.len}); + } + } + var it = pg.denseNodeIterator(); + while (try it.next(std.testing.allocator)) |n| { + defer n.deinit(std.testing.allocator); + std.log.warn(" id: {f} lat: {d} lon: {d}", .{ n, n.latitude.latlon, n.longitude.latlon }); + std.log.warn(" {f} {f}", .{ n.latitude, n.longitude }); + std.log.warn(" timestamp: {f}", .{n.timestamp}); + std.log.warn(" version: {?d}", .{n.version}); + std.log.warn(" uid: {?d}", .{n.uid}); + std.log.warn(" user_sid: {?s}", .{n.user_sid}); + std.log.warn(" visible: {?}", .{n.visible}); + + var kvi = n.kv.iterator(); + while (kvi.next()) |kv| { + std.log.warn(" '{s}'='{s}'", .{ kv.key_ptr.*, kv.value_ptr.* }); + } } - } - std.log.warn("kv_index: {d}", .{it.kv_index}); + std.log.warn("kv_index: {d}", .{it.kv_index}); + }, + .ways => { + std.log.warn(" ways", .{}); + }, + .relations => { + std.log.warn(" relations", .{}); + }, + .changesets => { + std.log.warn(" changesets", .{}); + }, + .empty => { + std.log.warn(" empty ", .{}); + }, } } }, diff --git a/src/PrimitiveBlock.zig b/src/PrimitiveBlock.zig new file mode 100644 index 0000000..21e4ff1 --- /dev/null +++ b/src/PrimitiveBlock.zig @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const PrimitiveBlock = @This(); + +const std = @import("std"); + +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +const DenseNodeIterator = @import("DenseNodeIterator.zig"); +const PrimitiveGroup = @import("PrimitiveGroup.zig"); + +block: OSMPBF.PrimitiveBlock, + +pub fn init(block: OSMPBF.PrimitiveBlock) PrimitiveBlock { + return .{ + .block = block, + }; +} + +pub const PrimitiveBlockIterator = struct { + block: *const OSMPBF.PrimitiveBlock, + index: usize, + + pub fn init(block: *const OSMPBF.PrimitiveBlock) PrimitiveBlockIterator { + return .{ + .block = block, + .index = 0, + }; + } + + pub fn next(self: *PrimitiveBlockIterator) ?PrimitiveGroup { + if (self.index >= self.block.primitivegroup.items.len) return null; + const group = &self.block.primitivegroup.items[self.index]; + self.index += 1; + return .{ + .block = self.block, + .group = group, + }; + } +}; + +pub fn iterator(self: *const PrimitiveBlock) PrimitiveBlockIterator { + return .{ + .block = &self.block, + .index = 0, + }; +} diff --git a/src/PrimitiveGroup.zig b/src/PrimitiveGroup.zig new file mode 100644 index 0000000..ad4e12b --- /dev/null +++ b/src/PrimitiveGroup.zig @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const PrimitiveGroup = @This(); + +const std = @import("std"); + +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +const DenseNodeIterator = @import("DenseNodeIterator.zig"); + +block: *const OSMPBF.PrimitiveBlock, +group: *const OSMPBF.PrimitiveGroup, + +pub fn init(block: *const OSMPBF.PrimitiveBlock, group: *const OSMPBF.PrimitiveGroup) PrimitiveGroup { + return .{ + .block = block, + .group = group, + }; +} + +pub const PrimitiveType = enum { + nodes, + dense_nodes, + ways, + relations, + changesets, + empty, +}; + +pub fn contains(self: *const PrimitiveGroup) PrimitiveType { + if (self.group.dense) |_| return .dense_nodes; + if (self.group.nodes.items.len > 0) return .nodes; + if (self.group.ways.items.len > 0) return .ways; + if (self.group.relations.items.len > 0) return .relations; + if (self.group.changesets.items.len > 0) return .changesets; + return .empty; +} + +pub fn denseNodeIterator(self: *const PrimitiveGroup) DenseNodeIterator { + return .init(self.block, self.group); +} diff --git a/src/Timestamp.zig b/src/Timestamp.zig new file mode 100644 index 0000000..fe24f30 --- /dev/null +++ b/src/Timestamp.zig @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const Timestamp = @This(); + +const std = @import("std"); + +timestamp: ?i64, +granularity: i32, + +pub fn init(timestamp: ?i64, granularity: i32) @This() { + return .{ + .timestamp = timestamp, + .granularity = granularity, + }; +} + +pub fn format(self: *const @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + const timestamp = timestamp: { + if (self.timestamp) |ts| break :timestamp ts; + try writer.print("null", .{}); + return; + }; + const millisecond_stamp = timestamp * self.granularity; + try writer.print("{d}", .{millisecond_stamp}); +} diff --git a/src/Way.zig b/src/Way.zig new file mode 100644 index 0000000..b9d5fe1 --- /dev/null +++ b/src/Way.zig @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const Way = @This(); + +const std = @import("std"); + +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +const Timestamp = @import("Timestamp.zig"); +const stringtable = @import("stringtable.zig"); + +id: i64, +refs: []i64, +kvs: std.StringArrayHashMapUnmanaged([]const u8), +version: i32, +timestamp: Timestamp, +changeset: ?i64, +uid: ?i32, +user_sid: ?[]const u8, +visible: ?bool, + +pub fn init(gpa: std.mem.Allocator, block: *const OSMPBF.PrimitiveBlock, way: *OSMPBF.Way) !Way { + const user_sid = user_sid: { + const info = &(way.info orelse break :user_sid null); + break :user_sid try stringtable.dupe(gpa, block, info.user_sid); + }; + errdefer if (user_sid) |v| gpa.free(v); + + const kvs = kvs: { + var kvs: std.StringArrayHashMapUnmanaged([]const u8) = .empty; + errdefer { + var it = kvs.iterator(); + while (it.next()) |entry| { + gpa.free(entry.key_ptr.*); + gpa.free(entry.value_ptr.*); + } + kvs.deinit(gpa); + } + + for (way.keys, way.vals) |key_index, value_index| { + const key = try stringtable.lookup(block, key_index) orelse return error.StringIndexInvalid; + const value = try stringtable.dupe(gpa, block, value_index) orelse return error.StringIndexInvalid; + const gop = gop: { + errdefer gpa.free(value); + break :gop try kvs.getOrPut(gpa, key); + }; + if (gop.found_existing) { + gpa.free(gop.value_ptr.*); + gop.value_ptr.* = value; + } else { + gop.key_ptr.* = key: { + errdefer gpa.free(value); + break :key try gpa.dupe(u8, key); + }; + gop.value_ptr.* = value; + } + } + + break :kvs kvs; + }; + + return .{ + .id = way.id, + .refs = refs: { + var refs = try gpa.alloc(i64, way.refs.len); + var acc: i64 = 0; + for (way.refs.items, 0..) |delta, i| { + acc += delta; + refs[i] = acc; + } + break :refs refs; + }, + .version = version: { + const info = &(way.info orelse break :version -1); + break :version info.version orelse -1; + }, + .timestamp = timestamp: { + const info = &(way.info orelse break :timestamp .{ + .timestamp = null, + .granularity = block.date_granularity, + }); + break :timestamp .{ + .timestamp = info.timestamp, + .granularity = block.date_granularity, + }; + }, + .changeset = changeset: { + const info = &(way.info orelse break :changeset null); + break :changeset info.changeset; + }, + .uid = uid: { + const info = &(way.info orelse break :uid null); + break :uid info.uid; + }, + .user_sid = user_sid, + .kvs = kvs, + }; +} diff --git a/src/WayIterator.zig b/src/WayIterator.zig new file mode 100644 index 0000000..a8b5216 --- /dev/null +++ b/src/WayIterator.zig @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const WayIterator = @This(); + +const std = @import("std"); + +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +const Way = @import("Way.zig"); + +block: *const OSMPBF.PrimitiveBlock, +group: *const OSMPBF.PrimitiveGroup, +index: usize, + +pub fn init(block: *const OSMPBF.PrimitiveBlock, group: *const OSMPBF.PrimitiveGroup) WayIterator { + return .{ + .block = block, + .group = group, + .index = 0, + }; +} + +pub fn next(self: *WayIterator, gpa: std.mem.Allocator) !?Way { + if (self.index >= self.group.ways.items.len) return null; + + const way = &self.group.ways.items[self.index]; + self.index += 1; + + return try .init(gpa, self.block, way); +} diff --git a/src/latlon.zig b/src/latlon.zig index f6dd513..980e8a2 100644 --- a/src/latlon.zig +++ b/src/latlon.zig @@ -17,7 +17,7 @@ pub const LatLonType = enum { longitude, pub inline fn toChar(self: @This(), dir: LatLonDir) u8 { - return switch (self) { + return switch (comptime self) { .latitude => switch (dir) { .neg => 'S', .pos => 'N', @@ -57,5 +57,9 @@ pub fn LatLon(comptime T: LatLonType) type { T.toChar(dir), }); } + + pub fn float(self: *const @This(), comptime F: type) F { + return @as(T, 0.000000001) * @as(F, @floatFromInt(self.offset + (self.granularity * self.latlon))); + } }; } diff --git a/src/stringtable.zig b/src/stringtable.zig new file mode 100644 index 0000000..489205f --- /dev/null +++ b/src/stringtable.zig @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +// SPDX-License-Identifier: MIT + +const std = @import("std"); + +const OSMPBF = @import("proto/OSMPBF.pb.zig"); + +pub fn dupe(alloc: std.mem.Allocator, block: *const OSMPBF.PrimitiveBlock, index_: ?u32) !?[]const u8 { + const string = try lookup(block, index_) orelse return null; + return try alloc.dupe(u8, string); +} + +pub fn lookup(block: *const OSMPBF.PrimitiveBlock, index_: ?u32) !?[]const u8 { + const index = index_ orelse return null; + const stringtable = (block.stringtable orelse return null).s.items; + if (index >= stringtable.len) return error.StringIndexOutOfRange; + return stringtable[@intCast(index)]; +}