Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,7 @@ pub fn build(b: *std.Build) void {
chart_mod.addImport("style", style_mod); // linestyle XML analysis
chart_mod.addImport("coverage", coverage_mod); // embedded-coverage attach on PMTiles opens
chart_mod.addImport("compose", compose_mod); // compose-backed view renders
chart_mod.addImport("raster", raster_mod); // the inventory reads picture charts

const bake_target = if (target.result.os.tag == .linux and target.result.abi != .musl)
b.resolveTargetQuery(.{ .cpu_arch = target.result.cpu.arch, .os_tag = .linux, .abi = .musl })
Expand Down Expand Up @@ -1068,6 +1069,25 @@ pub fn build(b: *std.Build) void {
// Charts read straight out of a .zip, and the aux files that travel with
// them: pure over std, so each tests alone.
_ = addPkgTest(b, test_step, "src/zipsrc.zig", target, optimize, &.{});
// src/chart.zig holds tests that nothing collected: the engine imports it
// as a module, and a module import does not pull another module's test
// blocks in. It needs libc for portray's embedded Lua.
addPkgTest(b, test_step, "src/chart.zig", target, optimize, &.{
.{ .name = "s57", .module = s57_mod },
.{ .name = "s101", .module = s101_mod },
.{ .name = "tiles", .module = tiles_mod },
.{ .name = "scene", .module = scene_mod },
.{ .name = "render", .module = render_mod },
.{ .name = "portray", .module = portray_mod },
.{ .name = "sprite", .module = sprite_mod },
.{ .name = "catalog", .module = catalog_embed },
.{ .name = "zipsrc", .module = zipsrc_mod },
.{ .name = "auxfiles", .module = auxfiles_mod },
.{ .name = "style", .module = style_mod },
.{ .name = "coverage", .module = coverage_mod },
.{ .name = "compose", .module = compose_mod },
.{ .name = "raster", .module = raster_mod },
}).link_libc = true;
_ = addPkgTest(b, test_step, "src/auxfiles.zig", target, optimize, &.{});
// Geometry core for the cross-band composition. No longer std-only: plane.zig
// reads its partition tuning/stats valves via std.c.getenv, so the test binary
Expand Down
60 changes: 60 additions & 0 deletions include/tile57.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,66 @@ typedef struct {

/* ---- reading ENC source data --------------------------------------------- */

/* What kind of chart file the inventory found. */
typedef enum {
TILE57_FILE_OTHER = 0, /* shortlisted by extension and not a chart */
TILE57_FILE_SOURCE = 1, /* a dataset; it bakes before it draws */
TILE57_FILE_UPDATE = 2, /* an S-57 update; it bakes with its base chart */
TILE57_FILE_BAKED = 3, /* a baked archive; it draws now */
TILE57_FILE_RASTER = 4 /* a picture chart */
} tile57_file_kind;

/* The standard a dataset states in its leading record. */
typedef enum {
TILE57_STANDARD_NONE = 0,
TILE57_STANDARD_S57 = 1,
TILE57_STANDARD_S101 = 2
} tile57_standard;

/* One file the inventory looked at. Strings are borrowed until
* tile57_inventory_close. A field the file does not state is an empty string
* or 0. */
typedef struct {
const char *path; /* as given, or relative to the directory walked */
uint64_t bytes;
uint8_t kind; /* tile57_file_kind */
uint8_t standard; /* tile57_standard; NONE unless a dataset */
const char *name; /* the cell name, from DSID */
const char *edition, *update, *issue_date;
uint16_t agency;
int32_t scale; /* compilation scale denominator */
bool has_bounds; double west, south, east, north;
/* Why a shortlisted file will not open. Empty when it read cleanly. */
const char *reason;
} tile57_inventory_row;

/* Opaque inventory handle: what one path holds. */
typedef struct tile57_inventory tile57_inventory;

/* Report the files under `path` that look like charts. `path` is one file, or
* a directory walked to the bottom. An exchange set still in its .zip is out
* of scope: an entry inflates before it can be read.
*
* An extension shortlists the files to open (.000 through .999, .pmtiles,
* .mbtiles, .kap, .bsb). The file then states what it is: a dataset states its
* standard in its leading record, its cell name and edition in DSID, and its
* scale in the dataset parameters. A shortlisted file that is something else
* is TILE57_FILE_OTHER with a reason.
*
* Every file reported on is opened. A dataset costs a leading-record peek and
* a baked archive costs its header.
*
* TILE57_OK with the handle in *out, holding no file open. It fails when the
* path cannot be read. */
tile57_status tile57_inventory_open(const char *path, tile57_inventory **out,
tile57_error *err);
void tile57_inventory_close(tile57_inventory *inv);

/* The rows, in path order. Borrowed until close. *out_n receives the count.
* It is 0 when the path holds no chart file. */
const tile57_inventory_row *tile57_inventory_rows(tile57_inventory *inv,
size_t *out_n);

/* The per-chart metadata of the S-57 data at `path` — ONE chart (a .000 file,
* with its .001.. update chain auto-read from the same directory) or a whole
* ENC_ROOT directory — as a JSON array, one object per chart:
Expand Down
81 changes: 81 additions & 0 deletions src/capi.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2590,3 +2590,84 @@ export fn tile57_free(ptr: ?*anyopaque) callconv(.c) void {
const total = std.mem.readInt(usize, base[0..@sizeOf(usize)], .little);
gpa.free(base[0..total]);
}

// ---- the inventory ----------------------------------------------------------

const CInventoryRow = extern struct {
path: [*:0]const u8,
bytes: u64,
kind: u8,
standard: u8,
name: [*:0]const u8,
edition: [*:0]const u8,
update: [*:0]const u8,
issue_date: [*:0]const u8,
agency: u16,
scale: i32,
has_bounds: bool,
west: f64,
south: f64,
east: f64,
north: f64,
reason: [*:0]const u8,
};

/// What one path holds: the rows, and the arena their strings live in.
const Inventory = struct {
inv: *chart.Inventory,
rows: []CInventoryRow,
};

/// Look through a path and report the files under it that look like charts.
/// See tile57.h.
export fn tile57_inventory_open(path: ?[*:0]const u8, out: ?*?*Inventory, err: ?*CError) callconv(.c) c_int {
const o = out orelse return failWith(err, .badarg, "out must not be null");
o.* = null;
const p = spanOpt(path) orelse return failWith(err, .badarg, "path must not be null");
const inv = chart.inventoryOpen(sharedIo(), p) catch |e| return failCtx(err, e, p);
errdefer inv.close();
const rows = gpa.alloc(CInventoryRow, inv.rows.len) catch |e| return fail(err, e);
errdefer gpa.free(rows);
for (inv.rows, rows) |src, *dst| {
dst.* = .{
.path = src.path.ptr,
.bytes = src.bytes,
.kind = @intFromEnum(src.kind),
.standard = @intFromEnum(src.standard),
.name = src.name.ptr,
.edition = src.edition.ptr,
.update = src.update.ptr,
.issue_date = src.issue_date.ptr,
.agency = src.agency,
.scale = src.scale,
.has_bounds = src.bounds != null,
.west = if (src.bounds) |b| b[0] else 0,
.south = if (src.bounds) |b| b[1] else 0,
.east = if (src.bounds) |b| b[2] else 0,
.north = if (src.bounds) |b| b[3] else 0,
.reason = src.reason.ptr,
};
}
const handle = gpa.create(Inventory) catch |e| return fail(err, e);
handle.* = .{ .inv = inv, .rows = rows };
o.* = handle;
return OK;
}

export fn tile57_inventory_close(handle: ?*Inventory) callconv(.c) void {
const h = handle orelse return;
gpa.free(h.rows);
h.inv.close();
gpa.destroy(h);
}

/// The rows, in path order. Borrowed until close. See tile57.h.
export fn tile57_inventory_rows(handle: ?*Inventory, out_n: ?*usize) callconv(.c) ?[*]const CInventoryRow {
const h = handle orelse {
if (out_n) |n| n.* = 0;
return null;
};
if (out_n) |n| n.* = h.rows.len;
if (h.rows.len == 0) return null;
return h.rows.ptr;
}
Loading
Loading