diff options
| author | godosa <godosa@godosa.eu> | 2026-10-06 23:52:03 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-06 23:52:03 +0200 |
| commit | 346b1c5195bffc71ceaa9262453e3c189656400b (patch) | |
| tree | 01ac0d31e2724cd6abcc689a5a228e2cbea2f6cf /tests/test_geo.py | |
| download | worldgen-346b1c5195bffc71ceaa9262453e3c189656400b.tar.gz worldgen-346b1c5195bffc71ceaa9262453e3c189656400b.zip | |
worldgen: initial public history
Diffstat (limited to 'tests/test_geo.py')
| -rw-r--r-- | tests/test_geo.py | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/tests/test_geo.py b/tests/test_geo.py new file mode 100644 index 0000000..d438299 --- /dev/null +++ b/tests/test_geo.py @@ -0,0 +1,84 @@ +import unittest +from types import SimpleNamespace + +import numpy as np + +from mapgen import geo as GE +from mapgen.plates import CONV +from tests.helpers import small_grid + + +def _area(ring): + a = np.asarray(ring, dtype=float) + return 0.5 * float(np.sum(a[:-1, 0] * a[1:, 1] - a[1:, 0] * a[:-1, 1])) + + +class GeoTest(unittest.TestCase): + def test_split_antimeridian(self): + parts = GE.split_antimeridian([[178.0, 0.0], [179.5, 1.0], [-179.5, 1.0], [-178.0, 0.0]]) + self.assertEqual(len(parts), 2) + self.assertEqual(parts[0][-1], [179.5, 1.0]) + + def test_river_split_at_antimeridian(self): + g = SimpleNamespace(n=4, lat=np.zeros(4), lon=np.array([170.0, 178.0, -178.0, -170.0])) + recv = np.array([1, 2, 3, 3]) + feats = GE.river_lines(g, recv, np.array([True, True, True, False]), np.array([1, 1, 2, 0], np.int8), + np.array([1.0, 2.0, 3.0, 0.0])) + self.assertEqual(len(feats), 2) + for f in feats: + xs = [c[0] for c in f["geometry"]["coordinates"]] + self.assertLess(max(xs) - min(xs), 180) + + def test_contour_square(self): + m = np.zeros((10, 20), bool) + m[3:7, 5:9] = True + rings = GE.contours(m) + self.assertEqual(len(rings), 1) + r = np.array(rings[0]) + self.assertEqual(tuple(r[0]), tuple(r[-1])) + self.assertTrue(4 <= r[:, 0].min() and r[:, 0].max() <= 9 and 2 <= r[:, 1].min() and r[:, 1].max() <= 7) + + def test_contour_ring_split(self): + m = np.zeros((10, 20), bool) + m[3:7, :3] = True + m[3:7, 17:] = True # one island wrapping the antimeridian + feats = GE.contour_features(m, "land") + self.assertEqual(len(feats), 1) + geom = feats[0]["geometry"] + self.assertEqual(geom["type"], "MultiPolygon") # fillable, split at ±180 + for poly in geom["coordinates"]: + ring = poly[0] + self.assertEqual(ring[0], ring[-1]) + xs = [c[0] for c in ring] + self.assertTrue(-180 <= min(xs) and max(xs) <= 180 and max(xs) - min(xs) < 180) + self.assertGreater(_area(ring), 0) # counter-clockwise exterior + + def test_polygon_orientation_and_holes(self): + m = np.zeros((20, 40), bool) + m[4:16, 8:24] = True + m[8:12, 13:19] = False # a lake-shaped hole + feats = GE.contour_features(m, "land") + self.assertEqual(len(feats), 1) + geom = feats[0]["geometry"] + self.assertEqual(geom["type"], "Polygon") + self.assertEqual(len(geom["coordinates"]), 2) + self.assertGreater(_area(geom["coordinates"][0]), 0) # exterior CCW + self.assertLess(_area(geom["coordinates"][1]), 0) # hole CW + + def test_river_features_have_single_order(self): + g = SimpleNamespace(n=6, lat=np.arange(6.0), lon=np.zeros(6)) + recv = np.array([1, 2, 3, 4, 5, 5]) + order = np.array([1, 1, 2, 2, 3, 0], np.int8) + feats = GE.river_lines(g, recv, np.array([True] * 5 + [False]), order, np.arange(6.0)) + self.assertEqual([f["properties"]["order"] for f in feats], [1, 2, 3]) + for f in feats: + self.assertGreaterEqual(len(f["geometry"]["coordinates"]), 2) + + def test_plate_boundaries(self): + g = small_grid(1) + plate = (g.lon > 0).astype(np.int16) + bt = np.full(g.n, CONV, np.int8) + feats = GE.boundary_features(g, plate, bt) + self.assertEqual(len(feats), 1) + self.assertEqual(feats[0]["properties"]["type"], "convergent") + self.assertGreater(len(feats[0]["geometry"]["coordinates"]), 5) |
