How to fix map borders on d3 map rasterization?

I am trying to use raster map playback after in this example . If you change the projection kavrayskiy7 projection on the azimuthal equidistant projection,

 var projection = d3.geo.azimuthalEquidistant() .scale(90) .translate([width / 2, height / 2]) .clipAngle(180 - 1e-3) .precision(.1); 

he must project the Earth onto a disk (image of a projection map). However, raster recreation goes beyond this disk and fills the entire canvas with an expanded image (the inverse projection function is not injective, several x / y points on the map correspond to the same lon / lat coordinate). In the original example, this should be avoided with the line

 if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { i += 4; continue; } 

but for this example this does not work. I found other glitches, for example, when using the Mollweide projection (two lines appear at the poles) due to the same effect.

To solve this problem, one way would be to correct the inverse projections so that they return an error or None when the input x / y is out of range. My attempt was to check if the point is at a distance using a direct projection of the entire sphere to get the SVG path with the border of the map, as indicated in this code:

 var path = d3.geo.path() .projection(projection); var bdry = svg.append("defs").append("path") .datum({type: "Sphere"}) .attr("id", "sphere") .attr("d", path); 

(see, for example, this example ). However, I did not find a simple way to check if the point [x,y] inside the closed SVG path.

So my questions are:

  • Is there a mistake in the back projections, or am I not using them correctly?
  • How can I find if the point [x,y] is inside the svg path, assuming this is the best approach?
  • To curiosity, where is the algorithm code for the d3 path function to obtain the boundary profile of the map? I could not find it on the github repository.

Thanks.

Edit: I looked at all 44 projections in this example , and I found glitches on the following 25:

Albers, Bromley, Collignon, Eckert II, Eckert IV, Eckert VI, Hammer, Hill, Goode Homolosine, Lambert Cylindrical Equal Area, Larrivée, Laskowski, McBryde-Thomas Flat-Polar Parabolic, McBryde-Thomas Flat-Polar Quartic, Mc Brad -Thomas Sinusoidal Planar, Molveid, Earth, Nell-Hammer, Polyclinic, Sinu-Molveid, Van der Grinten, van der Grinten IV, Wagner IV, Wagner VII, Winkel Triple.

+6
source share
2 answers

I use the second answer only because it is a different approach to the same problem. Again, this answer is an alternative approach that tries to avoid a point in solving a polygon that uses the svg contour of the projection length.

This option should (I just tried a few) work for any projection, while my other answer only works for projections projected onto a disk. Secondly, this approach does not try to determine the projection area in order to determine whether to display a pixel, but uses d3.projection itself.


Since multiple points can return the same value using projection.invert, we can run the forecast ahead to check if a pixel needs to be drawn.

If projection(projection.invert(point)) == point , then the point is within our projection.

Of course, there may be accuracy / rounding errors, so some degree of tolerance can be indicated.

This check is suitable for the for loop:

 for (var y = 0, i = -1; y < height; ++y) { for (var x = 0; x < width; ++x) { var p = projection.invert([x, y]), λ = p[0], φ = p[1]; var pxy = projection(p); var tolerance = 0.5; if ( λ > 180 || λ < -180 || φ > 90 || φ < -90 ) { i += 4; continue; } if ( (Math.abs(pxy[0] - x) < tolerance ) && (Math.abs(pxy[1] - y) < tolerance ) ) { 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; } else { i += 4; } } } 

As with the other answer, I built a block from it here .

I have not tested this answer for performance, and it seems strange that such verification is necessary, but it may be a suitable alternative approach to the svg approach suggested in your question.

+1
source

Although I'm sure that you are using the projection.inverse function correctly, relying on:

 if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { i += 4; continue; } 

for a clip, projection will always fail because projection. It seems that inversion always returns angles within 180 degrees east / west. Although there may be a way to modify the project itself to return values ​​greater than 180 degrees, it is probably more complicated than other approaches (and, frankly, this goes far beyond any answer I can give).

Similarly, using the SVG path to represent the contours of the world, and then using this as a basis for determining whether to draw a point, probably complicates this question.

Instead, by accepting a round disk, you can easily calculate the radius of the disk and determine whether to draw a pixel:

 var edge = {}; var center = {}; edge.x = projection([180 - 1e-6, 0])[0]; edge.y = projection([180 - 1e-6, 0])[1]; center.x = width/2; center.y = height/2; var radius = Math.pow( Math.pow(center.x - edge.x,2) + Math.pow(center.y - edge.y,2) , 0.5 ) 

Using the radius of the disk, we can then calculate whether the pixel falls on the disk or beyond in the for loop:

 for (var y = 0, i = -1; y < height; ++y) { for (var x = 0; x < width; ++x) { var p = projection.invert([x, y]), λ = p[0], φ = p[1]; if (Math.pow( Math.pow(center.xx,2) + Math.pow(center.yy,2), 0.5) < radius) { 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; } else { targetData[++i] = 0; targetData[++i] = 0; targetData[++i] = 0; targetData[++i] = 0; } } } 

Together they gave me:

enter image description here

For an aesthetic effect, it may be appropriate to cut the radius by a certain percentage. Of course, for different forecasts this approach may be difficult or impossible.

I put the code in bl.ock here (I moved it to d3 v4 in the process, in part, to see if the behavior of projection.inverse is the same).

For the third part of your question, you can try the d3 graticule function (graticule.outline) for some information on how d3 gets the boundary projection profile.

+2
source

Source: https://habr.com/ru/post/1014374/


All Articles