预投影几何体 v 让浏览器来做(又名效率 v 灵活性)

Pre-projected geometry v getting the browser to do it (aka efficiency v flexibility)

为了提高在线地图的性能,尤其是在智能手机上,我听从了 Mike Bostock 的建议,在将地理数据上传到服务器之前尽可能多地准备地理数据(根据他的 command-line cartography)。例如,我通常通过 d3.geoConicEqualArea() 在命令行投影 TopoJSON 数据,而不是让查看者的浏览器在加载地图时执行这项繁重的工作。

但是,我还想动态地使用.scale.fitSize.fitExtent.translate等方法,这意味着我不能"bake"预先将值缩放或转换为 TopoJSON 文件。

Bostock recommends 使用 d3.geoTransform() 作为 d3.geoConicEqualArea() 等投影的代理,如果您正在处理已经投影的数据但仍想缩放或转换它。例如,要翻转 y 轴上的投影,他建议:

var reflectY = d3.geoTransform({
      point: function(x, y) {
        this.stream.point(x, -y);
      }
    }),

    path = d3.geoPath()
        .projection(reflectY);

我的问题:如果我使用这个D3功能,我不是仍然强迫浏览器的浏览器做大量的数据处理,这会降低性能吗?预处理数据的目的是避免这种情况。还是我高估了上面d3.geoTransform()函数涉及的处理工作?

If I use this D3 function, aren't I still forcing the viewer's browser to do a lot of data processing, which will worsen the performance? The point of pre-processing the data is to avoid this. Or am I overestimating the processing work involved in the d3.geoTransform() function above?

简短回答:您高估了转换投影数据所需的工作量。


D3 geoProjections 的球形性质

d3 geoProjection比较独特。许多平台、工具或库采用由纬度和经度对组成的点,并将它们视为笛卡尔平面上的点。这在很大程度上简化了数学运算,但要付出代价:路径遵循笛卡尔路由。

D3 将经纬度点按原样处理:三维椭球体上的点。这在计算上花费更多,但提供其他好处 - 例如沿大圆路线路由路径段。

d3 将坐标视为 3d 地球上的点所产生的额外计算成本是:

  1. 球形数学

看一下简单的地理投影缩放、居中等之前:

function mercator(x, y) {
  return [x, Math.log(Math.tan(Math.PI / 4 + y / 2))];
}

这可能比您在上面建议的转换花费的时间更长。

  1. 路径

在笛卡尔平面上,两点之间的直线很容易,在球面上,这很困难。画一条从东经 179 度到西经 179 度的线——将它们视为在笛卡尔平面上很容易——在地球上画一条线。在球形地球上,直线穿过反子午线。

因此,在拉平路径时,需要沿路径采样,点与点之间的大圆距离需要弯曲,因此额外points.I对d3中的这个过程不确定,但肯定发生。

笛卡尔平面上的点不需要额外的采样 - 它们已经是平坦的,点之间的线是直的。不需要检测线是否以另一种方式环绕地球。

操作post投影

一旦投影,.fitSize 之类的东西将强制进行额外的工作,这基本上就是您使用 d3.geoTransform() 提出的建议:需要根据其 投影对特征进行转换和缩放位置和大小

这在 d3v3 中非常明显(在有 fitSize() 之前)当自动居中功能时:计算涉及投影功能的 svg 范围。


基本准科学性能比较

使用美国人口普查局shapefile,我创建了三个geojson文件:

  • 一个使用 WGS84 (long/lat)(文件大小:389 kb)
  • 一个在节点中使用带有普通 d3.geoAlbers 转换的 geoproject(文件大小:386 kb)
  • 一个在节点中使用 geoproject d3.geoAlbers().fitSize([500,500],d)(文件大小 385 kb)

速度的黄金标准应该是选项3,数据根据预期的显示范围缩放和居中,这里不需要变换,我将使用空投影来测试它

我使用以下方法将这些投影到 500x500 svg:

//  For the unprojected data
var projection = d3.geoAlbers()
 .fitSize([500,500],wgs84);

var geoPath = d3.geoPath().projection(projection)


// for the projected but unscaled and uncentered data  
var transform = d3.geoIdentity()
   .fitSize([500,500],albers);

  var projectedPath = d3.geoPath()
    .projection(transform);

// for the projected, centered, and scaled data
var nullProjection = d3.geoPath()

运行 这几百次,我得到的平均渲染时间(数据已预加载)为:

  • 71 毫秒:WGS84
  • 33 毫秒:投影但未缩放且未居中
  • 21 毫秒:投影、缩放和居中

我可以肯定地说,无论数据是否真正居中和缩放,预投影数据都会带来显着的性能提升。

注意,我使用 d3.geoIdentity() 而不是 d3.geoTransform(),因为它允许使用 fitSize(),如果需要,您可以在 y 上反映:.reflectY(true);