block by emeeks f3105fda25ff785dc5ed

Loading Tile Layers in d3.carto.map

Full Screen

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.

index.html

<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>

d3.geo.raster.js

// 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 "";
}

d3.quadtiles.js


<html>
  <head>
    <meta content="origin" name="referrer">
    <title>Rate limit &middot; 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> &mdash;
        <a href="https://githubstatus.com">GitHub Status</a> &mdash;
        <a href="https://twitter.com/githubstatus">@githubstatus</a>
      </div>
    </div>
  </body>
</html>


d3map.css

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;
}

tile.js

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;
};