ASCEND / tests /globe.test.mjs
mmrech's picture
v2.0.0: all-economy World lens with LCoGS and SDG 3.8 indicators, choropleth globe, build pipeline and tests
c140718 verified
Raw History Blame Contribute Delete
5.03 kB
import test from 'node:test';
import assert from 'node:assert/strict';
import { lonLatToVector, normaliseLongitude, cameraFocus, isMarkerVisible, metricFraction, makeGraticule, EQUIRECTANGULAR_OFFSET } from '../globe-math.mjs';
const close=(a,b)=>assert.ok(Math.abs(a-b)<1e-9,`${a} ≠ ${b}`);
test('Greenwich equator faces positive Z; eastern longitude positive X; north positive Y',()=>{
const g=lonLatToVector(0,0); close(g.x,0);close(g.y,0);close(g.z,1);
const e=lonLatToVector(90,0);close(e.x,1);close(e.z,0);
const n=lonLatToVector(0,90);close(n.y,1);close(n.z,0);
});
test('longitude latitude positions lie on requested sphere radius',()=>{
for (const [lon,lat] of [[-47.9292,-15.7801],[179.9,80],[-179.9,-80],[0,0]]){
const p=lonLatToVector(lon,lat,1.015);close(Math.hypot(p.x,p.y,p.z),1.015);
}
});
test('camera focus preserves country longitude, safely limits polar orbit, wraps longitude',()=>{
assert.deepEqual(cameraFocus({lon:-47.9292,lat:-15.7801}),{longitude:-47.9292,latitude:-15.7801});
assert.deepEqual(cameraFocus({lon:370,lat:89}),{longitude:10,latitude:75});
close(normaliseLongitude(-181),179);
});
test('backside reference points and below-horizon markers are hidden',()=>{
assert.equal(isMarkerVisible(0,0,{longitude:0,latitude:0},3),true);
assert.equal(isMarkerVisible(180,0,{longitude:0,latitude:0},3),false);
assert.equal(isMarkerVisible(80,0,{longitude:0,latitude:0},3),false);
assert.equal(isMarkerVisible(-47.9292,-15.7801,{longitude:-47.9292,latitude:-15.7801},3),true);
});
test('metric encoding preserves null, respects ordered bounds, uniform marker sizing independent of value',()=>{
assert.equal(metricFraction(null,0,100),null);assert.equal(metricFraction(undefined,0,100),null);
close(metricFraction(0,0,100),0);close(metricFraction(100,0,100),1);close(metricFraction(50,0,100),.5);
close(metricFraction(-5,0,100),0);close(metricFraction(200,0,100),1);
});
test('graticule consists of sphere surface segments, including Greenwich',()=>{
const segments=makeGraticule(30,4,1.002); assert.ok(segments.length>100);
assert.ok(segments.some(([a,b])=>Math.abs(a.x)<1e-9&&Math.abs(b.x)<1e-9&&a.z>0&&b.z>0));
for(const [a,b] of segments){close(Math.hypot(a.x,a.y,a.z),1.002);close(Math.hypot(b.x,b.y,b.z),1.002);}
});
import {SphereGeometry} from '../vendor/three-0.160.1.module.js';
test('the actual Three.js sphere UVs sample the correct Natural Earth texture longitude',()=>{
const sphere=new SphereGeometry(1,64,32),positions=sphere.getAttribute('position'),uvs=sphere.getAttribute('uv');
for(const lon of [0,90,-90,180]){
const wanted=lonLatToVector(lon,0);let index=-1;
for(let i=0;i<positions.count;i++)if(Math.abs(positions.getX(i)-wanted.x)<1e-6&&Math.abs(positions.getY(i)-wanted.y)<1e-6&&Math.abs(positions.getZ(i)-wanted.z)<1e-6){index=i;break;}
assert.ok(index>=0,'cardinal longitude exists in the real sphere geometry');
close((uvs.getX(index)+EQUIRECTANGULAR_OFFSET)%1,((lon+180)/360)%1);
close(1-uvs.getY(index),.5);
}
sphere.dispose();
});
import { vectorToLonLat, pointInRing, pointInPolygons, scaleFraction, rampColor, RAMP } from '../globe-math.mjs';
import countries from '../assets/countries-110m.mjs';
test('vectorToLonLat inverts lonLatToVector across hemispheres',()=>{
for (const [lon,lat] of [[-47.9292,-15.7801],[139.69,35.69],[-0.1276,51.5072],[179.5,-45],[-179.5,70]]){
const p=lonLatToVector(lon,lat,1.5),r=vectorToLonLat(p.x,p.y,p.z);
assert.ok(Math.abs(normaliseLongitude(r.lon-lon))<1e-6&&Math.abs(r.lat-lat)<1e-6,`${lon},${lat} -> ${r.lon},${r.lat}`);
}
});
test('point-in-polygon with holes (even-odd) behaves on a square with a hole',()=>{
const outer=[[0,0],[10,0],[10,10],[0,10],[0,0]],hole=[[4,4],[6,4],[6,6],[4,6],[4,4]];
assert.equal(pointInRing(5,5,outer),true);assert.equal(pointInRing(11,5,outer),false);
assert.equal(pointInPolygons(2,2,[[outer,hole]]),true);assert.equal(pointInPolygons(5,5,[[outer,hole]]),false);
});
test('capital coordinates fall inside their own 1:110m polygon for large economies',()=>{
const byId=new Map(countries.countries.map(c=>[c.id,c]));
for (const [id,lon,lat] of [['BRA',-47.9292,-15.7801],['IND',77.225,28.6353],['USA',-77.032,38.8895],['KEN',36.8126,-1.27975],['AUS',149.129,-35.282],['RUS',37.6176,55.7558]]){
assert.ok(byId.has(id),id+' geometry present');assert.equal(pointInPolygons(lon,lat,byId.get(id).polygons),true,id);
}
assert.ok(countries.countries.length>=170);
});
test('scale fraction handles log and linear scales and rejects non-positive log values',()=>{
close(scaleFraction(1000,10,100000,'log'),.5);assert.equal(scaleFraction(0,10,100,'log'),null);assert.equal(scaleFraction(null,0,1),null);close(scaleFraction(25,0,100,'linear'),.25);
});
test('ramp returns hex colours, endpoints match palette, missing is grey',()=>{
assert.equal(rampColor(0),RAMP[0]);assert.equal(rampColor(1),RAMP[RAMP.length-1]);assert.match(rampColor(.37),/^#[0-9a-f]{6}$/);assert.equal(rampColor(null),'#2b3f44');
});