SVG Downloads:
https://narukawa-lab.jp/archives/dymaxion-map/
D3-Projection Instruction
https://observablehq.com/@fil/d3-projections
https://observablehq.com/@d3/fullers-airocean
D3JS Azimuthal
https://d3js.org/d3-geo/azimuthal
D3geo Projections
https://d3js.org/d3-geo/projection
D3 Projections Demo Page
https://observablehq.com/@fil/d3-projections
Phillippe Rivières Airocean implementation:
https://observablehq.com/@fil/airocean-projection
https://github.com/d3/d3-geo-polygon/blob/main/src/airocean.js
Just look at it
/*
* Buckminster Fuller’s AirOcean arrangement of the icosahedron
*
* Implemented for D3.js by Jason Davies (2013),
* Enrico Spinielli (2017) and Philippe Rivière (2017, 2018)
*
*/
import { atan, degrees } from "./math.js";
import polyhedral from "./polyhedral/index.js";
import { default as grayFullerRaw } from "./grayfuller.js";
import {
geoCentroid as centroid,
geoContains as contains,
geoGnomonic as gnomonic,
geoProjection as projection
} from "d3-geo";
import { range } from "d3-array";
function airoceanRaw(faceProjection) {
const theta = atan(0.5) * degrees;
// construction inspired by
// https://en.wikipedia.org/wiki/Regular_icosahedron#Spherical_coordinates
const vertices = [[0, 90], [0, -90]].concat(
range(10).map((i) => [(i * 36 + 180) % 360 - 180, i & 1 ? theta : -theta])
);
// icosahedron
const 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((face) => face.map((i) => vertices[i]));
// add centroid
polyhedron.forEach((face) => (face.centroid = centroid({ type: "MultiPoint", coordinates: face })));
// split the relevant faces:
// * face[15] in the centroid: this will become face[15], face[20] and face[21]
// * face[14] in the middle of the side: this will become face[14] and face[22]
(function() {
let face, tmp, mid, centroid;
// Split face[15] in 3 faces at centroid.
face = polyhedron[15];
centroid = face.centroid;
tmp = face.slice();
face[0] = centroid; // (new) face[15]
face = [tmp[0], centroid, tmp[2]];
face.centroid = centroid;
polyhedron.push(face); // face[20]
face = [tmp[0], tmp[1], centroid];
face.centroid = centroid;
polyhedron.push(face); // face[21]
// Split face 14 at the edge.
face = polyhedron[14];
centroid = face.centroid;
tmp = face.slice();
// compute planar midpoint
const proj = gnomonic()
.scale(1)
.translate([0, 0])
.rotate([-centroid[0], -centroid[1]]);
const a = proj(face[1]),
b = proj(face[2]);
mid = proj.invert([(a[0] + b[0]) / 2, (a[1] + b[1]) / 2]);
face[1] = mid; // (new) face[14]
// build the new half face
face = [tmp[0], tmp[1], mid];
face.centroid = centroid; // use original face[14] centroid
polyhedron.push(face); // face[22]
// cut face 19 to connect to 22
face = polyhedron[19];
centroid = face.centroid;
tmp = face.slice();
face[1] = mid;
// build the new half face
face = [mid, tmp[0], tmp[1]];
face.centroid = centroid;
polyhedron.push(face); // face[23]
})();
const airocean = function(faceProjection) {
faceProjection =
faceProjection ||
// for half-triangles this is definitely not centroid({type: "MultiPoint", coordinates: face});
((face) => gnomonic()
.scale(1)
.translate([0, 0])
.rotate([-face.centroid[0], -face.centroid[1]]));
const faces = polyhedron.map((face, i) => {
const polygon = face.slice();
polygon.push(polygon[0]);
return {
face: face,
site: face.centroid,
id: i,
contains: function(lambda, phi) {
return contains({ type: "Polygon", coordinates: [polygon] }, [
lambda * degrees,
phi * degrees
]);
},
project: faceProjection(face)
};
});
// Connect each face to a parent face.
const parents = [
// N
-1, // 0
0, // 1
1, // 2
11, // 3
13, // 4
// Eq
6, // 5
7, // 6
1, // 7
7, // 8
8, // 9
9, // 10
10, // 11
11, // 12
12, // 13
13, // 14
// S
6, // 15
8, // 16
10, // 17
17, // 18
21, // 19
16, // 20
15, // 21
19, // 22
19 // 23
];
parents.forEach((d, i) => {
const node = faces[d];
node && (node.children || (node.children = [])).push(faces[i]);
});
function face(lambda, phi) {
for (let i = 0; i < faces.length; ++i) {
if (faces[i].contains(lambda, phi)) return faces[i];
}
}
// Polyhedral projection
const proj = polyhedral(
faces[0], // the root face
face // a function that returns a face given coords
);
proj.faces = faces;
return proj;
};
return airocean(faceProjection);
}
export default function () {
const p = airoceanRaw((face) => {
const c = face.centroid;
face.direction =
Math.abs(c[1] - 52.62) < 1 || Math.abs(c[1] + 10.81) < 1 ? 0 : 60;
return projection(grayFullerRaw())
.scale(1)
.translate([0, 0])
.rotate([-c[0], -c[1], face.direction || 0]);
});
return p
.rotate([-83.65929, 25.44458, -87.45184])
.angle(-60)
.scale(45.4631)
.center([126, 0]);
}
Leave a Reply