D3 Zoom的translateExtent无法按预期工作

时间:2019-08-23 12:29:39

标签: javascript d3.js zoom

我正在尝试使用d3 tiled3 zoom创建图块地图。我已经将zoom.extentzoom.translateExtent都设置为[[0,0], [width,height]]。但是当我缩放时,地图会跳到左上角。

这是示例代码

const tile = d3.tile()
    .extent([[0, 0], [width, height]])
    .tileSize(512);

const zoom = d3.zoom()       
    .translateExtent([[0, 0], [width, height]]) //  issue in this line
    .extent([[0, 0], [width, height]])
    .scaleExtent([1 << 10, 1 << 15])
    .on("zoom", () => zoomed(d3.event.transform));

// applied zoom like this
svg
    .call(zoom)
    .call(zoom.transform, d3.zoomIdentity
    .translate(width / 2, height / 2)
    .scale(-initialScale)
    .translate(...projection(initialCenter))
    .scale(-1));

function zoomed(transform) {
    projection
        .scale(transform.k / (2 * Math.PI))
        .translate([transform.x, transform.y]);
}

这里是complete demo in Observable

1 个答案:

答案 0 :(得分:1)

这里的主要挑战是了解您要约束的内容。投影的原始比例为1 /(2π):将世界投影到一个像素一个像素(每个像素2π或每个像素360度)的空间。这样可以更轻松地与d3-zoom集成,但是肯定不直观。

D3缩放用于修改投影,其缩放值沿1<<n行。这表示应用缩放后的世界宽度。如果基本投影对世界使用1像素的宽度,则缩放会将其拉伸到1<<n像素上。 1 << 10例如是1024。

现在,棘手的部分是缩放缩放范围以1的缩放比例应用。因此,即使给定了缩放范围,我们也需要将其应用于世界,就好像它在1x1像素框中显示一样。较小的比例范围值1024(1 << 10),我们实际上从未显示世界占据了一个像素。

因此,我们的翻译范围应位于以两个角为边界的框内:[-1,-1]和[1,1]。这有助于解释Cornel的评论。但是我们可以通过编程方式做到这一点吗?是的。

这就是让我为这个库疯狂的原因:缩放范围不是以简单的方式(例如地理坐标或屏幕坐标)注册的,而是相对于1x1像素世界的缩放单位,从而引入了新的坐标系跟踪可能在后台跟踪的内容。这当然不是很直观,并且一定程度上驱使我把这个模块抛在脑后(解释我的评论中的链接,d3-slippy仍处于试验阶段,但应该显示可以完成的操作-顺便说一句,该链接中的约束方法仅在您将所有内容都转换为它)。

因此,让我们以编程方式获取屏幕的缩放转换范围,如图所示。

首先,调用缩放并应用初始变换后,投影将更新以反映新的缩放级别并进行转换,因此我们可以使用projection.invert()来获取左上角和左下角的地理坐标在屏幕的右侧。

第二,我们可以(重新)为一个像素世界创建一个投影,并通过它推入左上角和右下角的地理坐标,以在一个像素世界中获得它们的投影坐标。

第三,我们在一个1像素的世界中获取左上角和右下角的投影坐标,然后将它们传递给zoom.translateExtent:

   // Call zoom, same as before:
   svg
      .call(zoom)
      .call(zoom.transform, d3.zoomIdentity
          .translate(width / 2, height / 2)
          .scale(-initialScale)
          .translate(...projection(initialCenter))
          .scale(-1));

  // Now we can work on setting the zoom translate extent:
  // Zooomed projected limits of screen in lat long:
  var top = projection.invert([0,0])[1];
  var left = projection.invert([0,0])[0];
  var bottom = projection.invert([width,height])[1];
  var right = projection.invert([width,height])[0];

  // original projection:
  var baseProjection = d3.geoMercator()
    .translate([0,0])
    .scale(1/Math.PI/2);

  // Projected limits of screen in projected unzoomed coordinates:
  var topLeft = baseProjection([left,top])
  var bottomRight = baseProjection([right,bottom])

  // Set the zoom translate extent:
  zoom.translateExtent([topLeft,bottomRight])