deck.glとD3.jsを組み合わせて、地図上にボロノイ図を描画する

概要

deck.glを使ってWebGLで描画した地図の上に、D3.jsを使ってsvgをオーバーレイしボロノイ図を描画します。

ソースコード

解説

deck.glでは、緯度経度と画面上の座標の投影変換をViewportクラスが担っています。
Viewportクラスは通常外部から利用できるような形でインタンス化されていませんが、サブクラスであるWebMercatorViewportを利用することで、投影変換を行うためのprojectメソッドを外部から利用することができます。

WebMercatorViewport Class

projectメソッドを使って母点の画面上の座標を取得し、それらを基にd3-voronoiモジュールを用いてボロノイ図を描画するための座標を算出しています。
あとは、webGL上にオーバーレイしたSVG要素にPathエレメントを設置してボロノイを描画しています。

import React, { useRef, useEffect } from "react";
import { voronoi } from "d3-voronoi";
import { select } from "d3-selection";

export default props => {
  const { viewport, data } = props;

  if (!data.map) return null;
  const width = viewport.width;
  const height = viewport.height;
  //WebMercatorViewportのprojectメソッドを使って緯度経度を画面上の座標に変換する
  const point = data.map(d => viewport.project(d.position));
  //取得した画面上の座標を用いてボロノイ図の描画に必要な座標を計算する
  const vor = voronoi().extent([[0, 0], [width, height]]);
  const polygons = vor(point).polygons();

  //pathを描画する親要素を取得
  const pathGroupEl = useRef(null);
  const update = React.useRef();

  //ボロノイをpathエレメントを使って描画
  update.current =  () => {
    const selected = select(pathGroupEl.current)
      .selectAll(".cell")
      .data(polygons);

    const enter = selected
      .enter()
      .append("path")
      .attr("class", "cell")
      .attr("fill", "none")
      .attr("stroke", "black");

    selected.merge(enter).attr("d", d => {
      if (!d || d.length < 1) return null;
      return M${d.join("L")}Z;
    });
  };

  //polygonsのデータに変化があったらボロノイを再描画する
  useEffect(() => {
    update.current();enderVoronoi();
  }, [polygons]);

  return (
    <svg viewBox={0 0 ${viewport.width} ${viewport.height}}>
      <g ref={pathGroupEl} />
    </svg>
  );
};

おまけ

今回はsvgを使ってボロノイを描画しましたが、d3-voronoiの計算結果をdeck.glのPathLayerに渡してwebGL上に描画する方がスマートかもしれません。