Fixing the tetrahedric gnomonic requires specially modified versions of d3-geo and d3-geo-projection to handle clipPolygon().
forked from Fil's block: Lee’s Tetrahedric Conformal Projection
xxxxxxxxxx
<!-- 578 ~= 500 * 2 / sqrt(3) -->
<div style="text-align:center"><canvas width="578" height="500"></canvas></div>
<script src="https://d3js.org/d3.v4.js"></script>
<!-- <script src="https://d3js.org/d3-geo-projection.v2.js"></script> -->
<script src="d3-geo.js"></script>
<script src="d3-geo-projection.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script src="https://unpkg.com/complex.js"></script>
<script>
var canvas = d3.select("canvas"),
width = canvas.property("width"),
height = canvas.property("height"),
context = canvas.node().getContext("2d");
// retina display
var devicePixelRatio = window.devicePixelRatio || 1;
canvas.style('width', canvas.attr('width')+'px');
canvas.style('height', canvas.attr('height')+'px');
canvas.attr('width', canvas.attr('width') * devicePixelRatio);
canvas.attr('height', canvas.attr('height') * devicePixelRatio);
context.scale(devicePixelRatio,devicePixelRatio);
// import from math
var epsilon = 1e-6, epsilon2 = epsilon * epsilon, asin = Math.asin;
var pi = Math.PI, degrees = 180 / pi, asin1_3 = Math.asin(1 / 3);
// the Sphere should go *exactly* to the vertices of the triangles
// because they are singular points
function sphere() {
var c = - asin1_3 * degrees;
var eps = 1e-6;
return {
type: "Polygon",
coordinates: [
[ [ -120, 90-eps ], [ -60, c ], [ 0, 90-eps ], [ 60, c ], [ 120, 90-eps ], [ 180, c ], [ -120, 90-eps ] ]
]
};
}
var centers = [
[0, 90],
[-180, -asin1_3 * degrees],
[-60, -asin1_3 * degrees],
[60, -asin1_3 * degrees]
];
var tetrahedron = [[1, 2, 3], [0, 2, 1], [0, 3, 2], [0, 1, 3]].map(function(
face
) {
return face.map(function(i) {
return centers[i];
});
});
d3.geoTetrahedralLee = function(faceProjection) {
faceProjection =
faceProjection ||
function(face) {
var c = d3.geoCentroid({ type: "MultiPoint", coordinates: face }),
rotate = [ -c[0], -c[1], 30 ];
if (Math.abs(c[1]) == 90) {
rotate = [ 0, -c[1], -30 ];
}
return d3
.geoProjection(d3.geoGnomonicRaw)
.scale(1)
.translate([0, 0])
.rotate(rotate);
};
var faces = tetrahedron.map(function(face) {
return { face: face, project: faceProjection(face) };
});
[-1, 0, 0, 0].forEach(function(d, i) {
var node = faces[d];
node && (node.children || (node.children = [])).push(faces[i]);
});
return d3
.geoPolyhedral(
faces[0],
function(lambda, phi) {
lambda *= degrees;
phi *= degrees;
for (var i = 0; i < faces.length; i++) {
if (
d3.geoContains(
{
type: "Polygon",
coordinates: [[...tetrahedron[i], tetrahedron[i][0]]]
},
[lambda, phi]
)
) {
return faces[i];
}
}
},
pi / 6
)
.clipAngle(360) // this is only to avoid antimeridian clipping on the Sphere
.precision(0.05)
.rotate([-30, 0])
.rotate([30, 180]) // for North Pole aspect, needs clipPolygon
.fitExtent([[1, 1], [width-1, height-1]], {type: "Sphere"})
};
projection = d3.geoTetrahedralLee();
var init_scale = projection.scale(),
path = d3.geoPath().projection(projection).context(context);
d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(
error,
world
) {
if (error) throw error;
var land = topojson.merge(world, world.objects.countries.geometries);
render = function() {
var tiling = false;
context.fillStyle = "#fff";
context.fillRect(0, 0, width, height);
if (!tiling) {
context.beginPath();
path(d3.geoGraticule()());
context.strokeStyle = "#777";
context.lineWidth = 0.5;
context.stroke(), context.closePath();
// equator
context.beginPath();
path(d3.geoCircle().center([0,90]).radius(90)());
context.strokeStyle = "#000";
context.lineWidth = 1;
context.stroke(), context.closePath();
// inner triangle
context.beginPath();
{
let rotate = projection.rotate();
var inner = centers.map(projection.rotate([0,0]));
projection.rotate(rotate);
}
context.moveTo(inner[1][0], inner[1][1]);
context.lineTo(inner[2][0], inner[2][1]);
context.lineTo(inner[3][0], inner[3][1]);
context.lineTo(inner[1][0], inner[1][1]);
context.strokeStyle = "#777";
context.lineWidth = 0.5;
context.setLineDash([5,3]);
context.stroke(), context.closePath();
context.setLineDash([]);
}
context.beginPath();
path({type:"Sphere"});
context.strokeStyle = "#000";
context.lineWidth = 2;
context.stroke(), context.closePath();
context.beginPath();
var now = performance.now();
path(land);
console.log('time', Math.round(performance.now()-now)+'ms');
context.lineWidth = 1;
context.strokeStyle = "#000";
context.stroke();
context.fillStyle = "#000";
context.fill();
context.closePath();
console.log(projection([0,-90]))
if (tiling) {
context.beginPath();
context.rotate(pi);
context.translate(-1247,-500);
path(land);
context.translate(575,0);
path(land);
context.lineWidth = 1;
//context.strokeStyle = "red";
context.stroke();
//context.fillStyle = "pink";
context.fill();
context.closePath();
}
};
render();
});
</script>
https://d3js.org/d3.v4.js
https://d3js.org/d3-geo-projection.v2.js
https://d3js.org/topojson.v2.min.js
https://unpkg.com/complex.js