Mapbox GL JS + d3.js + svg オーバレイ + tooltip/popup

こんにちは。
[Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/) + d3.js + svg オーバレイ + transition + topojson データ読み込みの例を見つけました。

- [Ian Johnson’s Block 
  WWSD #11: Mapbox-gl + d3 SVG overlay](https://bl.ocks.org/enjalot/0d87f32a1ccb9a720d29ba74142ba365)

これを、tilt/rotation 対応へ小修正し、加えて tooltip/popup 機能を手動で作ってみました[^1]。

[^1]: なお```map.queryRenderedFeatures(e.point, { layers: [layer_name] })``` を使うやり方にも少し挑戦したけれど、まだ良く分かっていません。

<img width="486" alt="mapboxgl.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/54617/4d4b3e3b-36b6-9651-7aa2-b11d53d001ed.jpeg">

```mapboxgl_d3_svg.html
<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
  <script src="http://d3js.org/topojson.v1.min.js"></script>
  <script src="http://d3js.org/d3.v3.min.js"></script>
  <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.29.0/mapbox-gl.js'></script>
  <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.29.0/mapbox-gl.css' rel='stylesheet' />
  <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
    #map { 
      position:absolute; 
      width: 100%;
      height: 100%;
    }
    svg {
      position: absolute;
      width: 100%;
      height: 100%;
    }
    .tooltip {
      font-family: "Helvetica";
      font-size:12px;
      width:auto;
      height:auto;
      position:absolute;
      text-align:center;
      border-style:solid;
      border-width:1px;
      background-color: rgba(255,255,255,0.5);
      border-radius: 2px;    
    }
  </style>
</head>

<body>
  <div id="map"></div>
  <script>
    var mapCenter = [-0.1,51.5119112], mapZoom = 13.5;
//mapboxgl.accessToken = '<your access token here>';
    var map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/light-v9',
      center: mapCenter,
      zoom: mapZoom,
    })
    map.addControl(new mapboxgl.NavigationControl());

    function mapboxProjection(lonlat) {
      var p = map.project(new mapboxgl.LngLat(lonlat[0], lonlat[1]))
      return [p.x, p.y];
    }

    var container = map.getCanvasContainer()
    var svg = d3.select(container).append("svg")  
    var tooltipOffset = [-12, 24];
    var tooltip = d3.select("body")
                     .append("div")
                     .attr("class", "tooltip")

    var url = "http://enjalot.github.io/wwsd/data/UK/london_stations.topojson";
    d3.json(url, function(err, data) {
      var points = topojson.feature(data, data.objects.london_stations)
      var dots = svg.selectAll("circle.dot").data(points.features)
      var defaultFillColor = "#0082a3";
      
      dots.enter().append("circle").classed("dot", true)
      .attr("r", 1)
      .style({
        fill: defaultFillColor,
        "fill-opacity": 0.6,
        stroke: "#004d60",
        "stroke-width": 1
      })
      .transition().duration(1400)
      .attr("r", 8)

      function render() {
        dots
          .each(function(d) {d.geometry.proj = mapboxProjection(d.geometry.coordinates);})
          .attr("transform", function(d) { return "translate(" + d.geometry.proj + ")" })
      }

      dots
      .on("mouseover", function(d,i) {
        d3.select(this).style({fill: "#f04040"});
        tooltip
          .html(d.properties.lines.map(function(x){return x.name}).join(', '))
          .style("left",(d.geometry.proj[0]+tooltipOffset[0])+"px")
          .style("top",(d.geometry.proj[1]-tooltipOffset[1])+"px")
          .style("display", "inline");
      })
      .on("mouseout", function(d,i) {
        d3.select(this).style({fill: defaultFillColor});
        tooltip
          .style("display", "none");
      })

      map.on("viewreset", function() {
        render()
      })
      map.on("move", function() {
        render()
      })

      render()
    })

  </script>
</body>
```