Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions extractrs-core/src/bbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,16 @@ impl BBox {
/// Uses quadrant-based projection with clamping for numerical robustness.
///
/// Port of exactextract's `Box::crossing()` (box.cpp:43-118).
///
/// **Degenerate fallback:** the original exactextract implementation
/// throws "Never get here" when a vertical/horizontal segment lies
/// entirely outside the box (i.e., the line never intersects this box).
/// This can be triggered by low-quality polygon data with collinear
/// vertices that happen to fall outside the current cell — see
/// isciences/exactextract#126. Rather than panic, we return the box
/// side closest to the segment so the traversal can make progress.
/// Callers should also avoid invoking `crossing()` in this state when
/// possible; see `Cell::take()` for the upstream guard.
pub fn crossing(&self, c1: &Coord, c2: &Coord) -> Crossing {
// Vertical line (x constant)
if c1.x == c2.x {
Expand All @@ -162,7 +172,14 @@ impl BBox {
} else if c2.y <= self.ymin {
return Crossing::new(Side::Bottom, c1.x, self.ymin);
} else {
unreachable!("vertical line doesn't exit box");
// Degenerate: vertical line entirely outside the box on x.
// Return the closest side so the traversal can advance.
let y = clamp(c2.y, self.ymin, self.ymax);
if c1.x <= self.xmin {
return Crossing::new(Side::Left, self.xmin, y);
} else {
return Crossing::new(Side::Right, self.xmax, y);
}
}
}

Expand All @@ -173,7 +190,13 @@ impl BBox {
} else if c2.x <= self.xmin {
return Crossing::new(Side::Left, self.xmin, c1.y);
} else {
unreachable!("horizontal line doesn't exit box");
// Degenerate: horizontal line entirely outside the box on y.
let x = clamp(c2.x, self.xmin, self.xmax);
if c1.y <= self.ymin {
return Crossing::new(Side::Bottom, x, self.ymin);
} else {
return Crossing::new(Side::Top, x, self.ymax);
}
}
}

Expand Down Expand Up @@ -290,6 +313,40 @@ mod tests {
assert!((c.coord.y - 1.0).abs() < 1e-10);
}

#[test]
fn test_crossing_vertical_degenerate_left_of_box() {
// Vertical segment whose x is strictly left of the box.
// Pre-fix this would panic with "vertical line doesn't exit box".
let b = unit_box();
let c = b.crossing(&Coord::new(-2.0, 0.5), &Coord::new(-2.0, 0.5));
assert_eq!(c.side, Side::Left);
assert_eq!(c.coord.x, 0.0);
}

#[test]
fn test_crossing_vertical_degenerate_right_of_box() {
let b = unit_box();
let c = b.crossing(&Coord::new(5.0, 0.5), &Coord::new(5.0, 0.5));
assert_eq!(c.side, Side::Right);
assert_eq!(c.coord.x, 1.0);
}

#[test]
fn test_crossing_horizontal_degenerate_above_box() {
let b = unit_box();
let c = b.crossing(&Coord::new(0.5, 5.0), &Coord::new(0.5, 5.0));
assert_eq!(c.side, Side::Top);
assert_eq!(c.coord.y, 1.0);
}

#[test]
fn test_crossing_horizontal_degenerate_below_box() {
let b = unit_box();
let c = b.crossing(&Coord::new(0.5, -2.0), &Coord::new(0.5, -2.0));
assert_eq!(c.side, Side::Bottom);
assert_eq!(c.coord.y, 0.0);
}

#[test]
fn test_crossing_diagonal_steep_up_right() {
let b = unit_box();
Expand Down
14 changes: 13 additions & 1 deletion extractrs-core/src/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,19 @@ impl Cell {
// Coordinate exits cell.
// Use prev_original for crossing computation to avoid robustness issues
// from interpolated coordinates (exactextract cell.cpp:126-129).
let crossing = if let Some(prev) = prev_original {
//
// Skip prev_original if it would form a degenerate vertical/horizontal
// segment lying entirely outside the cell — last_coordinate is always
// on the boundary or interior so it's a safe fallback. See
// isciences/exactextract#126 for the data conditions that trigger this.
let prev_safe = prev_original.filter(|prev| {
let vertical_outside = prev.x == c.x
&& (prev.x < self.bbox.xmin || prev.x > self.bbox.xmax);
let horizontal_outside = prev.y == c.y
&& (prev.y < self.bbox.ymin || prev.y > self.bbox.ymax);
!vertical_outside && !horizontal_outside
});
let crossing = if let Some(prev) = prev_safe {
self.bbox.crossing(prev, c)
} else {
let last = *self.traversals[idx].last_coordinate();
Expand Down
Loading