wbtopology 0.1.2

A pure-Rust topology suite inspired by JTS
Documentation
use wbtopology::{node_linestrings, Coord, LineString};

fn has_endpoint(ls: &LineString, p: Coord, eps: f64) -> bool {
    ls.coords
        .iter()
        .any(|c| (c.x - p.x).abs() <= eps && (c.y - p.y).abs() <= eps)
}

#[test]
fn noding_splits_two_crossing_segments() {
    let lines = vec![
        LineString::new(vec![Coord::xy(0.0, 5.0), Coord::xy(10.0, 5.0)]),
        LineString::new(vec![Coord::xy(5.0, 0.0), Coord::xy(5.0, 10.0)]),
    ];

    let out = node_linestrings(&lines, 1.0e-9);
    assert_eq!(out.len(), 4);

    let center = Coord::xy(5.0, 5.0);
    let touching_center = out
        .iter()
        .filter(|ls| has_endpoint(ls, center, 1.0e-9))
        .count();
    assert_eq!(touching_center, 4);
}

#[test]
fn noding_preserves_non_intersecting_line() {
    let lines = vec![LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xy(5.0, 0.0)])];
    let out = node_linestrings(&lines, 1.0e-9);
    assert_eq!(out.len(), 1);
    assert_eq!(out[0].coords[0], Coord::xy(0.0, 0.0));
    assert_eq!(out[0].coords[1], Coord::xy(5.0, 0.0));
}

#[test]
fn noding_splits_t_junction() {
    let lines = vec![
        LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xy(10.0, 0.0)]),
        LineString::new(vec![Coord::xy(5.0, 0.0), Coord::xy(5.0, 4.0)]),
    ];

    let out = node_linestrings(&lines, 1.0e-9);
    assert_eq!(out.len(), 3);

    let junction = Coord::xy(5.0, 0.0);
    let touching = out
        .iter()
        .filter(|ls| has_endpoint(ls, junction, 1.0e-9))
        .count();
    assert_eq!(touching, 3);
}

#[test]
fn noding_splits_collinear_overlap_boundaries() {
    let lines = vec![
        LineString::new(vec![Coord::xy(0.0, 0.0), Coord::xy(10.0, 0.0)]),
        LineString::new(vec![Coord::xy(2.0, 0.0), Coord::xy(8.0, 0.0)]),
    ];

    let out = node_linestrings(&lines, 1.0e-9);
    assert_eq!(out.len(), 4);

    let at2 = Coord::xy(2.0, 0.0);
    let at8 = Coord::xy(8.0, 0.0);
    assert!(out.iter().any(|ls| has_endpoint(ls, at2, 1.0e-9)));
    assert!(out.iter().any(|ls| has_endpoint(ls, at8, 1.0e-9)));
}

#[test]
fn noding_splits_large_coordinate_crossing_segments() {
    let base = 1.0e12;
    let span = 1.0e6;
    let lines = vec![
        LineString::new(vec![Coord::xy(base, base), Coord::xy(base + span, base + span)]),
        LineString::new(vec![Coord::xy(base, base + span), Coord::xy(base + span, base)]),
    ];

    let out = node_linestrings(&lines, 1.0e-9);
    assert_eq!(out.len(), 4);

    let center = Coord::xy(base + span * 0.5, base + span * 0.5);
    let touching_center = out
        .iter()
        .filter(|ls| has_endpoint(ls, center, 1.0e-6))
        .count();
    assert_eq!(touching_center, 4);
}

#[test]
fn noding_intersection_points_interpolate_z_per_segment() {
    let lines = vec![
        LineString::new(vec![
            Coord::xyz(0.0, 5.0, 0.0),
            Coord::xyz(10.0, 5.0, 10.0),
        ]),
        LineString::new(vec![
            Coord::xyz(5.0, 0.0, 100.0),
            Coord::xyz(5.0, 10.0, 200.0),
        ]),
    ];

    let out = node_linestrings(&lines, 1.0e-9);
    let center = Coord::xy(5.0, 5.0);
    let mut z_values: Vec<f64> = out
        .iter()
        .flat_map(|ls| ls.coords.iter())
        .filter(|c| (c.x - center.x).abs() <= 1.0e-9 && (c.y - center.y).abs() <= 1.0e-9)
        .filter_map(|c| c.z)
        .collect();

    z_values.sort_by(|a, b| a.total_cmp(b));
    z_values.dedup_by(|a, b| (*a - *b).abs() <= 1.0e-9);

    assert_eq!(z_values, vec![5.0, 150.0]);
}