plastic surgery

This commit is contained in:
Paul Masurel
2025-12-03 16:10:11 +01:00
parent 5d03c600ba
commit 1619e05bc5
4 changed files with 107 additions and 49 deletions
+8 -3
View File
@@ -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<SpatialSerializer> =
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,
})
+43 -3
View File
@@ -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<FieldEntry>,
fields_map: HashMap<String, Field>, // 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<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
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());
}
}
}
+33 -16
View File
@@ -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]
+23 -27
View File
@@ -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<Triangle>, doc_id: DocId, point: (f64, f64)) {
fn append_point(triangles: &mut Vec<Triangle>, 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<Triangle>, doc_id: DocId, line_string: Vec<(f64, f64)>) {
fn append_line_string(
triangles: &mut Vec<Triangle>,
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<Vec<IntPoint>>, ring: Vec<(f64, f64)>) {
let mut i_ring = Vec::new();
for point in ring {
fn append_ring(i_polygon: &mut Vec<Vec<IntPoint>>, 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<Triangle>, doc_id: DocId, polygon: Vec<Vec<(f64, f64)>>) {
let mut i_polygon = Vec::new();
fn append_polygon(triangles: &mut Vec<Triangle>, doc_id: DocId, polygon: &[Vec<(f64, f64)>]) {
let mut i_polygon: Vec<Vec<IntPoint>> = 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);