こんにちは。
"D3 3D globe trackball rotation" と言う記事を見つけたので、そっくり真似してみました(d3.js v4 利用)。マウス操作で地球を回転できます。
なお地軸を左右に傾ける必要が無い場合には、回転に関する関数定義はかなり簡素化できます(下記のtrackballAngles_simple()
, composedRotation_simple()
)。
d3_globe_rotation.html
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg { border: 0.5px solid #eee; }
.title {
display: inline-block;
font-size: 48px;
line-height: 90px;
text-align: center;
}
.land {
fill: #69D2E7;
stroke: none;
}
.countries {
stroke: #fff;
stroke-width: 2px;
fill: none;
}
.graticule {
fill: none;
stroke: #777;
stroke-width: 0.5px;
stroke-opacity: 0.5;
}
</style>
<body>
<div .title><a href="http://bl.ocks.org/patricksurry/5721459">D3 Globe Trackball</a></div>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="http://d3js.org/topojson.v3.min.js"></script>
<script>
var radius = 220, scale = 1.0, scaleMin = 0.1;
var projection = d3.geoOrthographic()
.scale(radius)
.translate([radius, radius])
.clipAngle(90);
var path = d3.geoPath()
.projection(projection);
var graticule = d3.geoGraticule();
var svg = d3.select("body").append("svg")
.attr("width", radius * 2)
.attr("height", radius * 2)
.on("mousedown", mousedown)
.on("mousemove", mousemove)
.on("mouseup", mouseup)
.on("wheel", zoom);
var circle = svg.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", radius)
.style("fill", "none")
.style("stroke", "black");
var url = "https://unpkg.com/world-atlas@1/world/50m.json"; // topojson
d3.json(url, function(error, world) {
svg.insert("path")
.datum(topojson.feature(world, world.objects.land))
.attr("class", "land")
.attr("d", path);
svg.insert("path")
.datum(topojson.mesh(world, world.objects.countries))
.attr("class", "countries")
.attr("d", path);
svg.append("path")
.datum(graticule)
.attr("class", "graticule")
.attr("d", path);
});
function zoom() {
scale *= Math.exp(d3.event.deltaY*0.01);
scale = Math.max(scale, scaleMin);
circle.attr("r", radius*scale);
projection.scale(radius*scale);
svg.selectAll("path").attr("d", path);
}
function trackballAngles(pt) {
var r = projection.scale();
var c = projection.translate();
var x = pt[0] - c[0], y = - (pt[1] - c[1]), ss = x*x + y*y;
var z = r*r > 2 * ss ? Math.sqrt(r*r - ss) : r*r / 2 / Math.sqrt(ss);
var lambda = Math.atan2(x, z) * 180 / Math.PI;
var phi = Math.atan2(y, z) * 180 / Math.PI
return [lambda, phi];
}
function composedRotation(λ, ϕ, γ, δλ, δϕ) {
λ = Math.PI / 180 * λ;
ϕ = Math.PI / 180 * ϕ;
γ = Math.PI / 180 * γ;
δλ = Math.PI / 180 * δλ;
δϕ = Math.PI / 180 * δϕ;
var sλ = Math.sin(λ), sϕ = Math.sin(ϕ), sγ = Math.sin(γ),
sδλ = Math.sin(δλ), sδϕ = Math.sin(δϕ),
cλ = Math.cos(λ), cϕ = Math.cos(ϕ), cγ = Math.cos(γ),
cδλ = Math.cos(δλ), cδϕ = Math.cos(δϕ);
var m00 = -sδλ * sλ * cϕ + (sγ * sλ * sϕ + cγ * cλ) * cδλ,
m01 = -sγ * cδλ * cϕ - sδλ * sϕ,
m02 = sδλ * cλ * cϕ - (sγ * sϕ * cλ - sλ * cγ) * cδλ,
m10 = - sδϕ * sλ * cδλ * cϕ - (sγ * sλ * sϕ + cγ * cλ) * sδλ * sδϕ - (sλ * sϕ * cγ - sγ * cλ) * cδϕ,
m11 = sδλ * sδϕ * sγ * cϕ - sδϕ * sϕ * cδλ + cδϕ * cγ * cϕ,
m12 = sδϕ * cδλ * cλ * cϕ + (sγ * sϕ * cλ - sλ * cγ) * sδλ * sδϕ + (sϕ * cγ * cλ + sγ * sλ) * cδϕ,
m20 = - sλ * cδλ * cδϕ * cϕ - (sγ * sλ * sϕ + cγ * cλ) * sδλ * cδϕ + (sλ * sϕ * cγ - sγ * cλ) * sδϕ,
m21 = sδλ * sγ * cδϕ * cϕ - sδϕ * cγ * cϕ - sϕ * cδλ * cδϕ,
m22 = cδλ * cδϕ * cλ * cϕ + (sγ * sϕ * cλ - sλ * cγ) * sδλ * cδϕ - (sϕ * cγ * cλ + sγ * sλ) * sδϕ;
if (m01 != 0 || m11 != 0) {
γ_ = Math.atan2(-m01, m11);
ϕ_ = Math.atan2(-m21, Math.sin(γ_) == 0 ? m11 / Math.cos(γ_) : - m01 / Math.sin(γ_));
λ_ = Math.atan2(-m20, m22);
} else {
γ_ = Math.atan2(m10, m00) - m21 * λ;
ϕ_ = - m21 * Math.PI / 2;
λ_ = λ;
}
return([λ_ * 180 / Math.PI, ϕ_ * 180 / Math.PI, γ_ * 180 / Math.PI]);
}
// simple rotation:
// function trackballAngles_simple(pt) {return pt};
// function composedRotation_simple(λ, ϕ, γ, δλ, δϕ) {return [λ + δλ/3, ϕ - δϕ/3]};
var o0, m0 = null;
function mousedown() {
m0 = trackballAngles(d3.mouse(this));
o0 = projection.rotate();
d3.event.preventDefault();
}
function mousemove() {
if (m0) {
var m1 = trackballAngles(d3.mouse(this));
o1 = composedRotation(o0[0], o0[1], o0[2], m1[0] - m0[0], m1[1] - m0[1])
projection.rotate(o1);
svg.selectAll("path").attr("d", path);
}
}
function mouseup() {
m0 = null;
}
</script>
なお同様なものも多数あるようです。
- http://marcneuwirth.com/blog/2012/06/24/creating-the-earth-with-d3-js/
- https://www.jasondavies.com/maps/rotate/
- http://webpages.uncc.edu/czhang22/samples/globe/index.html
- http://bl.ocks.org/KoGor/5994804
- http://bl.ocks.org/phoebebright/5976037
- http://bl.ocks.org/patricksurry/5721459
- https://bl.ocks.org/hugolpz/a62f1d61168eae985d5c
- http://bl.ocks.org/wetchler/b9a20341f395466b0b5e
- https://bl.ocks.org/emeeks/af3c0114adfd9ead565e6c0f4a9c494e