5

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/

Community
  • 1
  • 1
Kurt
  • 1,868
  • 3
  • 21
  • 42

1 Answers1

1

(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?

meetamit
  • 24,727
  • 9
  • 57
  • 68
  • 1
    I spent so much time trying to figure out why it was null. Never thought to just ignore it. Interestingly, out of all the supported projections by the library, it seems that only 3 (at least 3 I could tell) end up having nulls. So I just got lucky (unlucky?) in that I happened to chose one of the 3 in my testing. While there's still now some other oddities with some projections (collignon for example), you answered the main question here. Thanks. – Kurt Dec 12 '14 at 19:03