Skip to content

Instantly share code, notes, and snippets.

@akbstone
Forked from calvinmetcalf/README.md
Last active February 15, 2016 23:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akbstone/23d6ceff18837c49a53b to your computer and use it in GitHub Desktop.
Save akbstone/23d6ceff18837c49a53b to your computer and use it in GitHub Desktop.
Reprojected Raster Tiles
// Copyright 2014 Jason Davies, /web/20150419185921/http://www.jasondavies.com/
(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;
}
})();
<!DOCTYPE html>
<meta charset="utf-8">
<title>Reprojected Raster Tiles</title>
<style>
#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="//d3js.org/d3.v3.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="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";
image.src = "http://" + ["a", "b", "c"][Math.random() * 3 | 0] + ".tile.openstreetmap.org/" + 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment