Update README with project details and setup instructions; add data directories to .gitignore

This commit is contained in:
Leonid Pershin
2026-08-16 17:09:16 +03:00
parent 9d09d6e97f
commit 8460921bfa
73 changed files with 6978 additions and 1 deletions
+152
View File
@@ -0,0 +1,152 @@
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<IReadOnlyList<Vector2>> 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<IReadOnlyList<Vector2>> 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<IReadOnlyList<Vector2>> 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<IReadOnlyList<Vector2>> 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);
}
}