Skip to content

Instantly share code, notes, and snippets.

@HarryStevens
Last active November 26, 2019 01:24
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save HarryStevens/676a9e3d5681365045197238cdc1ba8b to your computer and use it in GitHub Desktop.
Raster Reprojection I
license: gpl-3.0

You can reproject equirectangular raster tiles onto a Canvas with an orthographic projection. You can also combine the Canvas with SVG raster tiles.

Toggle the checkbox to see what it looks like with and without a clipping mask.

You can achieve significant performance improvements with WebGL. See Raster Reprojection II for a demonstration.

The image is from Wikimedia Commons and described thus:

Heightmap of Earth's surface (including water and ice) in equirectangular projection, normalized as 8-bit grayscale, where lighter values indicate higher elevation. Sea level is shown as #0c0c0c.

Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<style>
body {
margin: 0;
}
.boundary, .polygon {
fill: none;
stroke: white;
}
.boundary {
stroke-opacity: .6;
}
.polygon {
stroke-opacity: .2;
stroke-dasharray: 5, 5;
}
svg, canvas, div {
position: absolute;
}
div {
z-index: 1;
background: rgba(255, 255, 255, .8);
padding: 6px 12px 6px 4px;
font-family: "Helvetica Neue", sans-serif;
font-size: .9em;
}
.mask {
display: none;
}
.mask.show {
display: block;
}
</style>
</head>
<body>
<div><input type="checkbox" checked />Mask</div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson@3"></script>
<script>
var width = window.innerWidth, height = window.innerHeight;
var canvas = d3.select("body").append("canvas").attr("width", width).attr("height", height);
var svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
var context = canvas.node().getContext("2d");
var projection = d3.geoOrthographic();
var path = d3.geoPath().projection(projection);
var mask = svg.append("defs")
.append("mask")
.attr("id", "hole");
var mask_rect = mask.append("rect").style("fill", "white").attr("width", width).attr("height", height);
var mask_circle = mask.append("path").datum({type: "Sphere"}).style("fill", "black");
var rect = svg.append("rect").attr("class", "mask").attr("mask", "url(#hole)").style("fill", "white").attr("width", width).attr("height", height);
d3.json("countries.json", (error, world) => {
if (error) throw error;
var mesh = topojson.mesh(world, world.objects.land, (a, b) => a === b);
var feature = topojson.feature(world, world.objects.countries);
projection.fitSize([width, height], mesh);
mask_circle.attr("d", path);
var image = new Image;
image.src = "raster.jpg";
image.onload = () => draw();
window.onresize = () => {
width = window.innerWidth, height = window.innerHeight;
projection.fitSize([width, height], mesh);
canvas.attr("width", width).attr("height", height);
svg.attr("width", width).attr("height", height);
mask_rect.attr("width", width).attr("height", height);
mask_circle.attr("d", path);
rect.attr("width", width).attr("height", height);
draw();
};
d3.timer((t) => {
projection.rotate([t / 90, t / 90, t / 90]);
draw();
});
rect.classed("show", true);
d3.select("input").on("click", () => {
rect.classed("show", d3.select("input").property("checked"));
});
function draw(){
// See: https://bl.ocks.org/mbostock/4329423
context.drawImage(image, 0, 0, width, height);
var sourceData = context.getImageData(0, 0, width, height).data,
target = context.createImageData(width, height),
targetData = target.data;
for (var y = 0, i = -1; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var p = projection.invert([x, y]), lambda = p[0], phi = p[1];
if (lambda > 180 || lambda < -180 || phi > 90 || phi < -90) { i += 4; continue; }
var q = ((90 - phi) / 180 * height | 0) * width + ((180 + lambda) / 360 * width | 0) << 2;
targetData[++i] = sourceData[q];
targetData[++i] = sourceData[++q];
targetData[++i] = sourceData[++q];
targetData[++i] = 255;
}
}
context.putImageData(target, 0, 0);
var polygons = svg.selectAll(".polygon")
.data(feature.features);
polygons.enter().append("path")
.attr("class", "polygon")
.merge(polygons)
.attr("d", path);
var boundary = svg.selectAll(".boundary")
.data([mesh]);
boundary.enter().append("path")
.attr("class", "boundary")
.merge(boundary)
.attr("clip-path", "url(#clip)")
.attr("d", path);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment