geo-polygonize-core 0.63.0

A native Rust port of the JTS/GEOS polygonization algorithm. Reconstruct valid polygons from a set of lines.
Documentation
use geo_polygonize_core::{
    polygonize_line_strings_with_execution_policy, polygonize_with_execution_policy, Coord3D,
    ExecutionPolicy, Line3D, NodingGuarantee, NodingOptions, PolygonizeError, PolygonizerOptions,
    PrecisionModel, SnapStrategy,
};
use geo_types::LineString;

fn assert_limit(error: PolygonizeError, stage: &str, limit: usize, observed: usize) {
    assert!(matches!(
        error,
        PolygonizeError::ResourceLimitExceeded {
            stage: actual_stage,
            limit: actual_limit,
            observed: actual_observed,
        } if actual_stage == stage && actual_limit == limit && actual_observed == observed
    ));
}

#[test]
fn input_limits_stop_before_polygonization() {
    let line = LineString::from(vec![(0., 0.), (1., 0.), (1., 1.)]);
    let options = PolygonizerOptions::default();

    assert_limit(
        polygonize_line_strings_with_execution_policy(
            [&line],
            &options,
            &ExecutionPolicy {
                max_input_line_strings: Some(0),
                ..Default::default()
            },
        )
        .unwrap_err(),
        "input_line_strings",
        0,
        1,
    );
    assert_limit(
        polygonize_line_strings_with_execution_policy(
            [&line],
            &options,
            &ExecutionPolicy {
                max_input_coordinates: Some(2),
                ..Default::default()
            },
        )
        .unwrap_err(),
        "input_coordinates",
        2,
        3,
    );

    let segment = Line3D::new(Coord3D::new(0., 0., 0.), Coord3D::new(1., 0., 0.), 0);
    assert_limit(
        polygonize_with_execution_policy(
            [segment.clone(), segment],
            &options,
            &ExecutionPolicy {
                max_input_segments: Some(1),
                ..Default::default()
            },
        )
        .unwrap_err(),
        "input_segments",
        1,
        2,
    );
}

#[test]
fn noded_segment_limit_stops_after_noding() {
    let options = PolygonizerOptions {
        node_input: true,
        ..Default::default()
    };
    let crossing = [
        Line3D::new(Coord3D::new(0., 0., 0.), Coord3D::new(2., 2., 0.), 0),
        Line3D::new(Coord3D::new(0., 2., 0.), Coord3D::new(2., 0., 0.), 1),
    ];

    assert!(matches!(
        polygonize_with_execution_policy(
            crossing,
            &options,
            &ExecutionPolicy {
                max_noded_segments: Some(0),
                ..Default::default()
            },
        ),
        Err(PolygonizeError::ResourceLimitExceeded {
            stage,
            limit: 0,
            observed,
        }) if stage == "noded_segments" && observed > 0
    ));
}

#[test]
fn noding_work_limits_stop_dense_candidates_before_splitting() {
    let options = PolygonizerOptions {
        node_input: true,
        ..Default::default()
    };
    let crossing = [
        Line3D::new(Coord3D::new(0., 0., 0.), Coord3D::new(2., 2., 0.), 0),
        Line3D::new(Coord3D::new(0., 2., 0.), Coord3D::new(2., 0., 0.), 1),
    ];

    for (policy, stage) in [
        (
            ExecutionPolicy {
                max_candidate_pairs: Some(0),
                ..Default::default()
            },
            "candidate_pairs",
        ),
        (
            ExecutionPolicy {
                max_exact_intersection_calls: Some(0),
                ..Default::default()
            },
            "exact_intersection_calls",
        ),
    ] {
        assert!(matches!(
            polygonize_with_execution_policy(crossing, &options, &policy),
            Err(PolygonizeError::ResourceLimitExceeded {
                stage: actual_stage,
                limit: 0,
                observed,
            }) if actual_stage == stage && observed > 0
        ));
    }
}

#[test]
fn certified_noding_obeys_candidate_limit() {
    let options = PolygonizerOptions {
        node_input: true,
        precision_model: PrecisionModel::FixedGrid { grid_size: 1.0 },
        snap_strategy: SnapStrategy::Grid,
        noding: NodingOptions {
            guarantee: NodingGuarantee::CertifiedFixedPrecision,
            ..Default::default()
        },
        ..Default::default()
    };
    let crossing = [
        Line3D::new(Coord3D::new(0., 0., 0.), Coord3D::new(2., 2., 0.), 0),
        Line3D::new(Coord3D::new(0., 2., 0.), Coord3D::new(2., 0., 0.), 1),
    ];

    assert_limit(
        polygonize_with_execution_policy(
            crossing,
            &options,
            &ExecutionPolicy {
                max_candidate_pairs: Some(0),
                ..Default::default()
            },
        )
        .unwrap_err(),
        "candidate_pairs",
        0,
        1,
    );
}

#[test]
fn split_and_iteration_limits_stop_noding() {
    let options = PolygonizerOptions {
        node_input: true,
        ..Default::default()
    };
    let crossing = [
        Line3D::new(Coord3D::new(0., 0., 0.), Coord3D::new(2., 2., 0.), 0),
        Line3D::new(Coord3D::new(0., 2., 0.), Coord3D::new(2., 0., 0.), 1),
    ];

    for (policy, stage) in [
        (
            ExecutionPolicy {
                max_split_events: Some(0),
                ..Default::default()
            },
            "split_events",
        ),
        (
            ExecutionPolicy {
                max_noding_iterations: Some(0),
                ..Default::default()
            },
            "noding_iterations",
        ),
    ] {
        assert!(matches!(
            polygonize_with_execution_policy(crossing, &options, &policy),
            Err(PolygonizeError::ResourceLimitExceeded {
                stage: actual_stage,
                limit: 0,
                observed,
            }) if actual_stage == stage && observed > 0
        ));
    }
}