Skip to content

Instantly share code, notes, and snippets.

@jake-low
Last active October 2, 2017 00:01
Show Gist options
  • Save jake-low/35de268bbcacac8bf9dfa5086d7e8d79 to your computer and use it in GitHub Desktop.
Save jake-low/35de268bbcacac8bf9dfa5086d7e8d79 to your computer and use it in GitHub Desktop.
world population density (2015)
license: gpl-3.0
height: 600
scrolling: no
border: yes

Read more about this map at http://www.jakelow.com/essays/mapping-the-world-population

This gist includes coarse data derived from SEDAC's UN-adjusted population density grids. The original data has a resolution of about 2.5 arc minutes. It's distributed as a 43200x17400 GeoTIFF file. In order to produce coarser data, I ran the following command:

gdalwarp -t_srs EPSG:4326 -tr 0.5 0.5 -r average gpwv4-unpd.original.tiff gpwv4-unpd.very-coarse.tiff

If you want to use higher-resolution data than is provided in this Gist, download the original dataset from SEDAC and run the same command, but substitute smaller values, e.g. -tr 0.1 0.1.

<!DOCTYPE html>
<svg width='960' height='600'></svg>
<script src="https://unpkg.com/d3-array@1"></script>
<script src="https://unpkg.com/d3-contour@1"></script>
<script src="https://unpkg.com/d3-collection@1"></script>
<script src="https://unpkg.com/d3-color@1"></script>
<script src="https://unpkg.com/d3-dispatch@1"></script>
<script src="https://unpkg.com/d3-geo@1"></script>
<script src="https://unpkg.com/d3-geo-projection@2"></script>
<script src="https://unpkg.com/d3-interpolate@1"></script>
<script src="https://unpkg.com/d3-request@1"></script>
<script src="https://unpkg.com/d3-selection@1"></script>
<script src="https://unpkg.com/d3-scale@1"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://unpkg.com/geotiff@0.4/dist/geotiff.browserify.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
queue()
.defer(d3.json, "world-50m.json")
.defer(d3.json, "lakes-50m.json")
.defer(d3.request("gpwv4-unpd.coarse.tiff").responseType("arraybuffer").get)
.await(ready);
function ready (error, world, lakes, unpd) {
if (error) throw error;
var tiff = GeoTIFF.parse(unpd.response),
image = tiff.getImage(),
values = image.readRasters()[0],
m = image.getHeight(),
n = image.getWidth();
values = values.map((v) => v < 0 ? 0 : v);
var numThresholds = 12;
var thresholds = d3.range(0, numThresholds).map((v) => Math.pow(2, v));
var color = d3.scaleThreshold()
.domain(thresholds)
.range(d3.range(0, numThresholds).map((v) => d3.interpolateMagma(v / numThresholds)));
var contours = d3.contours()
.size([n, m])
.smooth(false)
.thresholds([0, ...thresholds]);
var projection = d3.geoInterruptedMollweide()
.scale(165)
.precision(0.1);
var path = d3.geoPath(projection);
var svg = d3.select('svg'),
width = +svg.attr("width"),
height = +svg.attr("height");
svg.append('path').attr('class', 'background')
.datum({type: 'Sphere'})
.attr('fill', color(0))
.attr('d', path);
var defs = svg.append("defs");
defs.append("path")
.datum({type: "Sphere"})
.attr("id", "sphere")
.attr("d", path);
defs.append("path")
.datum(topojson.feature(world, world.objects.land))
.attr("id", "land")
.attr("d", path);
defs.append("clipPath")
.attr("id", "clipSphere")
.append("use").attr("xlink:href", "#sphere");
defs.append("clipPath")
.attr("id", "clipLand")
.attr("clip-path", "url(#clipSphere)")
.append("use").attr("xlink:href", "#land");
svg.append('g').attr('class', 'contours')
.selectAll("path")
.data(contours(values).map(invert))
.enter().append("path")
.attr("fill", function(d) { return color(d.value); })
.attr('clip-path', 'url(#clipLand)')
.attr("d", path);
svg.insert("path")
.datum(topojson.feature(world, world.objects.land))
.attr('fill', 'none')
.attr('stroke', 'white')
.attr('stroke-width', 0.2)
.attr("d", path);
svg.insert("path")
.datum(lakes)
.attr('fill', color(0))
.attr('stroke', 'white')
.attr('stroke-width', 0.1)
.attr("d", path);
var x = d3.scaleLog()
.base(2)
.domain([0.5, thresholds.slice(-1)])//.slice(0, -1))
.rangeRound([0, 600]);
// legend code based on Mike Bostock's https://bl.ocks.org/mbostock/39b34968ad5eab65de1d7da81f78bb27
// used here under terms of GPL v3
var keyGroup = svg.append("g")
.attr("class", "key")
.attr("transform", "translate(180,560)");
keyGroup.selectAll("rect")
.data(color.range().map(function(d) {
d = color.invertExtent(d);
if (d[0] == null) d[0] = x.domain()[0];
if (d[1] == null) d[1] = x.domain()[1];
return d;
}))
.enter().append("rect")
.attr("height", 10)
.attr("x", (d) => x(d[0]))
.attr("width", (d) => x(d[1]) - x(d[0]))
.attr("fill", (d) => color(d[0]));
keyGroup.append("text")
.attr("class", "caption")
.attr("x", x.range()[0])
.attr("y", -8)
.attr("fill", "#000")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.attr("font-size", "130%")
.text("Estimated population density in 2015 (persons per square kilometer)");
keyGroup.call(d3.axisBottom(x)
.tickSize(16)
.tickValues(color.domain().slice(0, -1)))
.select(".domain")
.remove();
// 'invert' function copyright Mike Bostock, used here under terms of GPL v3
// more info: https://bl.ocks.org/mbostock/83c0be21dba7602ee14982b020b12f51
function invert(d) {
var shared = {};
var p = {
type: "Polygon",
coordinates: d3.merge(d.coordinates.map(function(polygon) {
return polygon.map(function(ring) {
return ring.map(function(point) {
return [
point[0] / n * 360 - 180,
85 - point[1] / m * 145
];
}).reverse();
});
}))
};
// Record the y-intersections with the antimeridian.
p.coordinates.forEach(function(ring) {
ring.forEach(function(p) {
if (p[0] === -180) shared[p[1]] |= 1;
else if (p[0] === 180) shared[p[1]] |= 2;
});
});
// Offset any unshared antimeridian points to prevent their stitching.
p.coordinates.forEach(function(ring) {
ring.forEach(function(p) {
if ((p[0] === -180 || p[0] === 180) && shared[p[1]] !== 3) {
p[0] = p[0] === -180 ? -179.9995 : 179.9995;
}
});
});
p = d3.geoStitch(p);
if (!p.coordinates.length) p = {type: "Sphere"}; // TODO fix d3.geoStitch
p.value = d.value;
return p;
}
}
</script>
Display the source blob
Display the rendered blob
Raw
Loading
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