|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<title>proj tester</title> |
|
<meta name="viewport" content="width=device-width"/> |
|
</head> |
|
<body style="margin: 0"> |
|
|
|
<canvas id="a" style="position: absolute; left:0; top: 0; width: 100%; height: 50%;"></canvas> |
|
<canvas id="b" style="position: absolute; left:0; top: 50%; width: 100%; height: 50%;"></canvas> |
|
|
|
<script src="https://d3js.org/d3.v6.min.js"></script> |
|
<script src="https://d3js.org/d3-geo-projection.v3.min.js"></script> |
|
<script> |
|
|
|
// Source: https://observablehq.com/@d3/interrupted-mollweide-oceans |
|
const interruptedMollweideOceans = d3.geoInterrupt( |
|
d3.geoMollweideRaw, |
|
[[ // northern hemisphere |
|
[[-180, 0], [-130, 90], [-90, 5]], |
|
[[-90, 5], [-30, 90], [60, 5]], |
|
[[60, 5], [120, 90], [180, 0]] |
|
], [ // southern hemisphere |
|
[[-180, 0], [-120, -90], [-60, -5]], |
|
[[-60, -5], [20, -90], [90, -5]], |
|
[[90, -5], [140, -90], [180, 0]] |
|
]] |
|
); |
|
|
|
// Same as above, but with interruption points placed at the equator |
|
const interruptedMollweideOceans_adjusted = d3.geoInterrupt( |
|
d3.geoMollweideRaw, |
|
[[ // northern hemisphere |
|
[[-180, 0], [-130, 90], [-90, 0]], |
|
[[-90, 0], [-30, 90], [60, 0]], |
|
[[60, 0], [120, 90], [180, 0]] |
|
], [ // southern hemisphere |
|
[[-180, 0], [-120, -90], [-60, 0]], |
|
[[-60, 0], [20, -90], [90, 0]], |
|
[[90, 0], [140, -90], [180, 0]] |
|
]] |
|
); |
|
|
|
function fillGlobeWithProjectionInvert(canvas, proj) { |
|
const width = canvas.clientWidth * devicePixelRatio; |
|
const height = canvas.clientHeight * devicePixelRatio; |
|
canvas.width = width; |
|
canvas.height = height; |
|
|
|
proj.fitExtent([[20, 20], [width - 20, height - 20]], {type: "Sphere"}); |
|
|
|
const ctx = canvas.getContext("2d"); |
|
const path = d3.geoPath().projection(proj).context(ctx); |
|
|
|
// first fill the whole globe with red as a basis for comparison |
|
ctx.fillStyle = "red"; |
|
ctx.beginPath(); |
|
path({type: "Sphere"}); |
|
ctx.clip(); |
|
ctx.fill(); |
|
|
|
// now scan all pixels in the canvas and switch red to blue wherever the |
|
// projection's invert function is defined |
|
const imageData = ctx.getImageData(0, 0, width, height); |
|
const {data} = imageData; |
|
for (let y = 0; y < height; y++) { |
|
for (let x = 0; x < width; x++) { |
|
const [lon, lat] = proj.invert([x, y]) ?? []; |
|
if (!isNaN(lon) && !isNaN(lat)) { |
|
const i = (y * width + x) * 4; |
|
data[i ] = 100; // r |
|
data[i+1] = 100; // g |
|
data[i+2] = 255; // b |
|
} |
|
} |
|
} |
|
ctx.putImageData(imageData, 0, 0); |
|
|
|
// add a graticule to help visualize the projection |
|
const graticule = d3.geoGraticule()(); |
|
ctx.strokeStyle = "black"; |
|
ctx.lineWidth = 1; |
|
ctx.beginPath(); |
|
path(graticule); |
|
ctx.stroke(); |
|
} |
|
|
|
fillGlobeWithProjectionInvert(d3.select("#a").node(), interruptedMollweideOceans); |
|
fillGlobeWithProjectionInvert(d3.select("#b").node(), interruptedMollweideOceans_adjusted); |
|
|
|
</script> |
|
</body> |
|
</html> |