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/overlay_geom.test.mjs | |
| download | worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.tar.gz worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.zip | |
worldmap-viewer: initial public history
Diffstat (limited to 'viewer/tests/overlay_geom.test.mjs')
| -rw-r--r-- | viewer/tests/overlay_geom.test.mjs | 69 |
1 files changed, 69 insertions, 0 deletions
diff --git a/viewer/tests/overlay_geom.test.mjs b/viewer/tests/overlay_geom.test.mjs new file mode 100644 index 0000000..c4c6c6e --- /dev/null +++ b/viewer/tests/overlay_geom.test.mjs @@ -0,0 +1,69 @@ +import './_planet.mjs'; +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import * as O from '../js/overlay_geom.js'; +import { isClick } from '../js/input.js'; + +const fc = { type: 'FeatureCollection', features: [ + { type: 'Feature', properties: { order: 4 }, geometry: { type: 'LineString', coordinates: [[0, 0], [10, 0]] } }, + { type: 'Feature', properties: { order: 1 }, geometry: { type: 'LineString', coordinates: [[0, 5], [1, 5]] } }, + { type: 'Feature', properties: {}, geometry: { type: 'Polygon', coordinates: [[[0, 0], [1, 0], [1, 1], [0, 0]], [[0.2, 0.2], [0.4, 0.2], [0.2, 0.4], [0.2, 0.2]]] } }, + { type: 'Feature', properties: {}, geometry: { type: 'MultiPolygon', coordinates: [[[[5, 5], [6, 5], [6, 6], [5, 5]]]] } }, +] }; + +test('linesFromGeoJSON reads all geometry kinds and filters', () => { + assert.equal(O.linesFromGeoJSON(fc).length, 5); + const big = O.linesFromGeoJSON(fc, p => (p.order ?? 0) >= 3); + assert.equal(big.length, 1); + assert.deepEqual(big[0][1], { lat: 0, lon: 10 }); +}); + +test('polygon rings are split where they run along the antimeridian cut', () => { + const cut = { type: 'FeatureCollection', features: [{ type: 'Feature', properties: {}, geometry: { type: 'Polygon', + coordinates: [[[170, 0], [180, 0], [180, 10], [170, 10], [170, 0]]] } }] }; + const lines = O.linesFromGeoJSON(cut); + assert.equal(lines.length, 2); + for (const l of lines) for (let i = 0; i + 1 < l.length; i++) + assert.ok(!(Math.abs(l[i].lon) === 180 && Math.abs(l[i + 1].lon) === 180), 'no segment along lon ±180'); +}); + +test('globe segments lie just above the unit sphere and follow great circles', () => { + const pos = O.globeSegments([[{ lat: 0, lon: 0 }, { lat: 0, lon: 10 }]], 1.002, 1); + assert.equal(pos.length, 10 * 6); + for (let i = 0; i < pos.length; i += 3) assert.ok(Math.abs(Math.hypot(pos[i], pos[i + 1], pos[i + 2]) - 1.002) < 1e-6); +}); + +test('flat segments drop antimeridian jumps', () => { + for (const view of ['equal_earth', 'plate_carree']) { + assert.equal(O.flatSegments([[{ lat: 10, lon: 179 }, { lat: 10, lon: -179 }]], view).length, 0); + assert.equal(O.flatSegments([[{ lat: 10, lon: 170 }, { lat: 10, lon: 179 }]], view).length, 6); + } +}); + +test('densify and graticule', () => { + const d = O.densify([[{ lat: 0, lon: 0 }, { lat: 0, lon: 3 }, { lat: 0, lon: 5 }]], 1); + assert.equal(d[0].length, 6); + assert.equal(O.graticuleLines(30).length, 13 + 5); +}); + +test('clicks vs drags', () => { + assert.equal(isClick({ x: 10, y: 10, t: 0 }, { x: 12, y: 11, t: 200 }), true); + assert.equal(isClick({ x: 10, y: 10, t: 0 }, { x: 40, y: 10, t: 200 }), false); + assert.equal(isClick({ x: 10, y: 10, t: 0 }, { x: 10, y: 10, t: 2000 }), false); +}); + +test('drapedSegments: lines near the camera lie on the ground, finer close up; far ones are skipped', async () => { + const { drapedSegments } = await import('../js/overlay_geom.js'); + const { toVec, R_KM } = await import('../js/geo.js'); + const R_M = R_KM * 1000, eyeDir = toVec(40, -10), eye = eyeDir.map(v => v * (1 + 1500 / R_M)); // above the ground + const opts = { eye, n: eyeDir, cosCap: Math.cos(0.01), origin: eyeDir, heightM: lat => (lat > 40 ? 1000 : 200) }; + const near = [[{ lat: 40.001, lon: -10 }, { lat: 40.03, lon: -10 }]]; // ≈ 200 m … 4 km from the camera + const far = [[{ lat: 60, lon: 50 }, { lat: 61, lon: 50 }]]; + const p = drapedSegments([...near, ...far], opts); + assert.ok(p.length / 6 > 10, `near segment cut into pieces: ${p.length / 6}`); + for (let i = 0; i < p.length; i += 3) { + const r = Math.hypot(p[i] + eyeDir[0], p[i + 1] + eyeDir[1], p[i + 2] + eyeDir[2]); + assert.ok(r > 1 + 1000 / R_M && r < 1 + 1020 / R_M, `vertex ${i / 3} at ${(r - 1) * R_M} m`); // ground + 1 m, pulled ≤ 1% up + } + assert.equal(drapedSegments(far, opts).length, 0); +}); |
