diff --git a/src/indexer/segment_serializer.rs b/src/indexer/segment_serializer.rs index 34b832e7e..a6143f170 100644 --- a/src/indexer/segment_serializer.rs +++ b/src/indexer/segment_serializer.rs @@ -37,15 +37,20 @@ impl SegmentSerializer { let fieldnorms_write = segment.open_write(SegmentComponent::FieldNorms)?; let fieldnorms_serializer = FieldNormsSerializer::from_write(fieldnorms_write)?; - let spatial_write = segment.open_write(SegmentComponent::Spatial)?; - let spatial_serializer = SpatialSerializer::from_write(spatial_write)?; + let spatial_serializer: Option = + if segment.schema().contains_spatial_field() { + let spatial_write = segment.open_write(SegmentComponent::Spatial)?; + Some(SpatialSerializer::from_write(spatial_write)?) + } else { + None + }; let postings_serializer = InvertedIndexSerializer::open(&mut segment)?; Ok(SegmentSerializer { segment, store_writer, fast_field_write, - spatial_serializer: Some(spatial_serializer), + spatial_serializer, fieldnorms_serializer: Some(fieldnorms_serializer), postings_serializer, }) diff --git a/src/schema/schema.rs b/src/schema/schema.rs index ddfd533e4..24069870d 100644 --- a/src/schema/schema.rs +++ b/src/schema/schema.rs @@ -218,9 +218,14 @@ impl SchemaBuilder { /// Finalize the creation of a `Schema` /// This will consume your `SchemaBuilder` pub fn build(self) -> Schema { + let contains_spatial_field = self + .fields + .iter() + .any(|field_entry| field_entry.field_type().value_type() == Type::Spatial); Schema(Arc::new(InnerSchema { fields: self.fields, fields_map: self.fields_map, + contains_spatial_field, })) } } @@ -228,6 +233,7 @@ impl SchemaBuilder { struct InnerSchema { fields: Vec, fields_map: HashMap, // transient + contains_spatial_field: bool, } impl PartialEq for InnerSchema { @@ -378,6 +384,11 @@ impl Schema { } Some((field, json_path)) } + + /// Returns true if the schema contains a spatial field. + pub(crate) fn contains_spatial_field(&self) -> bool { + self.0.contains_spatial_field + } } impl Serialize for Schema { @@ -405,16 +416,16 @@ impl<'de> Deserialize<'de> for Schema { fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de> { - let mut schema = SchemaBuilder { + let mut schema_builder = SchemaBuilder { fields: Vec::with_capacity(seq.size_hint().unwrap_or(0)), fields_map: HashMap::with_capacity(seq.size_hint().unwrap_or(0)), }; while let Some(value) = seq.next_element()? { - schema.add_field(value); + schema_builder.add_field(value); } - Ok(schema.build()) + Ok(schema_builder.build()) } } @@ -1030,4 +1041,33 @@ mod tests { Some((default, "foobar")) ); } + + #[test] + fn test_contains_spatial_field() { + // No spatial field + { + let mut schema_builder = Schema::builder(); + schema_builder.add_text_field("title", TEXT); + let schema = schema_builder.build(); + assert!(!schema.contains_spatial_field()); + + // Serialization check + let schema_json = serde_json::to_string(&schema).unwrap(); + let schema_deserialized: Schema = serde_json::from_str(&schema_json).unwrap(); + assert!(!schema_deserialized.contains_spatial_field()); + } + // With spatial field + { + let mut schema_builder = Schema::builder(); + schema_builder.add_text_field("title", TEXT); + schema_builder.add_spatial_field("location", SPATIAL); + let schema = schema_builder.build(); + assert!(schema.contains_spatial_field()); + + // Serialization check + let schema_json = serde_json::to_string(&schema).unwrap(); + let schema_deserialized: Schema = serde_json::from_str(&schema_json).unwrap(); + assert!(schema_deserialized.contains_spatial_field()); + } + } } diff --git a/src/spatial/triangle.rs b/src/spatial/triangle.rs index 4abad4b0b..5136a683b 100644 --- a/src/spatial/triangle.rs +++ b/src/spatial/triangle.rs @@ -6,6 +6,7 @@ //! recovery when needed. use i_triangle::advanced::delaunay::IntDelaunay; +use i_triangle::i_overlay::i_float::int::point::IntPoint; use crate::DocId; @@ -141,11 +142,10 @@ impl Triangle { } // change orientation if clockwise (CW) if !is_counter_clockwise( - Coord { y: ay, x: ax }, - Coord { y: by, x: bx }, - Coord { y: cy, x: cx }, - ) - { + IntPoint { y: ay, x: ax }, + IntPoint { y: by, x: bx }, + IntPoint { y: cy, x: cx }, + ) { // To change the orientation, we simply swap B and C. let temp_x = bx; let temp_y = by; @@ -194,6 +194,22 @@ impl Triangle { } } + /// Builds a degenerated triangle degenerating for a single point. + /// All vertices are that point, and all vertices are boundaries. + pub fn from_point(doc_id: DocId, point_x: i32, point_y: i32) -> Triangle { + Triangle::new( + doc_id, + [point_y, point_x, point_y, point_x, point_y, point_x], + [true, true, true], + ) + } + + /// Builds a degenerated triangle for a segment. + /// Line segment AB is represented as the triangle ABA. + pub fn from_line_segment(doc_id: DocId, a_x: i32, a_y: i32, b_x: i32, b_y: i32) -> Triangle { + Triangle::new(doc_id, [a_y, a_x, b_y, b_x, a_y, a_x], [true, true, true]) + } + /// Create a triangle with only the doc_id and the words initialized to zero. /// /// The doc_id and words in the field are delta-compressed as a series with the doc_id @@ -333,21 +349,16 @@ pub fn delaunay_to_triangles(doc_id: u32, delaunay: &IntDelaunay, triangles: &mu } } -struct Coord { - x: i32, - y: i32, -} - /// Returns true if the path A -> B -> C is Counter-Clockwise (CCW) or collinear. /// Returns false if it is Clockwise (CW). #[inline(always)] -fn is_counter_clockwise(a: Coord, b: Coord, c: Coord) -> bool { +fn is_counter_clockwise(a: IntPoint, b: IntPoint, c: IntPoint) -> bool { // We calculate the 2D cross product (determinant) of vectors AB and AC. // Formula: (bx - ax)(cy - ay) - (by - ay)(cx - ax) // We cast to i64 to prevent overflow, as multiplying two i32s can exceed i32::MAX. let val = (b.x as i64 - a.x as i64) * (c.y as i64 - a.y as i64) - - (b.y as i64 - a.y as i64) * (c.x as i64 - a.x as i64); + - (b.y as i64 - a.y as i64) * (c.x as i64 - a.x as i64); // If the result is positive, the triangle is CCW. // If negative, it is CW. @@ -393,7 +404,7 @@ mod tests { let input_coords = [ 50, 40, // A (y, x) 10, 60, // B - 20, 10 // C + 20, 10, // C ]; // 2. Define Boundaries [ab, bc, ca] @@ -415,7 +426,7 @@ mod tests { let expected_coords = [ 20, 10, // C 10, 60, // B - 50, 40 // A + 50, 40, // A ]; // 5. Expected Boundaries @@ -428,8 +439,14 @@ mod tests { // Shift left by 1: [true, false, false] let expected_bounds = [true, false, false]; - assert_eq!(decoded_coords, expected_coords, "Coordinates did not decode as expected"); - assert_eq!(decoded_bounds, expected_bounds, "Boundary flags were incorrect (likely swap bug)"); + assert_eq!( + decoded_coords, expected_coords, + "Coordinates did not decode as expected" + ); + assert_eq!( + decoded_bounds, expected_bounds, + "Boundary flags were incorrect (likely swap bug)" + ); } #[test] diff --git a/src/spatial/writer.rs b/src/spatial/writer.rs index b1584fcd1..a4dec7dba 100644 --- a/src/spatial/writer.rs +++ b/src/spatial/writer.rs @@ -23,27 +23,27 @@ impl SpatialWriter { let triangles = &mut self.triangles_by_field.entry(field).or_default(); match geometry { Geometry::Point(point) => { - into_point(triangles, doc_id, point); + append_point(triangles, doc_id, point); } Geometry::MultiPoint(multi_point) => { for point in multi_point { - into_point(triangles, doc_id, point); + append_point(triangles, doc_id, point); } } Geometry::LineString(line_string) => { - into_line_string(triangles, doc_id, line_string); + append_line_string(triangles, doc_id, line_string); } Geometry::MultiLineString(multi_line_string) => { for line_string in multi_line_string { - into_line_string(triangles, doc_id, line_string); + append_line_string(triangles, doc_id, line_string); } } Geometry::Polygon(polygon) => { - into_polygon(triangles, doc_id, polygon); + append_polygon(triangles, doc_id, &polygon); } Geometry::MultiPolygon(multi_polygon) => { for polygon in multi_polygon { - into_polygon(triangles, doc_id, polygon); + append_polygon(triangles, doc_id, &polygon); } } Geometry::GeometryCollection(geometries) => { @@ -62,7 +62,7 @@ impl SpatialWriter { .sum() } - /// HUSH + /// Serializing our field. pub fn serialize(&mut self, mut serializer: SpatialSerializer) -> io::Result<()> { for (field, triangles) in &mut self.triangles_by_field { serializer.serialize_field(*field, triangles)?; @@ -81,7 +81,7 @@ impl Default for SpatialWriter { } } -/// HUSH +/// Convert a point of (longitude, latitude) to a integer point. pub fn as_point_i32(point: (f64, f64)) -> (i32, i32) { ( (point.0 / (360.0 / (1i64 << 32) as f64)).floor() as i32, @@ -89,43 +89,39 @@ pub fn as_point_i32(point: (f64, f64)) -> (i32, i32) { ) } -fn into_point(triangles: &mut Vec, doc_id: DocId, point: (f64, f64)) { +fn append_point(triangles: &mut Vec, doc_id: DocId, point: (f64, f64)) { let point = as_point_i32(point); - triangles.push(Triangle::new( - doc_id, - [point.1, point.0, point.1, point.0, point.1, point.0], - [true, true, true], - )); + triangles.push(Triangle::from_point(doc_id, point.0, point.1)); } -fn into_line_string(triangles: &mut Vec, doc_id: DocId, line_string: Vec<(f64, f64)>) { +fn append_line_string( + triangles: &mut Vec, + doc_id: DocId, + line_string: Vec<(f64, f64)>, +) { let mut previous = as_point_i32(line_string[0]); for point in line_string.into_iter().skip(1) { let point = as_point_i32(point); - triangles.push(Triangle::new( - doc_id, - [ - previous.1, previous.0, point.1, point.0, previous.1, previous.0, - ], - [true, true, true], + triangles.push(Triangle::from_line_segment( + doc_id, previous.0, previous.1, point.0, point.1, )); previous = point } } -fn into_ring(i_polygon: &mut Vec>, ring: Vec<(f64, f64)>) { - let mut i_ring = Vec::new(); - for point in ring { +fn append_ring(i_polygon: &mut Vec>, ring: &[(f64, f64)]) { + let mut i_ring = Vec::with_capacity(ring.len() + 1); + for &point in ring { let point = as_point_i32(point); i_ring.push(IntPoint::new(point.0, point.1)); } i_polygon.push(i_ring); } -fn into_polygon(triangles: &mut Vec, doc_id: DocId, polygon: Vec>) { - let mut i_polygon = Vec::new(); +fn append_polygon(triangles: &mut Vec, doc_id: DocId, polygon: &[Vec<(f64, f64)>]) { + let mut i_polygon: Vec> = Vec::new(); for ring in polygon { - into_ring(&mut i_polygon, ring); + append_ring(&mut i_polygon, ring); } let delaunay = i_polygon.triangulate().into_delaunay(); delaunay_to_triangles(doc_id, &delaunay, triangles);