|
// imports |
|
var atan = Math.atan, |
|
abs = Math.abs, |
|
ε = 1e-6, |
|
π = Math.PI, |
|
degrees = 180 / π, |
|
θ = atan(0.5) * degrees; |
|
|
|
// manually rotate/guessed from JvW article |
|
var rotate = [-85.8, -47.2, -84.0]; |
|
|
|
// rotate = [-83.653, -45, -81.5] // (solved by d3-geo-polygon 1.0.1) |
|
|
|
var d3Geo = d3, |
|
polyhedral = d3.geoPolyhedral; |
|
|
|
var width = 900, |
|
height = 400, |
|
margin = 5; |
|
|
|
// construction inspired by |
|
// https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates |
|
var vertices = [ |
|
[0, 90], |
|
[0, -90] |
|
].concat(d3.range(10).map(function(i) { |
|
// var φ = i * 36; |
|
var φ = ((i * 36) + 180) % 360 - 180; |
|
return [φ, i & 1 ? θ : -θ]; |
|
})); |
|
|
|
var polyhedron = [ |
|
[ 0, 3, 11], |
|
[ 0, 5, 3], |
|
[ 0, 7, 5], |
|
[ 0, 9, 7], |
|
[ 0, 11, 9], // North |
|
[ 2, 11, 3], |
|
[ 3, 4, 2], |
|
[ 4, 3, 5], |
|
[ 5, 6, 4], |
|
[ 6, 5, 7], |
|
[ 7, 8, 6], |
|
[ 8, 7, 9], |
|
[ 9, 10, 8], |
|
[10, 9, 11], |
|
[11, 2, 10], // Equator |
|
[ 1, 2, 4], |
|
[ 1, 4, 6], |
|
[ 1, 6, 8], |
|
[ 1, 8, 10], |
|
[ 1, 10, 2] // South |
|
].map(function(face) { |
|
return face.map(function(i) { |
|
return vertices[i]; |
|
}); |
|
}); |
|
|
|
// add centroid |
|
polyhedron.forEach(function(face) { |
|
face.centroid = d3Geo.geoCentroid({type: "MultiPoint", coordinates: face}); |
|
}); |
|
|
|
|
|
var jvw_icosahedron = function(faceProjection) { |
|
faceProjection = faceProjection || function(face) { |
|
var c = d3Geo.geoCentroid({type: "MultiPoint", coordinates: face}); |
|
if (abs(abs(c[1]) - 90) < ε) c[0] = 0; |
|
return d3Geo.geoGnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]); |
|
}; |
|
|
|
var faces = polyhedron.map(function(face) { |
|
var polygon = face.slice(); |
|
polygon.push(polygon[0]); |
|
return { |
|
face: face, |
|
contains: function(lambda, phi) { |
|
return d3Geo.geoContains({ type: "Polygon", coordinates: [ polygon ] }, |
|
[lambda * degrees, phi * degrees]); |
|
}, |
|
project: faceProjection(face) |
|
}; |
|
}); |
|
|
|
// Connect each face to a parent face. |
|
[ |
|
// N |
|
-1, // 0 |
|
0, // 1 |
|
9, // 2 |
|
2, // 3 |
|
0, // 4 |
|
|
|
// Eq |
|
0, // 5 |
|
5, // 6 |
|
6, // 7 |
|
7, // 8 |
|
8, // 9 |
|
|
|
9, // 10 |
|
10, // 11 |
|
11, // 12 |
|
4, // 13 |
|
5, // 14 |
|
|
|
// S |
|
6, // 15 |
|
8, // 16 |
|
18, // 17 |
|
19, // 18 |
|
14, // 19 |
|
].forEach(function (d, i) { |
|
var node = faces[d]; |
|
node && (node.children || (node.children = [])).push(faces[i]); |
|
}); |
|
|
|
function face(λ, φ) { |
|
for (var i = 0; i < faces.length; i++) { |
|
if (faces[i].contains(λ, φ)) return faces[i]; |
|
} |
|
} |
|
|
|
// Polyhedral projection |
|
var proj = polyhedral( |
|
faces[0], // the root face |
|
face, // a function that return a face give coords |
|
0 // rotation of the root face in the projected (pixel) space |
|
) |
|
.rotate(rotate) |
|
.fitExtent( |
|
[ |
|
[margin, margin], |
|
[width - margin, height - margin] |
|
], |
|
{type:"Sphere"} |
|
); |
|
|
|
proj.faces = faces; |
|
return proj; |
|
} |
|
|
|
|
|
d3.geoPolyhedralJakvanWijIcosahedron = jvw_icosahedron; |
|
|
|
// map in an icosahedron. |
|
var projection = d3.geoPolyhedralJakvanWijIcosahedron(); |
|
//var projection = d3.geoPolyhedralWaterman(), r = [1e-13,0.01]; |
|
|
|
var path = d3.geoPath().projection(projection); |
|
var graticule = d3.geoGraticule(); |
|
|
|
|
|
var svg = d3.select("#map").append("svg") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
svg.append("path") |
|
.datum({type: "Sphere"}) |
|
.attr("class", "background") |
|
.attr("d", path); |
|
|
|
svg.append("path") |
|
.datum(graticule) |
|
.attr("class", "graticule") |
|
.attr("d", path); |
|
|
|
svg.append("path") |
|
.datum({type: "Sphere"}) |
|
.attr("class", "outline") |
|
.attr("d", path); |
|
|
|
// src1 does not work with rotation [0,0], but is ok with [1e-13,0] |
|
// src2 does not work with rotation [0,0], but is ok with [1e-13,0.01] |
|
// src3 is happy with rotation [0,0] |
|
var src = "https://unpkg.com/world-atlas/world/50m.json"; |
|
|
|
projection.rotate(rotate) |
|
|
|
d3.json(src, function(error, world) { |
|
var land = topojson.feature(world, world.objects.land); |
|
|
|
svg |
|
.insert("path", ".graticule") |
|
.datum(land) |
|
.attr("class", "land") |
|
.attr("d", path); |
|
|
|
}); |
|
|
|
|
|
var render = function() { |
|
svg.selectAll('path').attr('d', path); |
|
}; |
|
|
|
|
|
//console.log(projection.clipAngle(), projection.clipPolygon()) |
|
|
|
render() |
|
|
|
|
|
d3.select(self.frameElement).style("height", height + "px"); |