Skip to content

Instantly share code, notes, and snippets.

@enjalot
Created July 16, 2014 06:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save enjalot/31168147b88a1748bc8b to your computer and use it in GitHub Desktop.
Save enjalot/31168147b88a1748bc8b to your computer and use it in GitHub Desktop.
d3.geo with d3.geo.zoom
// Copyright (c) 2013, Jason Davies, http://www.jasondavies.com
// See LICENSE.txt for details.
(function() {
var radians = Math.PI / 180,
degrees = 180 / Math.PI;
// TODO make incremental rotate optional
d3.geo.zoom = function() {
var projection,
zoomPoint,
event = d3.dispatch("zoomstart", "zoom", "zoomend"),
zoom = d3.behavior.zoom()
.on("zoomstart", function() {
var mouse0 = d3.mouse(this),
rotate = quaternionFromEuler(projection.rotate()),
point = position(projection, mouse0);
if (point) zoomPoint = point;
zoomOn.call(zoom, "zoom", function() {
projection.scale(d3.event.scale);
var mouse1 = d3.mouse(this),
between = rotateBetween(zoomPoint, position(projection, mouse1));
projection.rotate(eulerFromQuaternion(rotate = between
? multiply(rotate, between)
: multiply(bank(projection, mouse0, mouse1), rotate)));
mouse0 = mouse1;
event.zoom.apply(this, arguments);
});
event.zoomstart.apply(this, arguments);
})
.on("zoomend", function() {
zoomOn.call(zoom, "zoom", null);
event.zoomend.apply(this, arguments);
}),
zoomOn = zoom.on;
zoom.projection = function(_) {
return arguments.length ? zoom.scale((projection = _).scale()) : projection;
};
return d3.rebind(zoom, event, "on");
};
function bank(projection, p0, p1) {
var t = projection.translate(),
angle = Math.atan2(p0[1] - t[1], p0[0] - t[0]) - Math.atan2(p1[1] - t[1], p1[0] - t[0]);
return [Math.cos(angle / 2), 0, 0, Math.sin(angle / 2)];
}
function position(projection, point) {
var t = projection.translate(),
spherical = projection.invert(point);
return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical);
}
function quaternionFromEuler(euler) {
var λ = .5 * euler[0] * radians,
φ = .5 * euler[1] * radians,
γ = .5 * euler[2] * radians,
sinλ = Math.sin(λ), cosλ = Math.cos(λ),
sinφ = Math.sin(φ), cosφ = Math.cos(φ),
sinγ = Math.sin(γ), cosγ = Math.cos(γ);
return [
cosλ * cosφ * cosγ + sinλ * sinφ * sinγ,
sinλ * cosφ * cosγ - cosλ * sinφ * sinγ,
cosλ * sinφ * cosγ + sinλ * cosφ * sinγ,
cosλ * cosφ * sinγ - sinλ * sinφ * cosγ
];
}
function multiply(a, b) {
var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
return [
a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3,
a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2,
a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1,
a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0
];
}
function rotateBetween(a, b) {
if (!a || !b) return;
var axis = cross(a, b),
norm = Math.sqrt(dot(axis, axis)),
halfγ = .5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))),
k = Math.sin(halfγ) / norm;
return norm && [Math.cos(halfγ), axis[2] * k, -axis[1] * k, axis[0] * k];
}
function eulerFromQuaternion(q) {
return [
Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
];
}
function cartesian(spherical) {
var λ = spherical[0] * radians,
φ = spherical[1] * radians,
cosφ = Math.cos(φ);
return [
cosφ * Math.cos(λ),
cosφ * Math.sin(λ),
Math.sin(φ)
];
}
function dot(a, b) {
for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i];
return s;
}
function cross(a, b) {
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
];
}
})();
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="d3.geo.zoom.js"></script>
<style>
.land {
fill: green;
}
.graticule {
fill-opacity: 0;
stroke: gray;
stroke-opacity: 0.5;
}
.point {
stroke: red;
fill-opacity: 0.2;
fill: white;
}
</style>
</head>
<body>
<svg class="myworld"></svg>
<script>
d3.json("world110.json", function(err, world) {
var countries = topojson.feature(world, world.objects.land);
var width = 420
var height = 400
var projection = d3.geo.orthographic()
//var projection = d3.geo.albers()
//var projection = d3.geo.mercator()
.scale(170)
.rotate([100,0,0])
.translate([width/2, height/2])
.clipAngle(90);
var path = d3.geo.path()
.projection(projection);
var svg = d3.select(".myworld");
var graticule = d3.geo.graticule()
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
svg.append("path")
.datum(countries)
.attr("d", path)
.classed("land", true);
var zoom = d3.geo.zoom()
.projection(projection)
//.scaleExtent([projection.scale() * .7, projection.scale() * 10])
.on("zoom.redraw", function() {
d3.event.sourceEvent.preventDefault();
svg.selectAll("path").attr("d", path);
svg.selectAll("circle")
.attr({
cx: function(d) { return projection(d)[0] },
cy: function(d) { return projection(d)[1] },
})
})
d3.selectAll("path").call(zoom);
var lonlat = [-109, 37.7833];
var xy = projection(lonlat)
svg.append("circle")
.datum(lonlat)
.classed("point", true)
.attr({
cx: function(d) { return projection(d)[0] },
cy: function(d) { return projection(d)[1] },
r: 10
})
navigator.geolocation.getCurrentPosition(function(pos){
console.log(pos);
var coords = [pos.coords.longitude, pos.coords.latitude]
svg.append("circle")
.datum(coords)
.attr({
cx: function(d) { return projection(d)[0] },
cy: function(d) { return projection(d)[1] },
r: 15,
})
.classed("point", true)
.style("stroke", "blue");
})
})
</script>
</body>
</html>
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment