diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 00:14:38 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 00:14:38 +0200 |
| commit | 3443c1c65e9f1753e1e656b35d08416c1fa298f2 (patch) | |
| tree | 4e43236f460145a4d75d1b4616dcb7aa6ef08f51 /viewer/tests/geo.test.mjs | |
| download | worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.tar.gz worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.zip | |
worldmap-viewer: initial public history
Diffstat (limited to 'viewer/tests/geo.test.mjs')
| -rw-r--r-- | viewer/tests/geo.test.mjs | 77 |
1 files changed, 77 insertions, 0 deletions
diff --git a/viewer/tests/geo.test.mjs b/viewer/tests/geo.test.mjs new file mode 100644 index 0000000..4ebb2ea --- /dev/null +++ b/viewer/tests/geo.test.mjs @@ -0,0 +1,77 @@ +import './_planet.mjs'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import * as G from '../js/geo.js'; + +const near = (a, b, eps = 1e-9) => assert.ok(Math.abs(a - b) <= eps, `${a} vs ${b}`); + +test('lat/lon <-> vector round trip incl. poles and seam', () => { + for (const [lat, lon] of [[0, 0], [45, 120], [-60, -170], [89.999, 10], [-89.999, -100], [10, 179.999], [10, -179.999]]) { + const p = G.toLatLon(G.toVec(lat, lon)); + near(p.lat, lat, 1e-9); + near(p.lon, lon, 1e-7); + } + const pole = G.toLatLon(G.toVec(90, 0)); + near(pole.lat, 90, 1e-9); + assert.ok(Number.isFinite(pole.lon)); +}); + +test('distances on the 12,742 km sphere', () => { + near(G.distanceKm({ lat: 0, lon: 0 }, { lat: 0, lon: 90 }), (Math.PI / 2) * 12742, 1e-6); + near(G.distanceKm({ lat: 90, lon: 0 }, { lat: -90, lon: 0 }), Math.PI * 12742, 1e-6); + near(G.distanceKm({ lat: 10, lon: 179 }, { lat: 10, lon: -179 }), G.distanceKm({ lat: 10, lon: 0 }, { lat: 10, lon: 2 }), 1e-6); +}); + +test('interpolate follows the great circle', () => { + const pts = G.interpolate({ lat: 0, lon: 0 }, { lat: 0, lon: 90 }, 10); + assert.equal(pts.length, 10); + near(pts[5].lon, 50, 1e-9); + near(pts[5].lat, 0, 1e-9); +}); + +test('spherical polygon area', () => { + near(G.polygonAreaKm2([{ lat: 0, lon: 0 }, { lat: 0, lon: 90 }, { lat: 90, lon: 0 }]), (4 * Math.PI * 12742 ** 2) / 8, 1); + const cell = ((12742 * Math.PI) / 180) ** 2; + near(G.polygonAreaKm2([{ lat: 0, lon: 0 }, { lat: 0, lon: 1 }, { lat: 1, lon: 1 }, { lat: 1, lon: 0 }]), cell, cell * 0.01); +}); + +test('equal earth matches mapgen/projections.py', () => { + const fx = JSON.parse(readFileSync(new URL('./fixtures/projections.json', import.meta.url))); + near(G.EE_XMAX, fx.ee_xmax); + near(G.EE_YMAX, fx.ee_ymax); + for (const p of fx.equal_earth) { + const q = G.project('equal_earth', p.lat, p.lon); + near(q.x, p.x); + near(q.y, p.y); + const r = G.unproject('equal_earth', p.x, p.y); + near(r.lat, p.lat, 1e-6); + near(r.lon, p.lon, 1e-6); + } + assert.equal(G.unproject('equal_earth', G.EE_XMAX, G.EE_YMAX), null); + near(G.unproject('equal_earth', 0, G.EE_YMAX).lat, 90, 1e-6); // pole survives rounding +}); + +test('plate carree projection', () => { + const q = G.project('plate_carree', 45, -90); + near(q.x, -Math.PI / 2); + near(q.y, Math.PI / 4); + assert.equal(G.unproject('plate_carree', 4, 0), null); +}); + +test('sphere mesh: uv follows lat/lon and faces point outward', () => { + const m = G.sphereMesh(8, 16); + for (let i = 0; i < m.uvs.length / 2; i++) { + const ll = G.toLatLon([m.positions[3 * i], m.positions[3 * i + 1], m.positions[3 * i + 2]]); + const u = m.uvs[2 * i], v = m.uvs[2 * i + 1]; + near((ll.lat + 90) / 180, v, 1e-6); + if (Math.abs(ll.lat) < 89.9 && u > 1e-6 && u < 1 - 1e-6) near((ll.lon + 180) / 360, u, 1e-6); + } + assert.equal(m.indices.length, 8 * 16 * 6); + const q = 3 * 16 * 6 + 5 * 6; // a quad just north of the equator + const P = k => [m.positions[3 * k], m.positions[3 * k + 1], m.positions[3 * k + 2]]; + const [a, b, c] = [P(m.indices[q]), P(m.indices[q + 1]), P(m.indices[q + 2])]; + const u = [b[0] - a[0], b[1] - a[1], b[2] - a[2]], w = [c[0] - a[0], c[1] - a[1], c[2] - a[2]]; + const n = [u[1] * w[2] - u[2] * w[1], u[2] * w[0] - u[0] * w[2], u[0] * w[1] - u[1] * w[0]]; + assert.ok(n[0] * a[0] + n[1] * a[1] + n[2] * a[2] > 0, 'triangles wind counter-clockwise seen from outside'); +}); |
