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)