diff --git a/Cargo.toml b/Cargo.toml index 231ee23..624dc77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,10 @@ path = "tools/transform/main.rs" name = "triangulate" path = "tools/triangulate/main.rs" +[[bin]] +name = "urquhart" +path = "tools/urquhart/main.rs" + [dependencies] clap = {version="4.0", features=["derive"]} delaunator = "1.0" diff --git a/generative/triangulation.rs b/generative/triangulation.rs index 25a614a..634a955 100644 --- a/generative/triangulation.rs +++ b/generative/triangulation.rs @@ -1,5 +1,7 @@ +use crate::wkio::write_wkt_geometries; +use clap::ValueEnum; use geo::Point; -use petgraph::{Directed, Undirected}; +use petgraph::{visit::EdgeRef, Directed, Undirected}; type NodeData = Point; type EdgeWeight = (); @@ -194,13 +196,73 @@ impl Triangulation { } } +#[derive(Debug, Clone, ValueEnum)] +pub enum GraphFormat { + Tgf, + Wkt, +} + +pub fn write_graph( + writer: W, + graph: petgraph::Graph, + format: &GraphFormat, +) where + W: std::io::Write, + Direction: petgraph::EdgeType, +{ + match format { + GraphFormat::Tgf => write_graph_tgf(writer, graph), + GraphFormat::Wkt => write_graph_wkt(writer, graph), + } +} + +fn write_graph_tgf( + mut writer: W, + graph: petgraph::Graph, +) where + W: std::io::Write, + Direction: petgraph::EdgeType, +{ + // let (nodes, edges) = graph.into_nodes_edges(); + for idx in graph.node_indices() { + let coord = graph + .node_weight(idx) + .expect("Got index to nonexistent node."); + let index = idx.index(); + writeln!(writer, "{}\tPOINT({} {})", index, coord.x(), coord.y()) + .expect("Failed to write node label"); + } + writeln!(writer, "#").expect("Failed to write node/edge separator"); + for edge in graph.edge_references() { + writeln!( + writer, + "{}\t {}", + edge.source().index(), + edge.target().index() + ) + .expect("Failed to write edge"); + } +} + +fn write_graph_wkt( + writer: W, + graph: petgraph::Graph, +) where + W: std::io::Write, + Direction: petgraph::EdgeType, +{ + let edges = graph + .edge_references() + .map(|e| geo::Line::new(graph[e.source()], graph[e.target()])) + .map(geo::Geometry::Line); + write_wkt_geometries(writer, edges); +} + #[cfg(test)] mod tests { use super::*; use crate::flatten::flatten_geometries_into_points_ref; use crate::wkio::read_wkt_geometries; - #[cfg(feature = "test-io")] - use crate::wkio::write_wkt_geometries; use delaunator::EMPTY; #[test] diff --git a/tools/urquhart/cmdline.rs b/tools/urquhart/cmdline.rs new file mode 100644 index 0000000..b3b4ea3 --- /dev/null +++ b/tools/urquhart/cmdline.rs @@ -0,0 +1,31 @@ +use clap::Parser; +use generative::triangulation::GraphFormat; +use generative::wkio::GeometryFormat; +use std::path::PathBuf; + +/// Generate the Urquhart graph of the given geometries +/// +/// Approximates the point cloud's relative neighborhood. +#[derive(Debug, Parser)] +#[clap(name = "urquhart", verbatim_doc_comment)] +pub struct CmdlineOptions { + /// Increase logging verbosity. Defaults to ERROR level. + #[clap(short, long, action = clap::ArgAction::Count)] + pub verbosity: u8, + + /// Output file to write result to. Defaults to stdout. + #[clap(short, long)] + pub output: Option, + + /// Output geometry format. + #[clap(short = 'O', long, default_value = "wkt")] + pub output_format: GraphFormat, + + /// Input file to read input from. Defaults to stdin. + #[clap(short, long)] + pub input: Option, + + /// Input geometry format. + #[clap(short = 'I', long, default_value_t = GeometryFormat::Wkt)] + pub input_format: GeometryFormat, +} diff --git a/tools/urquhart/main.rs b/tools/urquhart/main.rs new file mode 100644 index 0000000..b5cee2f --- /dev/null +++ b/tools/urquhart/main.rs @@ -0,0 +1,28 @@ +mod cmdline; + +use clap::Parser; +use generative::flatten::flatten_geometries_into_points; +use generative::stdio::{get_input_reader, get_output_writer}; +use generative::triangulation::{triangulate, write_graph}; +use generative::wkio::read_geometries; +use stderrlog::ColorChoice; + +fn main() { + let args = cmdline::CmdlineOptions::parse(); + + stderrlog::new() + .verbosity(args.verbosity as usize + 1) // Default to WARN level. + .color(ColorChoice::Auto) + .init() + .expect("Failed to initialize stderrlog"); + + let reader = get_input_reader(&args.input).unwrap(); + let geometries = read_geometries(reader, &args.input_format); // lazily loaded + + let points = flatten_geometries_into_points(geometries); + let triangulation = triangulate(points); + let urquhart = triangulation.urquhart(); + + let writer = get_output_writer(&args.output).unwrap(); + write_graph(writer, urquhart, &args.output_format); +}