worldmap-viewer

git clone https://git.godosa.eu/worldmap-viewer

master

raw ยท 3836 bytes

import './_planet.mjs';
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { fakeRenderer } from './importmap.mjs';

const { FlatView, MAX_ZOOM } = await import('../js/flatmap.js');
const near = (a, b, eps) => assert.ok(Math.abs(a - b) <= eps, `${a} vs ${b}`);
const ev = (type, props) => Object.assign(new Event(type, { cancelable: true }), { button: 0, pointerId: 1, deltaMode: 0, ...props });

function flat(kind, st = { lat: 20, lon: 30, zoom: 3 }) {
  const r = fakeRenderer();
  const v = new FlatView(r, kind, () => {});
  v.resize(800, 400);
  v.setState(st);
  v.activate();
  return { v, canvas: r.domElement };
}

for (const kind of ['equal_earth', 'plate_carree']) {
  test(`${kind}: state round trip, zoom clamped`, () => {
    const s = flat(kind).v.getState();
    near(s.lat, 20, 1e-9); near(s.lon, 30, 1e-9);
    assert.equal(s.zoom, 3);
    assert.equal(flat(kind, { lat: 0, lon: 0, zoom: 1e9 }).v.getState().zoom, MAX_ZOOM);
    assert.equal(MAX_ZOOM, 20000);
    assert.equal(flat(kind, { lat: 0, lon: 0, zoom: 0 }).v.getState().zoom, 1);
  });

  test(`${kind}: pick and toScreen agree, incl. near the seam and poles`, () => {
    const { v } = flat(kind, { lat: 0, lon: 0, zoom: 1 });
    for (const [lat, lon] of [[0, 0], [45, 179.9], [-89, -120], [60, -179.9]]) {
      const q = v.toScreen(lat, lon), p = v.pick(q.x, q.y);
      near(p.lat, lat, 1e-6); near(p.lon, lon, 1e-6);
    }
  });

  test(`${kind}: wheel zoom keeps the point under the cursor`, () => {
    const { v, canvas } = flat(kind, { lat: 10, lon: 10, zoom: 2 });
    const before = v.pick(600, 120);
    canvas.dispatchEvent(ev('wheel', { deltaY: -300, clientX: 600, clientY: 120 }));
    assert.ok(v.getState().zoom > 2);
    const after = v.pick(600, 120);
    near(after.lat, before.lat, 1e-6); near(after.lon, before.lon, 1e-6);
  });

  test(`${kind}: drag pans with the cursor; an inactive view ignores input`, () => {
    const { v, canvas } = flat(kind, { lat: 0, lon: 0, zoom: 4 });
    const p = v.pick(400, 200);
    canvas.dispatchEvent(ev('pointerdown', { clientX: 400, clientY: 200 }));
    canvas.dispatchEvent(ev('pointermove', { clientX: 300, clientY: 200 }));
    canvas.dispatchEvent(ev('pointerup', { clientX: 300, clientY: 200 }));
    const q = v.pick(300, 200);
    near(q.lat, p.lat, 1e-6); near(q.lon, p.lon, 1e-6);
    v.deactivate();
    const s = v.getState();
    canvas.dispatchEvent(ev('wheel', { deltaY: -300, clientX: 10, clientY: 10 }));
    assert.deepEqual(v.getState(), s);
  });
}

test('equal earth: outside the outline picks null', () => {
  assert.equal(flat('equal_earth', { lat: 0, lon: 0, zoom: 1 }).v.pick(2, 2), null);
});

test('long lines are drawn along the great circle (bowing poleward), not straight in the projection', () => {
  const { v } = flat('plate_carree', { lat: 0, lon: 0, zoom: 1 });
  v.setLines('m', [[{ lat: 40, lon: -60 }, { lat: 40, lon: 60 }]], 0xffffff);
  const pos = v.lines.objects.get('m').geometry.attributes.position.array;
  assert.ok(pos.length / 6 > 10, `${pos.length / 6} segments`);
  const maxLat = Math.max(...pos.filter((_, i) => i % 3 === 1)) * 180 / Math.PI;
  assert.ok(maxLat > 50, `peak latitude ${maxLat}`);
});

test('equal earth: the centre cannot leave the outline, so the saved state reproduces the view', () => {
  const { v, canvas } = flat('equal_earth', { lat: 60, lon: 170, zoom: 8 });
  canvas.dispatchEvent(ev('pointerdown', { clientX: 400, clientY: 200 }));
  canvas.dispatchEvent(ev('pointermove', { clientX: 100, clientY: 200 }));   // pan east past lon 180
  canvas.dispatchEvent(ev('pointerup', { clientX: 100, clientY: 200 }));
  const s = v.getState(), before = v.toWorld(400, 200);
  v.setState(s);
  const after = v.toWorld(400, 200);
  near(after.X, before.X, 1e-9); near(after.Y, before.Y, 1e-9);
});