From a1d3b3c2ed2987514ca7c762832d15cdb5b91968 Mon Sep 17 00:00:00 2001 From: Austin Gill Date: Sat, 20 Jan 2024 17:04:45 -0600 Subject: [PATCH] Node the GeometryCollectionShim and return a GeometryGraphShim --- build.rs | 4 ++ generative/cxxbridge/coord_ffi.rs | 7 +++ generative/cxxbridge/geometry_collection.hpp | 3 +- generative/cxxbridge/geometry_graph.hpp | 48 +++++++++++++++ generative/cxxbridge/geometry_graph_ffi.rs | 15 +++++ generative/cxxbridge/mod.rs | 5 +- generative/cxxbridge/noder.hpp | 28 ++++++++- generative/cxxbridge/noder_ffi.rs | 65 ++++++++++++++++++-- tests/geometry-noder-tests.cpp | 30 +++++++++ 9 files changed, 195 insertions(+), 10 deletions(-) create mode 100644 generative/cxxbridge/geometry_graph.hpp create mode 100644 generative/cxxbridge/geometry_graph_ffi.rs diff --git a/build.rs b/build.rs index 404de76..afd99e3 100644 --- a/build.rs +++ b/build.rs @@ -35,6 +35,9 @@ fn main() { for cpp_header in glob::glob(format!("{allow_dir}/**/*.h").as_str()).unwrap() { println!("cargo:rerun-if-changed={}", cpp_header.unwrap().display()); } + for cpp_header in glob::glob(format!("{allow_dir}/**/*.hpp").as_str()).unwrap() { + println!("cargo:rerun-if-changed={}", cpp_header.unwrap().display()); + } } let out_dir = std::env::var("OUT_DIR").unwrap(); @@ -85,6 +88,7 @@ fn main() { let cxxbridge_sources = [ "generative/cxxbridge/coord_ffi.rs", "generative/cxxbridge/geometry_collection_ffi.rs", + "generative/cxxbridge/geometry_graph_ffi.rs", "generative/cxxbridge/noder_ffi.rs", ]; cxx_build::bridges(cxxbridge_sources) diff --git a/generative/cxxbridge/coord_ffi.rs b/generative/cxxbridge/coord_ffi.rs index 4c05aa4..72791ff 100644 --- a/generative/cxxbridge/coord_ffi.rs +++ b/generative/cxxbridge/coord_ffi.rs @@ -19,6 +19,13 @@ pub mod ffi { vec: Vec, } impl Vec {} + + #[derive(Debug, Clone, Copy, PartialEq)] + struct GraphEdge { + src: usize, + dst: usize, + } + impl Vec {} } impl From for ffi::CoordShim { diff --git a/generative/cxxbridge/geometry_collection.hpp b/generative/cxxbridge/geometry_collection.hpp index af01254..3a20574 100644 --- a/generative/cxxbridge/geometry_collection.hpp +++ b/generative/cxxbridge/geometry_collection.hpp @@ -24,7 +24,8 @@ get_geos_geoms_from_rust(const GeometryCollectionShim& rust_geoms, for (const auto& point : points) { const auto coord = geos::geom::CoordinateXY{point.x, point.y}; - factory->createPoint(coord); + auto geos_point = factory->createPoint(coord); + geos_geoms.push_back(std::move(geos_point)); } const auto linestrings = rust_geoms.get_linestrings(); diff --git a/generative/cxxbridge/geometry_graph.hpp b/generative/cxxbridge/geometry_graph.hpp new file mode 100644 index 0000000..98d1c1b --- /dev/null +++ b/generative/cxxbridge/geometry_graph.hpp @@ -0,0 +1,48 @@ +#pragma once +#include "generative/generative/cxxbridge/coord_ffi.rs.h" +#include + +#include + +#include + +class GeometryGraphShim +{ +public: + explicit GeometryGraphShim(generative::noding::GeometryGraph&& graph) : + m_inner(std::move(graph)) + { + } + + [[nodiscard]] rust::Vec nodes() const noexcept + { + const auto& cxx_nodes = m_inner.get_nodes(); + rust::Vec rust_nodes; + rust_nodes.reserve(cxx_nodes.size()); + + for (const auto& node : cxx_nodes) + { + auto rust_node = CoordShim{node.coord().x, node.coord().y}; + rust_nodes.emplace_back(rust_node); + } + + return rust_nodes; + } + + [[nodiscard]] rust::Vec edges() const noexcept + { + const auto& cxx_edges = m_inner.get_edge_pairs(); + rust::Vec rust_edges; + rust_edges.reserve(cxx_edges.size()); + for (const auto& edge : cxx_edges) + { + auto rust_edge = GraphEdge{edge.first.index, edge.second.index}; + rust_edges.emplace_back(rust_edge); + } + + return rust_edges; + } + +private: + generative::noding::GeometryGraph m_inner; +}; diff --git a/generative/cxxbridge/geometry_graph_ffi.rs b/generative/cxxbridge/geometry_graph_ffi.rs new file mode 100644 index 0000000..2fb9829 --- /dev/null +++ b/generative/cxxbridge/geometry_graph_ffi.rs @@ -0,0 +1,15 @@ +#[cxx::bridge] +pub mod ffi { + unsafe extern "C++" { + include!("geometry_graph.hpp"); + + type GeometryGraphShim; + type CoordShim = crate::cxxbridge::CoordShim; + type GraphEdge = crate::cxxbridge::GraphEdge; + + fn nodes(self: &GeometryGraphShim) -> Vec; + fn edges(self: &GeometryGraphShim) -> Vec; + } + + impl UniquePtr {} +} diff --git a/generative/cxxbridge/mod.rs b/generative/cxxbridge/mod.rs index 47cafa2..2bc5fe7 100644 --- a/generative/cxxbridge/mod.rs +++ b/generative/cxxbridge/mod.rs @@ -1,7 +1,10 @@ mod coord_ffi; mod geometry_collection; mod geometry_collection_ffi; +mod geometry_graph_ffi; mod noder_ffi; -pub use coord_ffi::ffi::{CoordShim, LineStringShim, PolygonShim}; +pub use coord_ffi::ffi::{CoordShim, GraphEdge, LineStringShim, PolygonShim}; pub use geometry_collection::GeometryCollectionShim; +pub use geometry_graph_ffi::ffi::GeometryGraphShim; +pub use noder_ffi::ffi::node; diff --git a/generative/cxxbridge/noder.hpp b/generative/cxxbridge/noder.hpp index 28e49d8..4f21233 100644 --- a/generative/cxxbridge/noder.hpp +++ b/generative/cxxbridge/noder.hpp @@ -1,8 +1,32 @@ #pragma once -#include "generative/generative/cxxbridge/geometry_collection_ffi.rs.h" +#include "generative/generative/cxxbridge/geometry_graph_ffi.rs.h" #include "geometry_collection.hpp" +#include +#include -inline void _compile_tester(const GeometryCollectionShim& rust_geoms) noexcept +#include + +#include + +[[nodiscard]] inline std::unique_ptr +node(const GeometryCollectionShim& rust_geoms, double tolerance) noexcept { const auto geos_geoms = copy_rust_collection_to_geos(rust_geoms); + std::cerr << "Noding: " << geos_geoms->toString() << std::endl; + + auto noded = std::unique_ptr(nullptr); + if (tolerance == 0.0) + { + noded = generative::noding::GeometryNoder::node(*geos_geoms, nullptr); + } else + { + auto noder = std::make_unique(tolerance); + noded = generative::noding::GeometryNoder::node(*geos_geoms, std::move(noder)); + } + std::cerr << "Noded: " << noded->toString() << std::endl; + auto graph = generative::noding::GeometryGraph(*noded); + + auto graph_shim = std::make_unique(std::move(graph)); + + return graph_shim; } diff --git a/generative/cxxbridge/noder_ffi.rs b/generative/cxxbridge/noder_ffi.rs index 00f9a4a..ebbcb74 100644 --- a/generative/cxxbridge/noder_ffi.rs +++ b/generative/cxxbridge/noder_ffi.rs @@ -1,22 +1,75 @@ #[cxx::bridge] -mod ffi { +pub mod ffi { unsafe extern "C++" { include!("noder.hpp"); type GeometryCollectionShim = crate::cxxbridge::GeometryCollectionShim; + type GeometryGraphShim = crate::cxxbridge::GeometryGraphShim; - fn _compile_tester(geoms: &GeometryCollectionShim); + /// Node the given collection of geometries + /// + /// # Safety + /// + /// The noding is done by either a geos SnappingNoder or IteratedNoder, both of which have + /// some gotchas. + /// + /// * The IteratedNoder can throw topology exceptions if it doesn't converge by MAX_ITERS + /// * The SnappingNoder doesn't handle isolated POINTs + unsafe fn node( + geoms: &GeometryCollectionShim, + tolerance: f64, + ) -> UniquePtr; } } #[cfg(test)] mod tests { use super::*; + use crate::cxxbridge::{CoordShim, GraphEdge}; #[test] - fn test_compile_tester() { - let collection = geo::GeometryCollection::default(); - let collection = crate::cxxbridge::GeometryCollectionShim(collection); - ffi::_compile_tester(&collection); + fn test_noder_doesnt_crash() { + let empty = crate::cxxbridge::GeometryCollectionShim::new(Vec::new()); + let tolerance = 0.001; + let graph = unsafe { ffi::node(&empty, tolerance) }; + + assert!(!graph.is_null()); + } + + #[test] + fn test_noder_isolated_points() { + let geoms = [geo::Point::new(0.0, 0.0), geo::Point::new(0.0, 1.0)]; + let geoms: Vec<_> = geoms.into_iter().map(geo::Geometry::Point).collect(); + let geoms = crate::cxxbridge::GeometryCollectionShim::new(geoms); + + let tolerance = 0.0; + let graph = unsafe { ffi::node(&geoms, tolerance) }; + assert!(!graph.is_null()); + + let nodes = graph.nodes(); + let expected = [CoordShim { x: 0.0, y: 0.0 }, CoordShim { x: 0.0, y: 1.0 }]; + assert_eq!(nodes, expected); + + let edges = graph.edges(); + assert!(edges.is_empty()); + } + + #[test] + fn test_noder_linestring() { + let line: geo::LineString = vec![(0.0, 0.0), (0.00001, 0.0), (2.0, 0.0)].into(); + let geoms = vec![geo::Geometry::LineString(line)]; + let geoms = crate::cxxbridge::GeometryCollectionShim::new(geoms); + + let tolerance = 0.0001; + let graph = unsafe { ffi::node(&geoms, tolerance) }; + assert!(!graph.is_null()); + + let nodes = graph.nodes(); + let expected = [CoordShim { x: 0.0, y: 0.0 }, CoordShim { x: 2.0, y: 0.0 }]; + assert_eq!(nodes, expected); + + let edges = graph.edges(); + let expected = [GraphEdge { src: 0, dst: 1 }]; + assert_eq!(edges, expected); } } diff --git a/tests/geometry-noder-tests.cpp b/tests/geometry-noder-tests.cpp index f1f2513..76814fa 100644 --- a/tests/geometry-noder-tests.cpp +++ b/tests/geometry-noder-tests.cpp @@ -11,6 +11,36 @@ using generative::io::from_wkt; +TEST(GeometryNoderTests, TwoPoints) +{ + const std::unique_ptr geometries = from_wkt( + // clang-format off + "GEOMETRYCOLLECTION(" + "POINT(1 1)," + "POINT(0 1)" + ")" + // clang-format on + ); + ASSERT_TRUE(geometries); + const std::unique_ptr expected = from_wkt( + // clang-format off + "GEOMETRYCOLLECTION(" + "POINT(1 1)," + "POINT(0 1)" + ")" + // clang-format on + ); + ASSERT_TRUE(expected); + + const std::unique_ptr noded = + generative::noding::GeometryNoder::node(*geometries); + ASSERT_TRUE(noded); + std::cerr << noded->toString() << std::endl; + + EXPECT_EQ(noded->getGeometryType(), expected->getGeometryType()); + EXPECT_TRUE(noded->equals(expected.get())); +} + TEST(GeometryNoderTests, DisjointPoint) { const std::unique_ptr geometries = from_wkt(