using System.Numerics; using TheLivingWorld.Core.Geo; using TheLivingWorld.Osm.Import; namespace TheLivingWorld.Tests; public class GeometryClipperTests { private static readonly RectBounds Unit = new(0, 0, 100, 100); [Fact] public void Leaves_a_polygon_that_is_already_inside_alone() { Vector2[] square = [new(10, 10), new(90, 10), new(90, 90), new(10, 90)]; var clipped = GeometryClipper.ClipPolygon(square, Unit); Assert.NotNull(clipped); Assert.Equal(4, clipped.Length); Assert.Equal(Polygons.Area(square), Polygons.Area(clipped), 1); } [Fact] public void Trims_a_polygon_that_hangs_over_the_edge() { Vector2[] overhanging = [new(50, 50), new(200, 50), new(200, 90), new(50, 90)]; var clipped = GeometryClipper.ClipPolygon(overhanging, Unit); Assert.NotNull(clipped); Assert.All(clipped, point => Assert.InRange(point.X, 0, 100)); Assert.Equal(50f * 40f, Polygons.Area(clipped), 1); } [Fact] public void Drops_a_polygon_that_misses_the_box_entirely() { Vector2[] elsewhere = [new(500, 500), new(600, 500), new(600, 600)]; Assert.Null(GeometryClipper.ClipPolygon(elsewhere, Unit)); } [Fact] public void Keeps_the_part_of_a_line_that_crosses_the_box() { Vector2[] line = [new(-50, 50), new(150, 50)]; var runs = GeometryClipper.ClipPolyline(line, Unit); var run = Assert.Single(runs); Assert.Equal(new Vector2(0, 50), run[0]); Assert.Equal(new Vector2(100, 50), run[^1]); } [Fact] public void Splits_a_line_that_leaves_and_comes_back() { Vector2[] line = [new(10, 50), new(150, 50), new(150, 20), new(10, 20)]; var runs = GeometryClipper.ClipPolyline(line, Unit); Assert.Equal(2, runs.Count); Assert.All(runs, run => Assert.All(run, point => Assert.InRange(point.X, 0, 100))); } [Fact] public void Drops_a_line_that_never_touches_the_box() { Vector2[] line = [new(-50, -50), new(-10, -20)]; Assert.Empty(GeometryClipper.ClipPolyline(line, Unit)); } } public class PolygonsTests { [Fact] public void Computes_the_area_of_a_square() { Vector2[] square = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; Assert.Equal(100f, Polygons.Area(square)); } [Fact] public void Detects_containment() { Vector2[] square = [new(0, 0), new(10, 0), new(10, 10), new(0, 10)]; Assert.True(Polygons.Contains(square, new Vector2(5, 5))); Assert.False(Polygons.Contains(square, new Vector2(15, 5))); } } public class RingAssemblerTests { [Fact] public void Joins_fragments_into_one_ring_whatever_their_order_or_direction() { // A 10x10 square split into three fragments; the middle one runs backwards. List> fragments = [ [new Vector2(10, 10), new Vector2(0, 10), new Vector2(0, 0)], [new Vector2(0, 0), new Vector2(10, 0)], [new Vector2(10, 10), new Vector2(10, 0)], ]; var rings = RingAssembler.Assemble(fragments); var ring = Assert.Single(rings); Assert.Equal(4, ring.Length); Assert.Equal(100f, Polygons.Area(ring)); } [Fact] public void Accepts_a_way_that_is_already_closed() { List> fragments = [ [new Vector2(0, 0), new Vector2(10, 0), new Vector2(10, 10), new Vector2(0, 10), new Vector2(0, 0)], ]; var ring = Assert.Single(RingAssembler.Assemble(fragments)); // The repeated closing vertex is dropped: a ring is stored open. Assert.Equal(4, ring.Length); } [Fact] public void Drops_fragments_that_never_close() { List> fragments = [ [new Vector2(0, 0), new Vector2(10, 0)], [new Vector2(10, 0), new Vector2(10, 10)], ]; Assert.Empty(RingAssembler.Assemble(fragments)); } [Fact] public void Separates_two_independent_rings() { List> fragments = [ [new Vector2(0, 0), new Vector2(10, 0), new Vector2(10, 10), new Vector2(0, 10), new Vector2(0, 0)], [new Vector2(50, 50), new Vector2(60, 50), new Vector2(60, 60), new Vector2(50, 50)], ]; Assert.Equal(2, RingAssembler.Assemble(fragments).Count); } }