Skip to content

Instantly share code, notes, and snippets.

@sarah37
Last active August 16, 2017 11:10
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 sarah37/d8a1722f7d9f2a40eb07a3e99bf3c0be to your computer and use it in GitHub Desktop.
Save sarah37/d8a1722f7d9f2a40eb07a3e99bf3c0be to your computer and use it in GitHub Desktop.
Globe with drag and zoom functionality

All of the dragging functions (in the mathsfunctions.js file) were taken from this block. I simplified the code somewhat and only kept what is essential to the dragging functionality, then added zoom. It always zooms to the center, never to the sides of the globe. Zoom is bounded between 0.75 and 50.

// width and height
var w = 960;
var h = 500;
// scale globe to size of window
var scl = Math.min(w, h)/2.5;
// map projection
var projection = d3.geoOrthographic()
.scale(scl)
.translate([ w/2, h/2 ]);
// path generator
var path = d3.geoPath()
.projection(projection);
// append svg
var svg = d3.select("#svgDiv")
.append("svg")
.attr("width", w)
.attr("height", h);
// append g element for map
var map = svg.append("g");
// enable drag
var drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged);
var gpos0, o0, gpos1, o1;
svg.call(drag);
// enable zoom
var zoom = d3.zoom()
.scaleExtent([0.75, 50]) //bound zoom
.on("zoom", zoomed);
svg.call(zoom);
// load topojson
d3.json("https://gist.githubusercontent.com/sarah37/dcca42b936545d9ee9f0bc8052e03dbd/raw/550cfee8177df10e515d82f7eb80bce4f72c52de/world-110m.json", function(json) {
map.append("path")
.datum({type: "Sphere"})
.attr("class", "ocean")
.attr("d", path);
map.append("path")
.datum(topojson.merge(json, json.objects.countries.geometries))
.attr("class", "land")
.attr("d", path);
map.append("path")
.datum(topojson.mesh(json, json.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
});
// functions for dragging
function dragstarted() {
gpos0 = projection.invert(d3.mouse(this));
o0 = projection.rotate();
}
function dragged() {
gpos1 = projection.invert(d3.mouse(this));
o0 = projection.rotate();
o1 = eulerAngles(gpos0, gpos1, o0);
projection.rotate(o1);
map.selectAll("path").attr("d", path);
}
// functions for zooming
function zoomed() {
projection.scale(d3.event.transform.translate(projection).k * scl)
map.selectAll("path").attr("d", path);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Globe</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="mathsfunctions.js"></script>
</head>
<body>
<div id="svgDiv"></div>
<script type="text/javascript" src="globe.js"></script>
</body>
</html>
// all functions are from http://bl.ocks.org/ivyywang/7c94cb5a3accd9913263
var to_radians = Math.PI / 180;
var to_degrees = 180 / Math.PI;
// Helper function: cross product of two vectors v0&v1
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]];
}
//Helper function: dot product of two vectors v0&v1
function dot(v0, v1) {
for (var i = 0, sum = 0; v0.length > i; ++i) sum += v0[i] * v1[i];
return sum;
}
// Helper function:
// This function converts a [lon, lat] coordinates into a [x,y,z] coordinate
// the [x, y, z] is Cartesian, with origin at lon/lat (0,0) center of the earth
function lonlat2xyz( coord ){
var lon = coord[0] * to_radians;
var lat = coord[1] * to_radians;
var x = Math.cos(lat) * Math.cos(lon);
var y = Math.cos(lat) * Math.sin(lon);
var z = Math.sin(lat);
return [x, y, z];
}
// Helper function:
// This function computes a quaternion representation for the rotation between to vectors
// https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions#Euler_angles_.E2.86.94_Quaternion
function quaternion(v0, v1) {
if (v0 && v1) {
var w = cross(v0, v1), // vector pendicular to v0 & v1
w_len = Math.sqrt(dot(w, w)); // length of w
if (w_len == 0)
return;
var theta = .5 * Math.acos(Math.max(-1, Math.min(1, dot(v0, v1)))),
qi = w[2] * Math.sin(theta) / w_len;
qj = - w[1] * Math.sin(theta) / w_len;
qk = w[0]* Math.sin(theta) / w_len;
qr = Math.cos(theta);
return theta && [qr, qi, qj, qk];
}
}
// Helper function:
// This functions converts euler angles to quaternion
// https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions#Euler_angles_.E2.86.94_Quaternion
function euler2quat(e) {
if(!e) return;
var roll = .5 * e[0] * to_radians,
pitch = .5 * e[1] * to_radians,
yaw = .5 * e[2] * to_radians,
sr = Math.sin(roll),
cr = Math.cos(roll),
sp = Math.sin(pitch),
cp = Math.cos(pitch),
sy = Math.sin(yaw),
cy = Math.cos(yaw),
qi = sr*cp*cy - cr*sp*sy,
qj = cr*sp*cy + sr*cp*sy,
qk = cr*cp*sy - sr*sp*cy,
qr = cr*cp*cy + sr*sp*sy;
return [qr, qi, qj, qk];
}
// This functions computes a quaternion multiply
// Geometrically, it means combining two quant rotations
// http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/arithmetic/index.htm
function quatMultiply(q1, q2) {
if(!q1 || !q2) return;
var a = q1[0],
b = q1[1],
c = q1[2],
d = q1[3],
e = q2[0],
f = q2[1],
g = q2[2],
h = q2[3];
return [
a*e - b*f - c*g - d*h,
b*e + a*f + c*h - d*g,
a*g - b*h + c*e + d*f,
a*h + b*g - c*f + d*e];
}
// This function computes quaternion to euler angles
// https://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions#Euler_angles_.E2.86.94_Quaternion
function quat2euler(t){
if(!t) return;
return [ Math.atan2(2 * (t[0] * t[1] + t[2] * t[3]), 1 - 2 * (t[1] * t[1] + t[2] * t[2])) * to_degrees,
Math.asin(Math.max(-1, Math.min(1, 2 * (t[0] * t[2] - t[3] * t[1])))) * to_degrees,
Math.atan2(2 * (t[0] * t[3] + t[1] * t[2]), 1 - 2 * (t[2] * t[2] + t[3] * t[3])) * to_degrees
]
}
/* This function computes the euler angles when given two vectors, and a rotation
This is really the only math function called with d3 code.
v0 - starting pos in lon/lat, commonly obtained by projection.invert
v1 - ending pos in lon/lat, commonly obtained by projection.invert
o0 - the projection rotation in euler angles at starting pos (v0), commonly obtained by projection.rotate
*/
function eulerAngles(v0, v1, o0) {
/*
The math behind this:
- first calculate the quaternion rotation between the two vectors, v0 & v1
- then multiply this rotation onto the original rotation at v0
- finally convert the resulted quat angle back to euler angles for d3 to rotate
*/
var t = quatMultiply( euler2quat(o0), quaternion(lonlat2xyz(v0), lonlat2xyz(v1) ) );
return quat2euler(t);
}
body, html {
margin: 0;
overflow: hidden;
}
.ocean {
fill: #bfd7e4;
}
.land {
fill: #eaeaea;
}
.boundary {
fill: none;
stroke: #9fa8ad;
stroke-linejoin: round;
stroke-linecap: round;
vector-effect: non-scaling-stroke;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment