Mapbox结合d3.js进行绘制

200次阅读
没有评论

介绍

Mapbox提供精美的矢量平铺地图。提供的Mapbox GL JS库具有一些不错的绘图功能,但是如果您需要进行高级绘图,则会很快遇到其局限性。

在这种情况下,d3.js具有几乎无限的自定义功能,可以为您提供帮助。

在本教程中,我将展示如何使两者一起工作。我们将创建上面的可视化效果,这是一张显示2018年所有5级或以上地震的地图。

您可以在Github存储库中找到本教程中使用的所有代码。

一个简单的地图

首先,请转到Mapbox并注册以获取访问令牌。

我们将从其简单的地图 示例作为基本模板开始。在HTML主体中,您可以使用mapbox令牌创建一个精美的空地图。

<div id="map"></div>
<script>
  mapboxgl.accessToken = "YOUR_TOKEN";
  var map = new mapboxgl.Map({
    container: "map",
    style: "mapbox://styles/mapbox/streets-v9",
    center: [-74.5, 40.0],
    zoom: 9
  });
</script>

添加d3 svg

接下来,我们添加通常的d3 svg元素,在其上绘制图形。我们将svg附加到mapbox画布上:

var container = map.getCanvasContainer();
var svg = d3
  .select(container)
  .append("svg")
  .attr("width", "100%")
  .attr("height", "500")
  .style("position", "absolute")
  .style("z-index", 2);

您可能会注意到我正在设置z-index。这是由于我遇到的那些小的隐藏陷阱之一:默认情况下,mapbox磁贴将放置在d3 svg画布的顶部,隐藏所有漂亮的图形(并且使您浪费大量时间试图找出问题所在)在控制台中没有错误消息)。

为避免这种情况,请z-index为svg指定css属性,然后对值较低的地图执行相同的操作(请注意,z-index 除非也设置了该position属性,否则将忽略):

#map {
  position: relative;
  z-index: 0;
  width: 100%;
  height: 500px;
}

另请注意,您的svg大小与地图相同。

让Mapbox和D3交互

使用d3和mapbox进行绘图的关键是使它们的坐标系相互对话。空间数据通常使用纬度/经度坐标,这是mapbox内部使用的坐标,但是d3必须知道画布上的哪个像素对应于哪个空间坐标。

使用以下投影功能可以完成此操作:

function project(d) {
  return map.project(new mapboxgl.LngLat(d[0], d[1]));
}

地图上的点

让我们在地图上获得一些d3对象!

首先,我们添加一些数据,添加圆圈并绑定数据。

var data = [[-74.5, 40.05], [-74.45, 40.0], [-74.55, 40.0]];

var dots = svg
  .selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("r", 20)
  .style("fill", "ff0000");

我们尚未设置位置-该投影方法发光的时候了。

在以下渲染方法中,我们将圆的数据传递给投影方法,获取与空间坐标相对应的画布坐标,并将其设置为圆的位置:

function render() {
  dots
    .attr("cx", function(d) {
      return project(d).x;
    })
    .attr("cy", function(d) {
      return project(d).y;
    });
}

我将其封装在一个render方法中,因为每次地图移动(通过拖动或缩放)时,我们都必须重新绘制圆的位置,我们这样做是这样的:

map.on("viewreset", render);
map.on("move", render);
map.on("moveend", render);
render(); // Call once to render

而已!这是连接d3和mapboxgl的基本模式,可用于绘制所有d3对象。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Earthquakes map</title>
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.js"></script>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <link
      href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css"
      rel="stylesheet"
    />
    <style>
      body {
        margin: 0;
        padding: 0;
      }
      #map {
        position: absolute;
        top: 0;
        bottom: 0;
        width: 100%;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      mapboxgl.accessToken = "INSERT_YOUR_TOKEN";

      var map;

      const data = d3
        .json(
          "https://gist.githubusercontent.com/franksh/0a1dcf63a0976c78c1f7869a5abf9775/raw/c256896524926042d6d7091154c7523758d0480c/earthquakes.json",
          function(d) {
            return d;
          }
        )
        .then(createMap)
        .then(createDots);

      function createMap(data) {
        map = new mapboxgl.Map({
          container: "map", // container id
          style: "mapbox://styles/mapbox/light-v9", // stylesheet location
          zoom: 1 // starting zoom
        });

        map.on("viewreset", render);
        map.on("move", render);
        map.on("moveend", render);

        // Optional: Modify map with d3
        d3.selectAll(".mapboxgl-canvas")
          .style("opacity", 1)
          .style("position", "absolute")
          .style("z-index", 1);
        return data;
      }

      function createDots(data) {
        var container = map.getCanvasContainer();

        var svg = d3
          .select(container)
          .append("svg")
          .attr("width", "100%")
          .attr("height", "2000")
          // Ensure d3 layer in front of map
          .style("position", "absolute")
          .style("z-index", 10);

        let dots = svg
          .selectAll("circle")
          .data(data.features)
          .enter()
          .append("circle")
          .attr("class", "circle")
          .attr("r", 5)
          .style("opacity", 0.7)
          .style("fill", "#ff3636");

        render();
      }

      // Projection method:
      // Project geojson coordinate to the map's current state
      function project(d) {
        return map.project(new mapboxgl.LngLat(d[0], d[1]));
      }

      // Render method redraws lines
      function render() {
        d3.selectAll(".circle")
          .attr("cx", function(d) {
            return project(d.geometry.coordinates).x;
          })
          .attr("cy", function(d) {
            return project(d.geometry.coordinates).y;
          });
      }
    </script>
  </body>
</html>

Mapbox结合d3.js进行绘制