C++ library for agricultural workspace data
zoneout is now organized around four separate concepts:
Plot: spatial payloadZone: recursive logical region with metadataGraph: connectivity network usinggraphixWorkspace: one zone tree plus one graph
This replaces the old mental model where Plot was a collection of zones and Zone tried to be the whole workspace by itself.
struct Plot {
Poly poly; // mandatory
std::optional<Grid> grid; // optional
};
struct Zone {
UUID id;
std::string name;
std::string type;
Plot plot;
std::unordered_map<std::string, std::string> properties;
std::vector<Zone> children;
};
struct NodeData {
UUID id;
datapod::Point position;
std::vector<UUID> zone_ids;
std::unordered_map<std::string, std::string> properties;
};
struct EdgeData {
UUID id;
std::vector<UUID> zone_ids;
std::unordered_map<std::string, std::string> properties;
};
using Graph = graphix::vertex::Graph<NodeData, EdgeData>;
struct Workspace {
Zone root_zone;
Graph graph;
};#include "zoneout/zoneout.hpp"
namespace dp = datapod;
int main() {
dp::Polygon boundary;
boundary.vertices.push_back({0.0, 0.0, 0.0});
boundary.vertices.push_back({100.0, 0.0, 0.0});
boundary.vertices.push_back({100.0, 50.0, 0.0});
boundary.vertices.push_back({0.0, 50.0, 0.0});
dp::Geo datum{52.0, 5.0, 0.0};
zoneout::Plot farm_plot = zoneout::PlotBuilder()
.with_name("farm")
.with_type("root")
.with_boundary(boundary)
.with_datum(datum)
.build();
zoneout::Zone farm("farm", "root", std::move(farm_plot));
farm.set_property("owner", "research_team");
zoneout::Zone field("field_a", "field", boundary, datum, 1.0);
farm.add_child(field);
zoneout::Workspace workspace(std::move(farm));
auto a = workspace.add_node(dp::Point{10.0, 10.0, 0.0}, {{"kind", "entry"}});
auto b = workspace.add_node(dp::Point{80.0, 40.0, 0.0}, {{"kind", "target"}});
workspace.add_edge(a, b, 1.0, graphix::vertex::EdgeType::Undirected, {{"kind", "lane"}});
workspace.save("farm_workspace");
auto loaded = zoneout::Workspace::load("farm_workspace");
return loaded.graph().edge_count() == 1 ? 0 : 1;
}Plotowns geometry and optional raster data only.Zoneowns identity, metadata, and recursive child zones.Workspaceowns the root zone tree and the graph.Graphis separate from zones. Nodes and edges carryzone_idsinstead of being owned by zones.- Zone nesting uses
depth, notlayer.
PlotBuilder()
.with_name(...)
.with_type(...)
.with_boundary(...)
.with_datum(...)
.with_resolution(...) // optional, creates a base grid
.build();
plot.poly();
plot.has_grid();
plot.grid();
plot.set_grid(...);
plot.clear_grid();
plot.save(directory);
Plot::load(directory);Zone(name, type, boundary, datum); // poly only
Zone(name, type, boundary, datum, resolution); // poly + generated grid
Zone(name, type, Plot{...});
zone.plot();
zone.poly();
zone.has_grid();
zone.plot().grid();
zone.plot().set_grid(...);
zone.plot().clear_grid();
zone.add_child(child);
zone.remove_child(child_id);
zone.find(zone_id);
zone.find_by_name(name);
zone.depth_of(zone_id);
zone.visit(visitor);
zone.add_raster_layer(...); // creates a grid in the plot if needed
zone.save(directory);
Zone::load(directory);Workspace(root_zone);
workspace.root_zone();
workspace.graph();
workspace.add_node(position, properties);
workspace.add_edge(source, target, weight, type, properties);
workspace.find_zone(zone_id);
workspace.find_node(node_id);
workspace.find_edge(edge_id);
workspace.zones_containing(point);
workspace.refresh_graph_zone_membership();
workspace.save(directory);
Workspace::load(directory);WorkspaceJson json = parse_workspace_json_file("workspace.json");
auto errors = validate_workspace_json(json);
if (!errors.empty()) {
// surface import errors to the UI
}
Workspace workspace = to_workspace(json);
write_workspace_json_file("roundtrip.json", workspace);Public draft/import APIs:
WorkspaceJsonZoneJsonNodeJsonEdgeJsonparse_workspace_json(...)parse_workspace_json_file(...)workspace_json(...)write_workspace_json_file(path, draft)write_workspace_json_file(path, workspace)validate_workspace_json(...)require_valid_workspace_json(...)to_workspace(...)from_workspace(...)load_workspace_json_file(...)
Workspace::save() writes:
workspace/
workspace.json
zones/
<zone-uuid>/
zone.json
vector.geojson
raster.tiff # optional
graph/
graph.json
Workspace::load() still supports the older temporary layout for compatibility.
The separate UI tool should use workspace.json as the interchange format.
That interchange JSON is intentionally separate from the native Workspace::save() directory layout.
make build
make test