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();
    options.unshift(option);
    return option.projection;
  }

  const nside = 6;

  const counts = {};
  stars.forEach(star => {
    const XY = HealPix.ang2XY(star, nside);
    counts[XY] = (counts[XY] || 0) + 1;
  });

  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),
                lat = rad2deg(ang.lat);
          return [180 - lon, lat];
        })
      },
      properties: {
        "XY": XY,
        "value": value,
      }
    };
  });

  const extent = d3.extent(features, d => d.properties.value),
        color = d3.scaleLinear().domain(extent).range(["#fff", "#1f77b4"]);

  let projection = getProjection();

  svg.append("path")
    .datum(d3.geoGraticule().outline)
    .attr("class", "graticule")
    .style("fill", color(extent[0]))
    .style("stroke", "black")
    .style("stroke-width", 3)
    .attr("d", d3.geoPath(projection));

  svg.selectAll(".healpix")
    .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) {
        lon = rad2deg(lon);
        lat = rad2deg(lat);

        const p0 = projection0([lon, lat]),
              p1 = projection1([lon, lat]);

        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() {
    svg.transition()
      .duration(delay)
      .each(function() {
        d3.select(this).selectAll("path")
          .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, projection = getProjection()))
          .transition()
          .duration(1 / 6 * delay)
          .attr("stroke-opacity", 1)
          .transition();
      });
  }

  const timer = d3.interval(tick, delay);

  invalidation.then(() => {
    timer.stop();
  });

  return window.node();
}

Example of a HealPix grid, mapped using different projections.

Resources

Tools