source.fact.ngo a coherence.ngo project

assets/emergence-projection.js

raw ↗ · AGPL-3.0

// Spherical Equal Earth: Šavrič, Patterson & Jenny (2018). // Shared by the geometry generator and the build-time attention-dot renderer. // https://shadedrelief.com/ee_proj/ — areas are preserved, not shapes or distances. const A1 = 1.340264, A2 = -0.081106, A3 = 0.000893, A4 = 0.003796; const rad = Math.PI / 180, root3 = Math.sqrt(3); const extentX = 2 * root3 * Math.PI / (3 * A1); export const equalEarthFrame = { name: 'Equal Earth', W: 720, H: 360, padding: 8 }; export const equalEarthScale = (equalEarthFrame.W - 2 * equalEarthFrame.padding) / (2 * extentX); export function projectEqualEarth(lon, lat, frame = equalEarthFrame) { const t = Math.asin(root3 / 2 * Math.sin(Math.max(-90, Math.min(90, lat)) * rad)); const t2 = t * t, t6 = t2 * t2 * t2; const x = 2 * root3 * lon * rad * Math.cos(t) / (3 * (A1 + 3 * A2 * t2 + t6 * (7 * A3 + 9 * A4 * t2))); const y = t * (A1 + A2 * t2 + t6 * (A3 + A4 * t2)); const scale = (frame.W - 2 * frame.padding) / (2 * extentX); return [frame.W / 2 + x * scale, frame.H / 2 - y * scale]; } export function invertEqualEarth(x, y, frame = equalEarthFrame) { const scale = (frame.W - 2 * frame.padding) / (2 * extentX); const target = (frame.H / 2 - y) / scale; let t = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, target / A1)); for (let i = 0; i < 8; i++) { const t2 = t * t, t6 = t2 * t2 * t2; t -= (t * (A1 + A2 * t2 + t6 * (A3 + A4 * t2)) - target) / (A1 + 3 * A2 * t2 + t6 * (7 * A3 + 9 * A4 * t2)); t = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, t)); } const t2 = t * t, t6 = t2 * t2 * t2; const lon = (x - frame.W / 2) / scale * 3 * (A1 + 3 * A2 * t2 + t6 * (7 * A3 + 9 * A4 * t2)) / (2 * root3 * Math.cos(t)); return [lon / rad, Math.asin(Math.max(-1, Math.min(1, 2 * Math.sin(t) / root3))) / rad]; } export const wrapLongitude = lon => ((lon + 180) % 360 + 360) % 360 - 180; // Cylindrical equal-area, with true scale at the focus latitude. // https://proj.org/en/stable/operations/projections/cea.html // Intended for close views; the world view stays Equal Earth. export function localAreaFrame(lon, lat) { const focusLat = Math.max(-75, Math.min(75, lat)); return { lon: wrapLongitude(lon), lat: focusLat, cos: Math.cos(focusLat * rad), sin: Math.sin(focusLat * rad) }; } export function projectLocalArea(lon, lat, focus) { return [equalEarthFrame.W / 2 + wrapLongitude(lon - focus.lon) * rad * equalEarthScale * focus.cos, equalEarthFrame.H / 2 + (focus.sin - Math.sin(lat * rad)) * equalEarthScale / focus.cos]; } export function invertLocalArea(x, y, focus) { return [wrapLongitude(focus.lon + (x - equalEarthFrame.W / 2) / (equalEarthScale * focus.cos * rad)), Math.asin(Math.max(-1, Math.min(1, focus.sin - (y - equalEarthFrame.H / 2) * focus.cos / equalEarthScale))) / rad]; }