Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3.js map projections on a canvas image

I'm trying to take the contents of a canvas element (which really is just an image loaded onto the canvas) and distort them into different map projections using d3. So for I've found exactly one example that does this (this other SO question).

The problem is that it doesn't work with every projection. The code:

var height = 375,
    width = 750;

var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = width;
canvas.height = height;

var context = canvas.getContext('2d');

var projection = d3.geo.lagrange()
    .translate([width/2, height/2])
    .scale(100); //SET SCALE HERE

var path = d3.geo.path().projection(projection);

var image = new Image();
image.crossOrigin = 'anonymous';
image.src = 'http://i.imgur.com/zZkxbz7.png';
image.onload = function() {
    var dx = width,
        dy = height;

    context.drawImage(image, 0, 0, dx, dy);

    var sourceData = context.getImageData(0, 0, dx, dy).data,
        target = context.createImageData(dx, dy),
        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]),    //ERROR HERE
                λ = p[0],
                φ = p[1];
            if (λ > 180 || λ < -180 || φ > 90 || φ < -90) {
                i += 4;
                continue;
            }
            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.clearRect(0, 0, width, height);
    context.putImageData(target, 0, 0);
}

In the above example, if I set the scale of the projection too low (say 80), then the variable p (in the for loop) ends up being null. I'm not sure why this happens and I need to set the scale so that the projection fits within the canvas area.

A working jsfiddle example: http://jsfiddle.net/vjnfyd8t/

like image 477
Kurt Avatar asked Nov 10 '22 21:11

Kurt


1 Answers

(This is an interesting question)

I can't quite decipher what's at play here, but FWIW, this is the implementation of the Lagrange invert() method. Evidently, there are cases when it returns null, presumably whenever x or y are outside some extents (x,y are pre-transformed here). So I guess you're supposed to ignore the nulls (by e.g. setting the output pixel to transparent), and you can still get a proper scaled map at the end.

This is what I get by just ignoring nulls (but highlighting them in red, to illustrate where it happens)

if (!p) {
  targetData[++i] = 255;
  targetData[++i] = 0;
  targetData[++i] = 0;
  targetData[++i] = 255;
  continue;
}

If the scale is set to 94, the red band disappears completely and it seems like a best fit (at least at that scale). Is that what you wanted?

like image 94
meetamit Avatar answered Nov 14 '22 21:11

meetamit