A stand-alone version of https://observablehq.com/@fil/a-conformal-airocean
Built with blockbuilder.org
A stand-alone version of https://observablehq.com/@fil/a-conformal-airocean
Built with blockbuilder.org
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v6.min.js"></script> | |
<script src="https://unpkg.com/d3-geo-polygon@1.12.1"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
</style> | |
</head> | |
<body> | |
<script> | |
function geoConformalAirocean() { | |
const {acos} = Math; | |
const degrees = 180 / Math.PI; | |
const sqrt3_4 = Math.sqrt(3) / 4; | |
const rotate = [115, acos(1 / 3) * .5 * degrees - 90, 180] | |
const rawLee = d3.geoTetrahedralLee() | |
.rotate(rotate) | |
.fitExtent([[-4, -1.5 / sqrt3_4], [4, 1.5 / sqrt3_4]], { | |
type: "Sphere" | |
}); | |
const forward = (l, p) => { | |
let [x, y] = rawLee([l * degrees, p * degrees]); | |
y = -y; | |
if (x < -2.1 || y < -2.73) { | |
x = -x - 4; | |
y = -y; | |
} | |
if (x < -4) { | |
x = -x - 8; | |
y = 6.93 - y; // 6.93 is a visual approximation—haven't figured out the exact value yet (please help!) | |
} | |
return [x, y]; | |
}; | |
forward.invert = (x, y) => { | |
y = -y; | |
const a = | |
rawLee.invert([x, y]) || | |
rawLee.invert([-x - 4, -y]) || | |
rawLee.invert([-x - 8, 6.93 - y]) || | |
rawLee.invert([-(-x - 8) - 4, 6.93 + y]); | |
if (a) return [a[0] * radians, a[1] * radians]; | |
}; | |
const clipPolygon = { | |
type: "Polygon", coordinates: [[ | |
[-20, 0], | |
[-27, 14], | |
[-27, 18], | |
[-20, 37], | |
[-30, 37], | |
[-35, -40], | |
[-30, -70], | |
[40, -50], | |
[70, -40], | |
[120, -60], | |
[150, -60], | |
[180, -60], | |
[-150, -50], | |
[-110, -40], | |
[-110, -30], | |
[-170, 10], | |
[160, 40], | |
[150, 40], | |
[150, 30], | |
[-170, 0], | |
[-150, -10], | |
[-140, -30], | |
[-170, -50], | |
[120, -50], | |
[90, -30], | |
[60, -25], | |
[30, -38], | |
[10, -38], | |
[-20, 0] | |
].map(d => [d[0] - .01, d[1] - .01])] | |
}; | |
return d3.geoProjection(forward) | |
.preclip(d3.geoClipPolygon(clipPolygon)) | |
.angle(57 - 180) | |
.fitExtent([[0,0], [960, 500]], {type: "Sphere"}); | |
} | |
const projection = geoConformalAirocean(); | |
const path = d3.geoPath(projection); | |
const svg = d3.select("body").append("svg") | |
.attr("width", 960) | |
.attr("height", 500); | |
const land = svg.append("path"); | |
d3.json("https://unpkg.com/visionscarto-world-atlas@0.0.6/world/110m_land.geojson") | |
.then(geo => land.datum(geo).attr("d", path)); | |
</script> | |
</body> | |