|
<!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> |
|
|
|
<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://d3js.org/d3.geo.polyhedron.v0.min.js"></script> |
|
<script src="http://www.jasondavies.com/maps/topojson.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) |
|
.translate([width / 2, height / 2]) |
|
.clipExtent([[p, p], [width - p, height - p]]); |
|
*/ |
|
|
|
// These all have some weird compression around the equator... |
|
|
|
var projection = d3.geo.azimuthalEqualArea() |
|
.clipAngle(180 - 1e-3) |
|
.scale(237) |
|
.translate([width / 2, height / 2]) |
|
.precision(.1) |
|
|
|
/* |
|
var projection = d3.geo.robinson() |
|
.scale(300) |
|
.translate([width / 2, height / 2]) |
|
.precision(.1); |
|
|
|
var projection = d3.geo.mercator() |
|
.scale((width + 1) / 2 / Math.PI) |
|
.translate([width / 2, height / 2]) |
|
.precision(.1); |
|
*/ |
|
|
|
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.sm.mapstack.stamen.com/((http%3A%2F%2Fotile1.mqcdn.com%2Ftiles%2F1.0.0%2Fsat%2F%7Bz%7D%2F%7Bx%7D%2F%7By%7D.jpg,mapbox-water[destination-out])[hsl-saturation],watercolor[soft-light])/" + 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> |