Skip to content

Instantly share code, notes, and snippets.

@KoGor
Last active November 24, 2021 10:31
Show Gist options
  • Save KoGor/7025316 to your computer and use it in GitHub Desktop.
Save KoGor/7025316 to your computer and use it in GitHub Desktop.
Globe to Map IV

Globe rotating all the time, click on canvas to unreel/reel. In this example transition is based on interpolation between two projections and map always cut along antimeridian, it creates effect of unreelling/reelling. You can see some artefacts during reeling back to globe, I guess it is because of clipping or timings overlapping, if you have any ideas how to fix it, let me know.

For other variations of transition from Orthographic to Equirectangular and back check these examples:

This projected is licensed under the terms of the MIT license.

<!DOCTYPE html>
<html lang="en">
<meta charset="utf-8">
<head>
<title>Globe to Map transition</title>
</head>
<body>
<!-- start -->
<div id="map">
<link href="mapStyle.css" rel="stylesheet">
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="unreelGlobe2Map2.js"></script>
</div>
<!-- end -->
</body>
</html>
#map {
position: relative;
}
svg {
background: #FFF;
pointer-events: all;
}
.zone {
stroke-width: .5px;
stroke-linejoin: round;
cursor: pointer;
}
.zoneTooltip {
position: absolute;
display: none;
pointer-events: none;
background: #fff;
padding: 3px;
text-align: left;
border: solid #ccc 1px;
color: #666;
font-size: 12px;
font-family: sans-serif;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.infoLabel {
position: absolute;
bottom: 15px;
left: 10px;
display: none;
border: solid #ccc 1px;
color: #666;
background: #FFFFFF;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-webkit-box-shadow: 0px 0px 3px #66C2FF;
-moz-box-shadow: 0px 0px 3px #66C2FF;
box-shadow: 0px 0px 3px #66C2FF;
text-align: left;
padding: 2px;
font-size: 12px;
font-family: sans-serif;
pointer-events: none;
}
.infoLabel span {
margin: 0px 5px;
}
.mapData {
fill: #E6E6E6;
stroke: #ffffff;
stroke-width: 1px;
}
.ortho {
fill: #E6E6E6;
stroke: #E6E6E6;
stroke-width: 1px;
}
.mapData:hover {
fill: #66C2FF;
}
.focused {
fill: #FF7519;
}
var mapWidth = 960,
mapHeight = 500,
ortho = true,
clipMode = false,
speed = -7e-3,
start = Date.now(),
corr = 0;
var projectionGlobe = d3.geo.orthographic()
.scale(240)
.center([0, 0])
.translate([mapWidth / 2, mapHeight / 2])
.clipAngle(90);
var projectionMap = d3.geo.equirectangular()
.scale(145)
.center([0, 0])
.translate([mapWidth / 2, mapHeight / 2])
var projection = projectionGlobe;
var canvas = d3.select("div#map").append("canvas")
.attr("overflow", "hidden")
.attr("width", mapWidth)
.attr("height", mapHeight);
var context = canvas.node().getContext("2d");
var path = d3.geo.path()
.projection(projection)
.context(context);
//Loading data
queue()
.defer(d3.json, "/d/5685937/world-110m.json")
.defer(d3.tsv, "/d/5685937/world-110m-country-names.tsv")
.await(ready);
function ready(error, world, countryData) {
var countryById = {},
land = topojson.feature(world, world.objects.land),
borders = topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; });
var globe2map = interpolatedProjection(projectionGlobe, projectionMap),
map2globe = interpolatedProjection(projectionMap, projectionGlobe);
canvas.on("click", function(d) {
//Transforming Globe to Map
if (ortho === true) {
ortho = false;
defaultRotate();
setTimeout(function() {
projection = globe2map;
path.projection(projection);
clipMode = false;
animation(projection);
}
, 1600);
} else {
reset();
}
});
//Globe rotating via timer
d3.timer(function() {
if (ortho === true) {
var λ = speed * (Date.now() - start);
projection.rotate([λ + corr, 0]);
context.clearRect(0, 0, mapWidth, mapHeight);
context.beginPath();
context.fillStyle = "#E6E6E6";
path(land);
context.fill();
}
});
function reset() {
//Transforming Map to Globe
projection = map2globe;
path.projection(projection);
clipMode = true;
animation(projection);
setTimeout(function() {
start = Date.now();
ortho = true;
}
, 7600);
}
//Unreelling transformation
function animation(interProj) {
d3.transition()
.duration(7500)
.tween("projection", function() {
return function(_) {
interProj.alpha(_);
context.clearRect(0, 0, mapWidth, mapHeight);
context.beginPath();
path(land);
context.fillStyle = "#E6E6E6";
context.fill();
context.beginPath();
path(borders);
context.strokeStyle = "#ffffff";
context.lineWidth = .5
context.stroke();
};
})
}
function interpolatedProjection(a, b) {
var projection = d3.geo.projection(raw).scale(1),
center = projection.center,
translate = projection.translate,
clip = projection.clipAngle,
α;
function raw(λ, φ) {
var pa = a([λ *= 180 / Math.PI, φ *= 180 / Math.PI]), pb = b([λ, φ]);
return [(1 - α) * pa[0] + α * pb[0], (α - 1) * pa[1] - α * pb[1]];
}
projection.alpha = function(_) {
if (!arguments.length) return α;
α = +_;
var ca = a.center(), cb = b.center(),
ta = a.translate(), tb = b.translate();
center([(1 - α) * ca[0] + α * cb[0], (1 - α) * ca[1] + α * cb[1]]);
translate([(1 - α) * ta[0] + α * tb[0], (1 - α) * ta[1] + α * tb[1]]);
if (clipMode === true) {clip(180 - α * 90);}
return projection;
};
delete projection.scale;
delete projection.translate;
delete projection.center;
return projection.alpha(0);
}
//Rotate to default before animation
function defaultRotate() {
d3.transition()
.duration(1500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [0, 0]);
return function(t) {
projection.rotate(r(t));
context.clearRect(0, 0, mapWidth, mapHeight);
context.beginPath();
path(land);
context.fillStyle = "#E6E6E6";
context.fill();
};
})
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment