Created
July 16, 2014 06:09
-
-
Save enjalot/31168147b88a1748bc8b to your computer and use it in GitHub Desktop.
d3.geo with d3.geo.zoom
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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] | |
]; | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment