|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
|
|
body { |
|
background: #e6e6d5; |
|
} |
|
|
|
.stroke { |
|
fill: none; |
|
stroke: #000; |
|
stroke-width: 3px; |
|
} |
|
|
|
.fill { |
|
fill: #fff; |
|
} |
|
|
|
.graticule { |
|
fill: none; |
|
stroke: #777; |
|
stroke-width: .5px; |
|
stroke-opacity: .5; |
|
} |
|
|
|
.land { |
|
fill: #222; |
|
} |
|
|
|
.boundary { |
|
fill: none; |
|
stroke: #fff; |
|
stroke-width: .5px; |
|
} |
|
|
|
</style> |
|
<body> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://d3js.org/topojson.v2.min.js"></script> |
|
|
|
<!-- dat.gui --> |
|
<script src="https://unpkg.com/dat.gui/build/dat.gui.min.js"></script> |
|
<link rel="stylesheet" href="https://raw.githack.com/liabru/dat-gui-light-theme/master/dat-gui-light-theme.css"/> |
|
<script> |
|
function gui(opts, redraw, config) { |
|
var gui = new dat.GUI(config.options || {}); |
|
for (var i in opts) { |
|
add(gui, opts, i); |
|
} |
|
|
|
function add(src, o, t) { |
|
if (typeof o[t] == 'object') { |
|
var group = src.addFolder(t); |
|
for (var j in o[t]) { |
|
add(group, o[t], j); |
|
} |
|
} else { |
|
var control = (t.match(/color/i)) |
|
? src.addColor(o, t) |
|
: src.add(o, t); |
|
if (config.listen) control.listen(); |
|
if (redraw) control.onChange(redraw); |
|
|
|
} |
|
} |
|
} |
|
</script> |
|
<!-- /dat.gui --> |
|
|
|
|
|
|
|
<script> |
|
d3.json("https://unpkg.com/visionscarto-world-atlas/world/110m.json", function (error, world) { |
|
if (error) throw error; |
|
|
|
var width = 960, |
|
height = 480; |
|
|
|
var graticule = d3.geoGraticule(); |
|
|
|
var svg = d3.select("body").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var defs = svg.append("defs"); |
|
|
|
defs.append("path") |
|
.datum({ |
|
type: "Sphere" |
|
}) |
|
.attr("id", "sphere"); |
|
|
|
defs.append("clipPath") |
|
.attr("id", "clip") |
|
.append("use") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
svg.append("use") |
|
.attr("class", "stroke") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
svg.append("use") |
|
.attr("class", "fill") |
|
.attr("xlink:href", "#sphere"); |
|
|
|
svg.append("path") |
|
.datum(graticule) |
|
.attr("class", "graticule") |
|
.attr("clip-path", "url(#clip)"); |
|
|
|
|
|
svg.insert("path", ".graticule") |
|
.datum(topojson.feature(world, world.objects.land)) |
|
.attr("class", "land") |
|
.attr("clip-path", "url(#clip)"); |
|
|
|
svg.insert("path", ".graticule") |
|
.datum(topojson.mesh(world, world.objects.countries, function (a, b) { |
|
return a !== b; |
|
})) |
|
.attr("class", "boundary") |
|
.attr("clip-path", "url(#clip)"); |
|
|
|
|
|
|
|
|
|
var opts = { alpha: 0.5 }; |
|
gui(opts, redraw, {}); |
|
redraw(); |
|
|
|
|
|
function redraw() { |
|
|
|
var alpha = opts.alpha = Math.min(opts.alpha,1), |
|
beta = 1.0 - alpha; |
|
var equatorial = foucaultraw(Math.PI, 0)[0] - foucaultraw(-Math.PI, 0)[0]; |
|
var polar = foucaultraw(0, Math.PI / 2)[1] - foucaultraw(0, -Math.PI / 2)[1]; |
|
var ratio = Math.sqrt(2 * polar / equatorial); |
|
console.log('ratio', ratio, polar, equatorial) |
|
|
|
function foucaultraw(lambda, phi) { |
|
var cosphi = Math.cos(phi), |
|
sinphi = Math.sin(phi); |
|
return [ |
|
cosphi / (beta + alpha * cosphi) * lambda, |
|
(beta * phi + alpha * sinphi) |
|
]; |
|
} |
|
|
|
function foucault(lambda, phi) { |
|
var p = foucaultraw(lambda, phi); |
|
return [ p[0] * ratio, p[1] / ratio ]; |
|
} |
|
|
|
|
|
var projection = d3.geoProjection(foucault) |
|
.rotate([-10.5]) |
|
.fitExtent([[10, 10], [width - 30, height - 10]], { |
|
type: "Sphere" |
|
}); |
|
|
|
var path = d3.geoPath() |
|
.projection(projection); |
|
|
|
svg.selectAll('path').attr("d", path); |
|
|
|
} |
|
}); |
|
|
|
</script> |