diff --git a/Cargo.toml b/Cargo.toml index 6a4e084b9..bd0c60087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ measure_time = "0.9.0" arc-swap = "1.5.0" bon = "3.3.1" robust = "1.2" +i_triangle = "0.38.0" columnar = { version = "0.6", path = "./columnar", package = "tantivy-columnar" } sstable = { version = "0.6", path = "./sstable", package = "tantivy-sstable", optional = true } @@ -70,6 +71,7 @@ hyperloglogplus = { version = "0.4.1", features = ["const-loop"] } futures-util = { version = "0.3.28", optional = true } futures-channel = { version = "0.3.28", optional = true } fnv = "1.0.7" +geo-types = "0.7.17" [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/src/spatial/triangle.rs b/src/spatial/triangle.rs index 689cdf2a7..e25d9734f 100644 --- a/src/spatial/triangle.rs +++ b/src/spatial/triangle.rs @@ -5,6 +5,9 @@ //! contain an additional vertex and packed reconstruction metadata, allowing exact triangle //! recovery when needed. +use geo_types::MultiPolygon; +use i_triangle::i_overlay::i_float::int::point::IntPoint; +use i_triangle::int::triangulatable::IntTriangulatable; use robust::{orient2d, Coord}; const MINY_MINX_MAXY_MAXX_Y_X: i32 = 0; @@ -300,8 +303,66 @@ impl Triangle { } } +/// Triangulates a geographic polygon into encoded triangles for block kd-tree indexing. +/// +/// This function is used by applications to convert a `MultiPolygon` into triangles for indexing +/// in the block kd-tree. Once triangulated, the encoded triangles can be inserted into the block +/// kd-tree and queries against the block kd-tree will return the associated `doc_id`. +/// +/// Takes a polygon with floating-point lat/lon coordinates, converts to integer coordinates with +/// millimeter precision (using 2^32 scaling), performs constrained Delaunay triangulation, and +/// encodes the resulting triangles with boundary edge information. +/// +/// Handles polygons with holes correctly, preserving which triangle edges lie on the original +/// polygon boundaries versus internal tessellation edges. +pub fn triangulate(doc_id: u32, geometry: G, triangles: &mut Vec) +where G: Into> { + let multi = geometry.into(); + for polygon in multi { + let exterior: Vec = polygon + .exterior() + .coords() + .map(|coord| { + let lat = (coord.y / (180.0 / (1i64 << 32) as f64)).floor() as i32; + let lon = (coord.x / (360.0 / (1i64 << 32) as f64)).floor() as i32; + IntPoint::new(lon, lat) + }) + .collect(); + let mut i_polygon = vec![exterior]; + for interior in polygon.interiors() { + let hole: Vec = interior + .coords() + .map(|coord| { + let lat = (coord.y / (180.0 / (1i64 << 32) as f64)).floor() as i32; + let lon = (coord.x / (360.0 / (1i64 << 32) as f64)).floor() as i32; + IntPoint::new(lon, lat) + }) + .collect(); + i_polygon.push(hole); + } + let delaunay = i_polygon.triangulate().into_delaunay(); + for (_, triangle) in delaunay.triangles.iter().enumerate() { + let bounds = [ + triangle.neighbors[0] == usize::MAX, + triangle.neighbors[1] == usize::MAX, + triangle.neighbors[2] == usize::MAX, + ]; + let v0 = &delaunay.points[triangle.vertices[0].index]; + let v1 = &delaunay.points[triangle.vertices[1].index]; + let v2 = &delaunay.points[triangle.vertices[2].index]; + triangles.push(Triangle::new( + doc_id, + [v0.y, v0.x, v1.y, v1.x, v2.y, v2.x], + bounds, + )) + } + } +} + #[cfg(test)] mod tests { + use geo_types::polygon; + use super::*; #[test] @@ -373,4 +434,14 @@ mod tests { assert_eq!(decoded_coords, coords); } } + + #[test] + fn triangulate_box() { + let polygon = polygon![ + (x: 0.0, y: 0.0), (x: 10.0, y: 0.0), (x: 10.0, y: 10.0), (x: 0.0, y: 10.0) + ]; + let mut triangles = Vec::new(); + triangulate(1, polygon, &mut triangles); + assert_eq!(triangles.len(), 2); + } }