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)