Skip to content

Instantly share code, notes, and snippets.

@Fil
Last active October 4, 2017 09:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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
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