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 "";
}
(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;
}
})();
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;
};