louis-e/arnis · error

NaN encountered while sorting scanline intersections

Error message

NaN encountered while sorting scanline intersections

What it means

`compute_scanline_spans` sorts the x-coordinates where polygon edges cross a scanline using `a.partial_cmp(b).expect("NaN encountered while sorting scanline intersections")`. Since f64 has no total order, a NaN intersection coordinate makes `partial_cmp` return None and the code panics — NaN can only come from upstream geometry (malformed polygon vertices or a degenerate edge producing 0/0).

Source

Thrown at src/element_processing/water_areas.rs:357

    max_x: i32,
) -> Vec<(i32, i32)> {
    let mut xs: Vec<f64> = Vec::new();
    for edge in edges {
        // Crossing test: (z1 > z) != (z2 > z)
        // Matches geo's convention (bottom-inclusive, top-exclusive).
        if (edge.z1 > z) != (edge.z2 > z) {
            let t = (z - edge.z1) / (edge.z2 - edge.z1);
            xs.push(edge.x1 + t * (edge.x2 - edge.x1));
        }
    }

    if xs.is_empty() {
        return Vec::new();
    }

    xs.sort_unstable_by(|a, b| {
        a.partial_cmp(b)
            .expect("NaN encountered while sorting scanline intersections")
    });

    debug_assert!(
        xs.len().is_multiple_of(2),
        "Odd number of scanline crossings ({}) at z={}, possible malformed polygon",
        xs.len(),
        z
    );

    // Pair consecutive crossings into fill spans (even-odd rule)
    let mut spans = Vec::with_capacity(xs.len() / 2);
    let mut i = 0;
    while i + 1 < xs.len() {
        let start = (xs[i].ceil() as i32).max(min_x);
        let end = (xs[i + 1].floor() as i32).min(max_x);
        if start <= end {
            spans.push((start, end));
        }

View on GitHub (pinned to 34048924d9)

Solutions

  1. Filter or fix polygon vertices upstream: reject/repair polygons containing NaN or infinite coordinates before scanline conversion
  2. Skip degenerate edges (zero height) when computing intersections so no 0/0 occurs
  3. Validate the projection/transform output for NaN before building polygons
  4. As a stopgap, replace the expect with a total-order fallback (e.g. `a.partial_cmp(b).unwrap_or(Ordering::Equal)`) and log the offending polygon

Example fix

// before
.expect("NaN encountered while sorting scanline intersections")
// after
.unwrap_or_else(|| {
    eprintln!("NaN in scanline at z={z}; skipping polygon");
    std::cmp::Ordering::Equal
})
Defensive patterns

Strategy: validation

Validate before calling

fn polygon_has_finite_coords(poly: &[[f64; 2]]) -> bool { poly.iter().all(|p| p[0].is_finite() && p[1].is_finite()) }

Type guard

fn is_finite_point(p: &[f64; 2]) -> bool { p[0].is_finite() && p[1].is_finite() }

Try / catch

// If you must sort possibly-NaN floats: xs.sort_unstable_by(|a,b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));

Prevention

When it happens

Trigger: A water-area polygon with NaN or infinite vertex coordinates (bad/missing OSM tags, division by zero when intersecting a horizontal scanline with a degenerate vertical edge), producing NaN scanline intersections passed into the sort.

Common situations: Importing map data where a node coordinate is NaN; zero-length edges in the polygon; a coordinate transform (projection) producing NaN for points outside its domain.

Related errors


AI-assisted analysis of louis-e/arnis@34048924d9 (2026-09-03). Data as JSON: /api/errors/1fb2ce8b7ac5fbf3. Report an issue: GitHub.