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.
<html xmlns="//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="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;
}
.countryborders {
fill: rgba(0,0,0,0);
stroke-width: 1px;
stroke: gray;
cursor: pointer;
}
.roads {
stroke: brown;
stroke-width: 1px;
fill: none;
}
</style>
<script>
function makeSomeMaps() {
map = d3.carto.map();
d3.select("#map").call(map);
map.centerOn([-0.1275,51.507],"latlong");
tileLayer1 = d3.carto.layer.tile();
tileLayer1
.path("examples.map-zgrqqx0w")
.label("Terrain 1")
.visibility(false);
tileLayer2 = d3.carto.layer.tile();
tileLayer2
.path("elijahmeeks.map-ktkeam22")
.label("Terrain 2");
tileLayer3 = d3.carto.layer.tile();
tileLayer3
.path("examples.map-h67hf2ic")
.label("Streets")
.visibility(false);
geojsonLayer = d3.carto.layer.geojson();
geojsonLayer
.path("//bl.ocks.org/emeeks/raw/c970c9ee3e242e90004b/world.geojson")
.label("GeoBorders")
.visibility(false)
.cssClass("countryborders")
.renderMode("canvas");
topojsonLayer = d3.carto.layer.topojson();
topojsonLayer
.path("//bl.ocks.org/emeeks/raw/c970c9ee3e242e90004b/sample_routes.topojson")
.label("TopoRoutes")
.visibility(false)
.cssClass("roads")
.renderMode("canvas");
map.addCartoLayer(tileLayer1);
map.addCartoLayer(tileLayer2);
map.addCartoLayer(tileLayer3);
map.addCartoLayer(topojsonLayer);
map.addCartoLayer(geojsonLayer);
}
</script>
<body onload="makeSomeMaps()">
<div id="map"></div>
<footer>
<script src="//d3js.org/d3.v3.min.js" charset="utf-8" type="text/javascript"></script>
<script src="//d3js.org/topojson.v1.min.js" type="text/javascript">
</script>
<script src="//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>
// 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 "";
}
<html>
<head>
<meta content="origin" name="referrer">
<title>Rate limit · GitHub</title>
<meta name="viewport" content="width=device-width">
<style type="text/css" media="screen">
body {
background-color: #f6f8fa;
color: rgba(0, 0, 0, 0.5);
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
font-size: 14px;
line-height: 1.5;
}
.c { margin: 50px auto; max-width: 600px; text-align: center; padding: 0 24px; }
a { text-decoration: none; }
a:hover { text-decoration: underline; }
h1 { color: #24292e; line-height: 60px; font-size: 48px; font-weight: 300; margin: 0px; }
p { margin: 20px 0 40px; }
#s { margin-top: 35px; }
#s a {
color: #666666;
font-weight: 200;
font-size: 14px;
margin: 0 10px;
}
</style>
</head>
<body>
<div class="c">
<h1>Access has been restricted</h1>
<p>You have triggered a rate limit.<br><br>
Please wait a few minutes before you try again;<br>
in some cases this may take up to an hour.
</p>
<div id="s">
<a href="https://support.github.com">Contact Support</a> —
<a href="https://githubstatus.com">GitHub Status</a> —
<a href="https://twitter.com/githubstatus">@githubstatus</a>
</div>
</div>
</body>
</html>
path,circle,rect,polygon,ellipse,line {
vector-effect: non-scaling-stroke;
}
svg, canvas {
top: 0;
}
#d3MapZoomBox {
position: absolute;
z-index: 10;
height: 100px;
width: 25px;
top: 10px;
right: 50px;
}
#d3MapZoomBox > button {
height:25px;
width: 25px;
line-height: 25px;
}
.d3MapControlsBox > button {
font-size:22px;
font-weight:900;
border: none;
height:25px;
width:25px;
background: rgba(35,31,32,.85);
color: white;
padding: 0;
cursor: pointer;
}
.d3MapControlsBox > button:hover {
background: black;
}
#d3MapPanBox {
position: absolute;
z-index: 10;
height: 100px;
width: 25px;
top: 60px;
right: 50px;
}
#d3MapPanBox > button {
height:25px;
width: 25px;
line-height: 25px;
}
#d3MapPanBox > button#left {
position: absolute;
left: -25px;
top: 10px;
}
#d3MapPanBox > button#right {
position: absolute;
right: -25px;
top: 10px;
}
#d3MapLayerBox {
position: relative;
z-index: 10;
height: 100px;
width: 120px;
top: 10px;
left: 10px;
overflow: auto;
color: white;
background: rgba(35,31,32,.85);
}
#d3MapLayerBox > div {
margin: 5px;
border: none;
}
#d3MapLayerBox ul {
list-style: none;
padding: 0;
margin: 0;
cursor: pointer;
}
#d3MapLayerBox li {
list-style: none;
padding: 0;
}
#d3MapLayerBox li:hover {
font-weight:700;
}
#d3MapLayerBox li input {
cursor: pointer;
}
div.d3MapModal {
position: absolute;
z-index: 11;
background: rgba(35,31,32,.90);
top: 50px;
left: 50px;
color: white;
max-width: 400px;
}
div.d3MapModalContent {
width:100%;
height: 100%;
overflow: auto;
}
div.d3MapModalContent > p {
padding: 0px 20px;
margin: 5px 0;
}
div.d3MapModalContent > h1 {
padding: 0px 20px;
font-size: 20px;
}
div.d3MapModalArrow {
content: "";
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-top: 20px solid rgba(35,31,32,.90);
position: absolute;
bottom: -20px;
left: 33px;
}
#d3MapSVG {
}
rect.minimap-extent {
fill: rgba(200,255,255,0.35);
stroke: black;
stroke-width: 2px;
stroke-dasharray: 5 5;
}
circle.newpoints {
fill: black;
stroke: red;
stroke-width: 2px;
}
path.newfeatures {
fill: steelblue;
fill-opacity: .5;
stroke: pink;
stroke-width: 2px;
}
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;
};