Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active October 4, 2017 09:14
Show Gist options
  • Save Fil/90c1d39fa1b92281fd119c02188da858 to your computer and use it in GitHub Desktop.
Save Fil/90c1d39fa1b92281fd119c02188da858 to your computer and use it in GitHub Desktop.
Furuti cubic projection #1 (Reichard) with d3-geo/clip [UNLISTED]
license: mit

Carlos Furuti's cubic globe #1 - http://www.progonos.com/furuti/MapProj/Normal/ProjPoly/projPoly2.html

Based on "Earth in a Cube" by Enrico Spinielli and on my research for d3-geo-projection/pull/86 and d3-geo/issues/46.

Re-incorporating Jason Davies’ clipPolygon() code into d3v4.

Code base at Fil/d3-geo:clip-polygon.

See also https://bl.ocks.org/Fil/694ba0d0bc1fc4c24eb257dc210eb01a

forked from Fil's block: Furuti 3 - projection.clipPolygon()

forked from Fil's block: Furuti cubic projection #1

Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
.poly {
fill: #999;
fill-opacity: 0.4;
stroke: black;
stroke-width: .5px;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: 0.3px;
stroke-opacity: 0.5;
}
.sphere {
fill: none;
stroke: black;
stroke-width: 2px;
}
.face {
fill: lightblue;
}
</style>
<title>Furuti 1</title>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script><!-- https://github.com/d3/d3/releases/tag/v4.11.0 minimum, for projection.preclip() -->
<script src="https://recifs.neocities.org/d3-geo-projection-clip-polyhedral.js"> </script><!-- this unpublished version uses d3-geo-polygon if available -->
<script src="https://unpkg.com/d3-geo-polygon"></script>
<script src="versor.js"></script>
<script>
var width = 960, height = 500;
var scaleProj = Math.min(width/2, height)/Math.PI;
// imports
var atan = Math.atan, sqrt1_2 = Math.sqrt(1/2), pi = Math.PI, degrees = 180 / Math.PI;
var d3Geo = d3, polyhedral = d3.geoPolyhedral;
var phi1 = atan(sqrt1_2) * degrees;
var cube = [
[0, phi1], [90, phi1], [180, phi1], [-90, phi1],
[0, -phi1], [90, -phi1], [180, -phi1], [-90, -phi1]
];
var cube$1 = [
[0, 3, 2, 1], // N
[0, 1, 5, 4],
[1, 2, 6, 5],
[2, 3, 7, 6],
[3, 0, 4, 7],
[4, 5, 6, 7] // S
].map(function(face) {
return face.map(function(i) {
return cube[i];
});
});
var f1 = function(faceProjection) {
// it is possible to pass a specific projection on each face
// by default is is a gnomonic projection centered on the face's centroid
// scale 1 by convention
faceProjection = faceProjection || function(face) {
var c = d3Geo.geoCentroid({type: "MultiPoint", coordinates: face});
return d3Geo.geoGnomonic().scale(1).translate([0, 0]).rotate([-c[0], -c[1]]);
};
// the faces from the cube each yield
// - face: its four vertices
// - contains: does this face contain a point?
// - project: local projection on this face
var faces = cube$1.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)
};
});
// Build a tree of the faces, starting with face 0 (North Pole)
// which has no parent (-1); the next four faces around the equator
// are attached to the north face (0); the face containing the South Pole
// is attached to South America (4)
[-1, 4, 5, 2, 0, 1].forEach(function(d, i) {
var node = faces[d];
node && (node.children || (node.children = [])).push(faces[i]);
});
// Polyhedral projection
var proj = polyhedral(faces[0], function(lambda, phi) {
for (var i = 0; i < faces.length; i++) {
if (faces[i].contains(lambda, phi)) return faces[i];
}
},
pi/2 // rotation of the root face in the projected (pixel) space
)
//.clipAngle(1) // no antimeridian clipping on the Sphere
.fitExtent([[20,20],[width-20, height-20]], {type:"Sphere"})
proj.faces = faces;
return proj;
};
d3.geoPolyhedralFuruti1 = f1;
var projection = d3.geoPolyhedralFuruti1()
.rotate([30,0]);
// var preclip = projection.preclip();
//projection.preclip(s => d3.geoClipRectangle(-0.6,-0.2, 0.5,0.5)(preclip(s)))
//projection.postclip(d3.geoClipRectangle(150,100, 250, 200))
//projection.postclip(d3.geoClipRectangle(0,100, 250, 200))
var path = d3.geoPath().projection(projection);
var graticule = d3.geoGraticule();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
svg.call(d3.drag().on("start", dragstarted).on("drag", dragged));
var movable = svg.append("g");
var countries = movable.append("g")
.attr("class", "poly");
d3.json('countries.geo.json', function(err, world) {
countries
.selectAll('path')
.data(world.features)
.enter()
.append('path')
.attr("d", path);
});
movable.selectAll(".graticule")
.data(graticule.lines)
.enter().append("path")
.attr("class", "graticule")
.attr("d", path);
var rotate = projection.rotate();
projection.rotate([0,0]);
svg.append("path")
.datum({type: "MultiPoint", coordinates: projection.faces.map(function(face) {
return d3.geoCentroid({type: "MultiPoint", coordinates: face.face});
})})
.attr("class", "face")
.attr("d", path);
projection.rotate(rotate);
movable.append('path')
.datum({type: "Point", coordinates: [0,90]})
.attr('d', path);
movable.append('path')
.datum({type: "Point", coordinates: [0,-90]})
.attr('d', path);
svg.append('path')
.datum({type: "Sphere"})
.attr("class", "sphere")
.attr('d', path);
var render = function() {
movable.selectAll('path').attr('d', path);
},
v0, // Mouse position in Cartesian coordinates at start of drag gesture.
r0, // Projection rotation as Euler angles at start.
q0; // Projection rotation as versor at start.
function dragstarted() {
v0 = versor.cartesian(projection.invert(d3.mouse(this)));
r0 = projection.rotate();
q0 = versor(r0);
}
function dragged() {
var inv = projection.rotate(r0).invert(d3.mouse(this));
if (!inv || isNaN(inv[0])) return;
var v1 = versor.cartesian(inv),
q1 = versor.multiply(q0, versor.delta(v0, v1)),
r1 = versor.rotation(q1);
projection.rotate(r1);
render();
}
// projection.rotate([29, -11, 12]) && render()
</script>
</body>
</html>
// Version 0.0.0. Copyright 2017 Mike Bostock.
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.versor = factory());
}(this, (function() {'use strict';
var acos = Math.acos,
asin = Math.asin,
atan2 = Math.atan2,
cos = Math.cos,
max = Math.max,
min = Math.min,
PI = Math.PI,
sin = Math.sin,
sqrt = Math.sqrt,
radians = PI / 180,
degrees = 180 / PI;
// Returns the unit quaternion for the given Euler rotation angles [λ, φ, γ].
function versor(e) {
var l = e[0] / 2 * radians, sl = sin(l), cl = cos(l), // λ / 2
p = e[1] / 2 * radians, sp = sin(p), cp = cos(p), // φ / 2
g = e[2] / 2 * radians, sg = sin(g), cg = cos(g); // γ / 2
return [
cl * cp * cg + sl * sp * sg,
sl * cp * cg - cl * sp * sg,
cl * sp * cg + sl * cp * sg,
cl * cp * sg - sl * sp * cg
];
}
// Returns Cartesian coordinates [x, y, z] given spherical coordinates [λ, φ].
versor.cartesian = function(e) {
var l = e[0] * radians, p = e[1] * radians, cp = cos(p);
return [cp * cos(l), cp * sin(l), sin(p)];
};
// Returns the Euler rotation angles [λ, φ, γ] for the given quaternion.
versor.rotation = function(q) {
return [
atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
asin(max(-1, min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
];
};
// Returns the quaternion to rotate between two cartesian points on the sphere.
versor.delta = function(v0, v1) {
var w = cross(v0, v1), l = sqrt(dot(w, w));
if (!l) return [1, 0, 0, 0];
var t = acos(max(-1, min(1, dot(v0, v1)))) / 2, s = sin(t); // t = θ / 2
return [cos(t), w[2] / l * s, -w[1] / l * s, w[0] / l * s];
};
// Returns the quaternion that represents q0 * q1.
versor.multiply = function(q0, q1) {
return [
q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] - q0[3] * q1[3],
q0[1] * q1[0] + q0[0] * q1[1] + q0[2] * q1[3] - q0[3] * q1[2],
q0[0] * q1[2] - q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1],
q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0]
];
};
function cross(v0, v1) {
return [
v0[1] * v1[2] - v0[2] * v1[1],
v0[2] * v1[0] - v0[0] * v1[2],
v0[0] * v1[1] - v0[1] * v1[0]
];
}
function dot(v0, v1) {
return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
}
return versor;
})));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment