Skip to content

Instantly share code, notes, and snippets.

@mpmckenna8
Forked from emeeks/d3.geo.raster.js
Last active August 31, 2015 18:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mpmckenna8/55557f2cc06ae603f242 to your computer and use it in GitHub Desktop.
Save mpmckenna8/55557f2cc06ae603f242 to your computer and use it in GitHub Desktop.
Mapbox tiles on a d3.globe, oh gosh

Loading multiple tile layers with d3.carto.map.

Each layer is represented in the layer selector and can be hidden or displayed by clicking its name or the checkbox.

This time with some of my maps from mapbox on a globe.

// Copyright 2014, Jason Davies, http://www.jasondavies.com/
(function() {
d3.geo.raster = function(projection) {
var path = d3.geo.path().projection(projection),
url = null,
scaleExtent = [0, Infinity],
subdomains = ["a", "b", "c", "d"];
var reprojectDispatch = d3.dispatch('reprojectcomplete');
var imgCanvas = document.createElement("canvas"),
imgContext = imgCanvas.getContext("2d");
function redraw(layer) {
// TODO improve zoom level computation
var z = Math.max(scaleExtent[0], Math.min(scaleExtent[1], (Math.log(projection.scale()) / Math.LN2 | 0) - 6)),
pot = z + 6,
ds = projection.scale() / (1 << pot),
t = projection.translate();
layer.style(prefix + "transform", "translate(" + t.map(pixel) + ")scale(" + ds + ")");
var tile = layer.selectAll(".tile")
.data(d3.quadTiles(projection, z), key);
tile.enter().append("canvas")
.attr("class", "tile")
.each(function(d) {
var canvas = this,
image = d.image = new Image,
k = d.key;
image.crossOrigin = true;
image.onload = function() { setTimeout(function() { onload(d, canvas, pot); }, 1); };
image.src = url({x: k[0], y: k[1], z: k[2], subdomain: subdomains[(k[0] * 31 + k[1]) % subdomains.length]});
})
.transition()
.delay(500)
.each("end", function() {reprojectDispatch.reprojectcomplete()});
tile.exit().remove();
}
redraw.url = function(_) {
if (!arguments.length) return url;
url = typeof _ === "string" ? urlTemplate(_) : _;
return redraw;
};
redraw.scaleExtent = function(_) {
return arguments.length ? (scaleExtent = _, redraw) : scaleExtent;
};
redraw.subdomains = function(_) {
return arguments.length ? (subdomains = _, redraw) : subdomains;
};
d3.rebind(redraw, reprojectDispatch, "on");
return redraw;
function onload(d, canvas, pot) {
var t = projection.translate(),
s = projection.scale(),
c = projection.clipExtent(),
image = d.image,
dx = image.width,
dy = image.height,
k = d.key,
width = 1 << k[2];
projection.translate([0, 0]).scale(1 << pot).clipExtent(null);
imgCanvas.width = dx, imgCanvas.height = dy;
imgContext.drawImage(image, 0, 0, dx, dy);
var bounds = path.bounds(d),
x0 = d.x0 = bounds[0][0] | 0,
y0 = d.y0 = bounds[0][1] | 0,
x1 = bounds[1][0] + 1 | 0,
y1 = bounds[1][1] + 1 | 0;
var Lambda0 = k[0] / width * 360 - 180,
Lambda1 = (k[0] + 1) / width * 360 - 180,
Phi0 = k[1] / width * 360 - 180,
Phi1 = (k[1] + 1) / width * 360 - 180;
mPhi0 = mercatorPhi(Phi0),
mPhi1 = mercatorPhi(Phi1);
var width = canvas.width = x1 - x0,
height = canvas.height = y1 - y0,
context = canvas.getContext("2d");
if (width > 0 && height > 0) {
var sourceData = imgContext.getImageData(0, 0, dx, dy).data,
target = context.createImageData(width, height),
targetData = target.data,
interpolate = bilinear(function(x, y, offset) {
return sourceData[(y * dx + x) * 4 + offset];
});
for (var y = y0, i = -1; y < y1; ++y) {
for (var x = x0; x < x1; ++x) {
var p = projection.invert([x, y]), Lambda, Phi;
if (!p || isNaN(Lambda = p[0]) || isNaN(Phi = p[1]) || Lambda > Lambda1 || Lambda < Lambda0 || Phi > mPhi0 || Phi < mPhi1) { i += 4; continue; }
Phi = mercatorPhi.invert(Phi);
var sx = (Lambda - Lambda0) / (Lambda1 - Lambda0) * dx,
sy = (Phi - Phi0) / (Phi1 - Phi0) * dy;
if (1) {
var q = (((Lambda - Lambda0) / (Lambda1 - Lambda0) * dx | 0) + ((Phi - Phi0) / (Phi1 - Phi0) * dy | 0) * dx) * 4;
targetData[++i] = sourceData[q];
targetData[++i] = sourceData[++q];
targetData[++i] = sourceData[++q];
} else {
targetData[++i] = interpolate(sx, sy, 0);
targetData[++i] = interpolate(sx, sy, 1);
targetData[++i] = interpolate(sx, sy, 2);
}
targetData[++i] = 0xff;
}
}
context.putImageData(target, 0, 0);
}
d3.selectAll([canvas])
.style("left", x0 + "px")
.style("top", y0 + "px");
projection.translate(t).scale(s).clipExtent(c);
}
};
function key(d) { return d.key.join(", "); }
function pixel(d) { return (d | 0) + "px"; }
// Find latitude based on Mercator y-coordinate (in degrees).
function mercatorPhi(y) {
return Math.atan(Math.exp(-y * Math.PI / 180)) * 360 / Math.PI - 90;
}
mercatorPhi.invert = function(Phi) {
return -Math.log(Math.tan(Math.PI * .25 + Phi * Math.PI / 360)) * 180 / Math.PI;
};
function bilinear(f) {
return function(x, y, o) {
var x0 = Math.floor(x),
y0 = Math.floor(y),
x1 = Math.ceil(x),
y1 = Math.ceil(y);
if (x0 === x1 || y0 === y1) return f(x0, y0, o);
return (f(x0, y0, o) * (x1 - x) * (y1 - y)
+ f(x1, y0, o) * (x - x0) * (y1 - y)
+ f(x0, y1, o) * (x1 - x) * (y - y0)
+ f(x1, y1, o) * (x - x0) * (y - y0)) / ((x1 - x0) * (y1 - y0));
};
}
function urlTemplate(s) {
return function(o) {
return s.replace(/\{([^\}]+)\}/g, function(_, d) {
var v = o[d];
return v != null ? v : d === "quadkey" && quadkey(o.x, o.y, o.z);
});
};
}
function quadkey(column, row, zoom) {
var key = [];
for (var i = 1; i <= zoom; i++) {
key.push((((row >> zoom - i) & 1) << 1) | ((column >> zoom - i) & 1));
}
return key.join("");
}
})();
// Check for vendor prefixes, by Mike Bostock.
var prefix = prefixMatch(["webkit", "ms", "Moz", "O"]);
function prefixMatch(p) {
var i = -1, n = p.length, s = document.body.style;
while (++i < n) if (p[i] + "Transform" in s) return "-" + p[i].toLowerCase() + "-";
return "";
}
(function() {
d3.quadTiles = function(projection, zoom) {
var tiles = [],
width = 1 << (zoom = Math.max(0, zoom)),
step = Math.max(.2, Math.min(1, zoom * .01)),
invisible,
precision = projection.precision(),
stream = projection.precision(960).stream({
point: function() { invisible = false; },
lineStart: noop,
lineEnd: noop,
polygonStart: noop,
polygonEnd: noop
});
visit(-180, -180, 180, 180);
projection.precision(precision);
return tiles;
function visit(x1, y1, x2, y2) {
var w = x2 - x1,
m1 = mercatorφ(y1),
m2 = mercatorφ(y2),
δ = step * w;
invisible = true;
stream.polygonStart(), stream.lineStart();
for (var x = x1; x < x2 + δ / 2 && invisible; x += δ) stream.point(x, m1);
for (var y = m1; (y += δ) < m2 && invisible;) stream.point(x2, y);
for (var x = x2; x > x1 - δ / 2 && invisible; x -= δ) stream.point(x, m2);
for (var y = m2; (y -= δ) > m1 && invisible;) stream.point(x1, y);
if (invisible) stream.point(x1, m1);
stream.lineEnd(), stream.polygonEnd();
if (w <= 360 / width) {
// TODO :)
if (!invisible) tiles.push({type: "Polygon", coordinates: [
d3.range(x1, x2 + δ / 2, δ).map(function(x) { return [x, y1]; })
.concat([[x2, .5 * (y1 + y2)]])
.concat(d3.range(x2, x1 - δ / 2, -δ).map(function(x) { return [x, y2]; }))
.concat([[x1, .5 * (y1 + y2)]])
.concat([[x1, y1]]).map(function(d) { return [d[0], mercatorφ(d[1])]; })
], key: [(180 + x1) / 360 * width | 0, (180 + y1) / 360 * width | 0, zoom], centroid: [.5 * (x1 + x2), .5 * (m1 + m2)]});
} else if (!invisible) {
var x = .5 * (x1 + x2), y = .5 * (y1 + y2);
visit(x1, y1, x, y);
visit(x, y1, x2, y);
visit(x1, y, x, y2);
visit(x, y, x2, y2);
}
}
}
function noop() {}
function mercatorφ(y) {
return Math.atan(Math.exp(-y * Math.PI / 180)) * 360 / Math.PI - 90;
}
})();
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>d3.carto.map - Multiple Tile Layers</title>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="https://raw.githubusercontent.com/emeeks/d3-carto-map/master/d3map.css" />
<link type="text/css" rel="stylesheet" href="https://raw.githubusercontent.com/emeeks/d3-carto-map/master/examples/example.css" />
</head>
<style>
html,body {
height: 100%;
width: 100%;
margin: 0;
}
#map {
height: 100%;
width: 100%;
position: absolute;
}
</style>
<script>
function makeSomeMaps() {
map = d3.carto.map();
d3.select("#map").call(map);
map.mode('globe');
map.refresh();
tileLayer = d3.carto.layer.tile();
tileLayer
.path("mpmckenna8.map-pvvgcbh5")
.label("Base")
.on("load", recenter);
map.addTileLayer("mpmckenna8.map-5hpnzi5m", "Purple", "mapbox");
map.addTileLayer("examples.map-h67hf2ic", "Streets", "mapbox", false);
map.centerOn([-0.1275,51.507],"latlong");
map.addTopoJSONLayer("http://bl.ocks.org/emeeks/raw/c970c9ee3e242e90004b/world.topojson","world","countries","canvas","countries");
map.addTopoJSONLayer("http://bl.ocks.org/emeeks/raw/c970c9ee3e242e90004b/sample_routes.topojson","toporoutes","rivers","canvas","all");
map.addCartoLayer(tileLayer);
function recenter() {
map.centerOn([-0.1275,51.507],"latlong",5000);
}
}
</script>
<body onload="makeSomeMaps()">
<div id="map"></div>
<footer>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="http://d3js.org/topojson.v1.min.js" type="text/javascript">
</script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js" type="text/javascript">
</script>
<script src="tile.js" type="text/javascript">
</script>
<script src="d3.quadtiles.js" type="text/javascript">
</script>
<script src="d3.geo.raster.js" type="text/javascript">
</script>
<script src="https://rawgit.com/emeeks/d3-carto-map/master/d3.carto.map.js" type="text/javascript">
</script>
</footer>
</body>
</html>
d3.geo.tile = function() {
var size = [960, 500],
scale = 256,
translate = [size[0] / 2, size[1] / 2],
zoomDelta = 0;
function tile() {
var z = Math.max(Math.log(scale) / Math.LN2 - 8, 0),
z0 = Math.round(z + zoomDelta),
k = Math.pow(2, z - z0 + 8),
origin = [(translate[0] - scale / 2) / k, (translate[1] - scale / 2) / k],
tiles = [],
cols = d3.range(Math.max(0, Math.floor(-origin[0])), Math.max(0, Math.ceil(size[0] / k - origin[0]))),
rows = d3.range(Math.max(0, Math.floor(-origin[1])), Math.max(0, Math.ceil(size[1] / k - origin[1])));
rows.forEach(function(y) {
cols.forEach(function(x) {
tiles.push([x, y, z0]);
});
});
tiles.translate = origin;
tiles.scale = k;
return tiles;
}
tile.size = function(_) {
if (!arguments.length) return size;
size = _;
return tile;
};
tile.scale = function(_) {
if (!arguments.length) return scale;
scale = _;
return tile;
};
tile.translate = function(_) {
if (!arguments.length) return translate;
translate = _;
return tile;
};
tile.zoomDelta = function(_) {
if (!arguments.length) return zoomDelta;
zoomDelta = +_;
return tile;
};
return tile;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment