import { describe, it, expect } from 'vitest'; import { createMercatorProjection, mercatorY, MAX_MERCATOR_LAT } from './map-projection'; describe('mercatorY', () => { it('is zero at the equator', () => { // tan(pi/4) lands a float epsilon below 1, so the log is ~-1e-16, not 0. expect(mercatorY(0)).toBeCloseTo(0, 12); }); it('grows northward and is symmetric', () => { expect(mercatorY(45)).toBeGreaterThan(0); expect(mercatorY(-45)).toBeCloseTo(-mercatorY(45), 10); }); it('clamps beyond the Mercator limit instead of diverging', () => { expect(Number.isFinite(mercatorY(90))).toBe(true); expect(mercatorY(90)).toBe(mercatorY(MAX_MERCATOR_LAT)); }); }); describe('createMercatorProjection', () => { // Roughly the bounding box of Rio Grande do Sul. const points = [ { lat: -27.08, lng: -53.11 }, { lat: -33.75, lng: -49.69 }, ]; it('returns null when there is nothing to fit', () => { expect(createMercatorProjection([], 800, 600)).toBeNull(); }); it('returns null when the container has no area', () => { expect(createMercatorProjection(points, 0, 0)).toBeNull(); }); it('keeps every projected point inside the container', () => { const projection = createMercatorProjection(points, 800, 600, 24)!; points.forEach(point => { const { x, y } = projection.project(point); expect(x).toBeGreaterThanOrEqual(0); expect(x).toBeLessThanOrEqual(800); expect(y).toBeGreaterThanOrEqual(0); expect(y).toBeLessThanOrEqual(600); }); }); it('puts north above south on screen', () => { const projection = createMercatorProjection(points, 800, 600)!; const north = projection.project({ lat: -27.08, lng: -51 }); const south = projection.project({ lat: -33.75, lng: -51 }); expect(north.y).toBeLessThan(south.y); }); it('puts west left of east', () => { const projection = createMercatorProjection(points, 800, 600)!; const west = projection.project({ lat: -30, lng: -53.11 }); const east = projection.project({ lat: -30, lng: -49.69 }); expect(west.x).toBeLessThan(east.x); }); it('scales both axes uniformly so the geography is not stretched', () => { // A square-ish extent in a very wide container must not fill the width. const square = [ { lat: 0, lng: 0 }, { lat: 10, lng: 10 }, ]; const projection = createMercatorProjection(square, 1000, 200, 0)!; const a = projection.project(square[0]); const b = projection.project(square[1]); const spanX = Math.abs(b.x - a.x); const spanY = Math.abs(b.y - a.y); // Mercator stretches latitude slightly, but the two spans must stay within // the same order — a non-uniform fit would push spanX toward 1000. expect(spanX).toBeLessThan(400); expect(spanX / spanY).toBeGreaterThan(0.5); expect(spanX / spanY).toBeLessThan(2); }); it('centers a single point instead of dividing by zero', () => { const projection = createMercatorProjection([{ lat: -30, lng: -51 }], 800, 600)!; const { x, y } = projection.project({ lat: -30, lng: -51 }); expect(x).toBeCloseTo(400, 5); expect(y).toBeCloseTo(300, 5); }); it('handles a perfectly vertical line of points', () => { const projection = createMercatorProjection( [ { lat: -27, lng: -51 }, { lat: -33, lng: -51 }, ], 800, 600 )!; const a = projection.project({ lat: -27, lng: -51 }); const b = projection.project({ lat: -33, lng: -51 }); expect(Number.isFinite(a.x)).toBe(true); expect(Number.isFinite(a.y)).toBe(true); expect(a.x).toBeCloseTo(b.x, 5); expect(a.y).toBeLessThan(b.y); }); });