A globe with pan and zoom showing all cities with population data from the MaxMind WorldCities database (~48k cities).
The globe is rendered with canvas. Also compare:
Canvas Globe with cities - buffered
Built with blockbuilder.org
forked from larsvers's block: Canvas Globe with cities
xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0;background: #fcfcfa; }
</style>
</head>
<body>
<script>
/* Set up */
/* ====== */
var width = 960,
height = 500,
originalScale = height / 2.1,
scale = originalScale,
translation = [width / 2, height / 2],
origin = [71,-42,0],
worldVel = [.1,-.05,0],
satVel = worldVel[0]*1.8,
t0 = Date.now(),
scaleChange,
rotation;
var sphere = {type: 'Sphere'};
var graticule = d3.geoGraticule();
function circle(ctx, x, y, radius, colour) {
ctx.fillStyle = colour;
ctx.beginPath();
ctx.arc(x, y, radius, 2 * Math.PI, false);
ctx.fill()
}
// set up the main canvas and the projection
var canvas = d3.select('body').append('canvas')
.attr('width', width)
.attr('height', height);
var context = canvas.node().getContext('2d');
var worldProjection = d3.geoOrthographic()
.scale(scale)
.translate(translation)
.clipAngle(90);
var satProjection = d3.geoOrthographic()
.scale(scale*1.25)
.translate(translation)
.clipAngle(121.5);
var worldPath = d3.geoPath()
.projection(worldProjection)
.context(context)
.pointRadius(1);
var satPath = d3.geoPath()
.projection(satProjection)
.context(context)
.pointRadius(3.7392);
/* Data load */
/* ========= */
d3.queue()
.defer(d3.csv, 'satellites.csv')
.defer(d3.json, 'world-110m-simple.json')
.await(load);
function load(error, cities, world) {
if (error) { console.log(error); }
var land = topojson.feature(world, world.objects.countries),
grid = graticule();
var outerArray = [];
cities.forEach(function(el) {
var innerArray = [+el.Latitude, +el.Longitude];
outerArray.push(innerArray);
});
var points = {
type: "MultiPoint",
coordinates: outerArray
};
// Draw the world
function drawWorld() {
requestAnimationFrame(drawWorld);
context.clearRect(0, 0, width, height);
context.save();
context.beginPath();
worldPath(sphere);
context.lineWidth = 1;
context.strokeStyle = '#ccc';
context.stroke();
// context.beginPath();
// satPath(sphere);
// context.lineWidth = 1;
// context.strokeStyle = '#fcf';
// context.stroke();
context.beginPath();
worldPath(grid);
context.lineWidth = .5;
context.strokeStyle = '#ddd';
context.stroke();
context.beginPath();
worldPath(land);
context.fillStyle = '#ccc';
context.fill();
context.beginPath();
worldPath(land);
context.lineWidth = .5;
context.strokeStyle = '#fff';
context.stroke();
context.beginPath();
satPath(points);
context.fillStyle = "tomato"
context.fill();
context.restore();
spin();
} // drawWorld()
// First draw
requestAnimationFrame(drawWorld);
var zoom = d3.zoom()
.scaleExtent([0.5, 4])
.on("zoom", zoomed)
canvas.call(zoom);
var previousScaleFactor = 1;
function zoomed() {
//worldTimer.stop();
var dx = d3.event.sourceEvent.movementX;
var dy = d3.event.sourceEvent.movementY;
var event = d3.event.sourceEvent.type;
context.save();
context.clearRect(0, 0, width, height);
if (event === 'wheel') {
scaleFactor = d3.event.transform.k;
scaleChange = scaleFactor - previousScaleFactor;
scale = scale + scaleChange * originalScale;
worldProjection.scale(scale);
satProjection.scale(scale*1.2);
previousScaleFactor = scaleFactor;
} else {
var r = worldProjection.rotate();
rotation = [r[0] + dx * 0.4, r[1] - dy * 0.5, r[2]];
worldProjection.rotate(rotation);
satProjection.rotate(rotation);
}
//worldTimer.restart(spin);
context.restore();
} // zoomed()
function spin() {
//Update sat locations
for (i=0;i<points.coordinates.length;i++){
points.coordinates[i][1] += satVel;
}
//Auto rotate system
worldProjection.rotate()
} // spin()
} // load()
</script>
</body>
https://d3js.org/d3.v4.min.js
https://d3js.org/topojson.v1.min.js