Skip to content

Instantly share code, notes, and snippets.

@calvinmetcalf
Last active December 9, 2017 17:16
Show Gist options
  • Save calvinmetcalf/5629688 to your computer and use it in GitHub Desktop.
Save calvinmetcalf/5629688 to your computer and use it in GitHub Desktop.
Reprojected Raster Tiles
<!DOCTYPE html>
<meta charset="utf-8">
<title>Reprojected Raster Tiles</title>
<style>
@import url(http://www.jasondavies.com/maps/maps.css);
#map {
position: relative;
margin: 0 auto;
overflow: hidden;
}
.layer0 {
-webkit-transform: scale(.5);
-webkit-transform-origin: 0 0 0;
}
.layer {
-webkit-transform-origin: 0 0 0;
}
.tile {
position: absolute;
}
</style>
<div id="map"></div>
<p class="caption">Zoomable raster world map on an Lambert Conic Conformal aka Massachusetts State Plane.
<div class="wrapper">
<p>Based off <a href="http://www.jasondavies.com/maps/raster/"> Jason Davies map</a> which is a combination of <a href="http://bl.ocks.org/mbostock/4329423">Mike Bostock’s raster reprojection</a>, <a href="../tile/">automatic projection tiles</a> and a <a href="http://a.tiles.mapbox.com/v3/examples.map-4l7djmvo/page.html">MapBox terrain example</a>.
<p>Thanks to <a href="http://bost.ocks.org/mike">Mike Bostock</a> and <a href="http://somebits.com">Nelson Minar</a> for their comments and encouragement!
</div>
<script src="http://www.jasondavies.com/d3.min.js"></script>
<script src="http://www.jasondavies.com/maps/d3.geo.projection.min.js"></script>
<script src="http://www.jasondavies.com/maps/topojson.min.js"></script>
<script src="http://www.jasondavies.com/maps/d3.quadtiles.js"></script>
<script>
var ratio = 2,
width = 960 * ratio,
height = 600 * ratio,
p = .5;
var projection = d3.geo.conicConformal()
.parallels([42.68333333333333, 41.71666666666667])
.rotate([71.55, 0])
.center([0, 42])
.scale((150 * ratio)<<7)
.translate([width / 2, height / 2])
.clipExtent([[p, p], [width - p, height - p]]);
var layer = d3.select("#map")
.style("width", width / ratio + "px")
.style("height", height / ratio + "px")
.call(d3.behavior.zoom()
.translate([.5 * width / ratio, .5 * height / ratio])
.scale(projection.scale() / ratio)
.scaleExtent([1e2, 1e8])
.on("zoom", function() {
var t = d3.event.translate,
s = d3.event.scale;
projection.translate([t[0] * ratio, t[1] * ratio]).scale(s * ratio);
redraw();
}))
.append("div").attr("class", "layer0")
.append("div").attr("class", "layer")
var path = d3.geo.path().projection(projection);
var imgCanvas = document.createElement("canvas"),
imgContext = imgCanvas.getContext("2d");
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 λ0 = k[0] / width * 360 - 180,
λ1 = (k[0] + 1) / width * 360 - 180,
φ1 = mercatorφ(k[1] / width * 360 - 180),
φ0 = mercatorφ((k[1] + 1) / width * 360 - 180);
var width = canvas.width = x1 - x0,
height = canvas.height = y1 - y0,
context = canvas.getContext("2d");
if (width && height) {
var sourceData = imgContext.getImageData(0, 0, dx, dy).data,
target = context.createImageData(width, height),
targetData = target.data;
for (var y = y0, i = -1; y < y1; ++y) {
for (var x = x0; x < x1; ++x) {
var p = projection.invert([x, y]), λ = p[0], φ = p[1];
if (λ > λ1 || λ < λ0 || φ > φ1 || φ < φ0) { i += 4; continue; }
var q = (((λ - λ0) / (λ1 - λ0) * dx | 0) + ((φ1 - φ) / (φ1 - φ0) * dy | 0) * dx) * 4;
//var q = ((90 - φ) / 180 * dy | 0) * dx + ((180 + λ) / 360 * dx | 0) << 2;
targetData[++i] = sourceData[q];
targetData[++i] = sourceData[++q];
targetData[++i] = sourceData[++q];
targetData[++i] = 255;
}
}
context.putImageData(target, 0, 0);
}
d3.selectAll([canvas])
.style("left", x0 + "px")
.style("top", y0 + "px");
projection.translate(t).scale(s).clipExtent(c);
}
redraw();
function redraw() {
// TODO improve zoom level computation
var pot = Math.log(projection.scale()) / Math.LN2 | 0,
ds = projection.scale() / (1 << pot),
t = projection.translate(),
z = pot - 6;
layer.style("-webkit-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;
var image = d.image = new Image;
image.crossOrigin = true;
image.onload = function() { onload(d, canvas, pot); };
var k = d.key;
image.src = "http://" + ["a", "b", "c"][(k[0] * 31 + k[1]) % 3] + ".tiles.mapbox.com/v3/examples.map-4l7djmvo/" + k[2] + "/" + k[0] + "/" + k[1] + ".png";
});
tile.exit().remove();
}
function key(d) { return d.key.join(", "); }
function mercatorφ(y) {
return Math.atan(Math.exp(-y * Math.PI / 180)) * 360 / Math.PI - 90;
}
function pixel(d) { return (d | 0) + "px"; }
</script>
@almccon
Copy link

almccon commented Jun 15, 2016

Needs d3.quadtiles.js, which seems to have moved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment