HealPix
Astronomy
Mapping
Code
= {
chart const window = d3.create("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", `0 0 ${fullWidth} ${fullHeight}`)
.attr("preserveAspectRatio", "xMidYMid meet");
const svg = window.append("g")
.attr("class", "healpix");
const options = [
name: "Aitoff", projection: d3.geoAitoff() },
{ name: "Baker", projection: d3.geoBaker() },
{ name: "Boggs", projection: d3.geoBoggs() },
{ name: "Bottomley", projection: d3.geoBottomley() },
{ name: "Craster", projection: d3.geoCraster() },
{ name: "CylindricalEqualArea", projection: d3.geoCylindricalEqualArea() },
{ name: "Eckert1", projection: d3.geoEckert1() },
{ name: "Eckert3", projection: d3.geoEckert3() },
{ name: "Eisenlohr", projection: d3.geoEisenlohr() },
{ name: "Foucaut", projection: d3.geoFoucaut() },
{ name: "Gilbert", projection: d3.geoGilbert() },
{ name: "Ginzburg9", projection: d3.geoGinzburg9() },
{ name: "Gringorten", projection: d3.geoGringorten() },
{ name: "Hammer", projection: d3.geoHammer() },
{ name: "Lagrange", projection: d3.geoLagrange() },
{ name: "Mollweide", projection: d3.geoMollweide() },
{ name: "NaturalEarth1", projection: d3.geoNaturalEarth1() },
{ name: "Nicolosi", projection: d3.geoNicolosi() }
{ .reverse();
]
function getProjection() {
const option = options.pop();
.unshift(option);
optionsreturn option.projection;
}
const nside = 6;
const counts = {};
.forEach(star => {
starsconst XY = HealPix.ang2XY(star, nside);
= (counts[XY] || 0) + 1;
counts[XY] ;
})
const features = Object.entries(counts).map(([key, value]) => {
const XY = key;
const boundary = HealPix.boundary(XY, nside, 3);
return {
type: "Feature",
geometry: {
type: "LineString",
coordinates: boundary.map(ang => {
const lon = rad2deg(ang.lon),
= rad2deg(ang.lat);
lat return [180 - lon, lat];
}),
}properties: {
"XY": XY,
"value": value,
};
};
})
const extent = d3.extent(features, d => d.properties.value),
= d3.scaleLinear().domain(extent).range(["#fff", "#1f77b4"]);
color
let projection = getProjection();
.append("path")
svg.datum(d3.geoGraticule().outline)
.attr("class", "graticule")
.style("fill", color(extent[0]))
.style("stroke", "black")
.style("stroke-width", 3)
.attr("d", d3.geoPath(projection));
.selectAll(".healpix")
svg.data(features, d => d.properties.XY)
.join("path")
.attr("d", d3.geoPath(projection))
.style("stroke", "gray")
.style("fill", d => color(d.properties.value));
function projectionTween(projection0, projection1) {
return function(d) {
let t = 0;
function project(lon, lat) {
= rad2deg(lon);
lon = rad2deg(lat);
lat
const p0 = projection0([lon, lat]),
= projection1([lon, lat]);
p1
return [(1 - t) * p0[0] + t * p1[0], (1 - t) * -p0[1] + t * -p1[1]];
}
const projection = d3.geoProjection(project)
.scale(1)
.translate([fullWidth / 2, fullHeight / 2]);
const path = d3.geoPath(projection);
return function(_) {
= _;
t return path(d);
;
};
}
}
const delay = 2000;
function tick() {
.transition()
svg.duration(delay)
.each(function() {
.select(this).selectAll("path")
d3.attr("stroke-opacity", 1)
.transition()
.delay(2 / 6 * delay)
.duration(1 / 6 * delay)
.attr("stroke-opacity", 0)
.transition()
.duration(2 / 6 * delay)
.attrTween("d", projectionTween(
, projection = getProjection()))
projection.transition()
.duration(1 / 6 * delay)
.attr("stroke-opacity", 1)
.transition();
;
})
}
const timer = d3.interval(tick, delay);
.then(() => {
invalidation.stop();
timer;
})
return window.node();
}
Example of a HealPix grid, mapped using different projections.