预先投影的几何图形 v 让浏览器执行此操作(又名效率 v 灵活性)



为了提高我的在线地图的性能,特别是在智能手机上,我遵循Mike Bostock的建议,在将地理数据上传到服务器之前尽可能多地准备地理数据(根据他的命令行制图)。例如,我通常在命令行上投影 TopoJSON 数据,通常是通过d3.geoConicEqualArea(),而不是让查看器的浏览器在加载地图时完成这种繁重的工作。

但是,我还想动态使用.scale.fitSize.fitExtent.translate等方法,这意味着我无法事先"烘焙"比例或将值转换为 TopoJSON 文件。

Bostock 建议使用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()函数所涉及的处理工作?

如果我使用这个 D3 函数,我不是还在强制查看者的浏览器吗 做大量的数据处理,这会降低性能吗?这 对数据进行预处理的目的是避免这种情况。还是我 高估了 d3.geoTransform() 中涉及的处理工作 上面的功能?

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


D3地理投影的球面性质

d3 地理投影相对独特。许多平台、工具或库采用由纬度和经度对组成的点,并将它们视为在笛卡尔平面上。这在很大程度上简化了数学,但也有代价:路径遵循笛卡尔路由。

D3 将经度纬度点视为:三维椭球体上的点。这在计算上成本更高,但提供了其他好处 - 例如沿大圆路由路由路径段。

将坐标视为 3D 地球上的点时,d3 会产生额外的计算成本:

  1. 球面数学

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

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

这可能需要比您上面建议的转换更长的时间。

  1. Pathing

在笛卡尔平面上,两点之间的直线很容易,在球体上,这很难。取一条从东经179度延伸到西经179度的线——把它们当作在笛卡尔平面上一样,这很容易——在地球上画一条线。在球形地球上,这条线穿过反子午线。

因此,在平整路径时,需要沿路线进行采样,点之间的大圆距离需要弯曲,因此需要额外的点。我不确定 d3 中的这个过程,但它肯定会发生。

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

业务后预测

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

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


基本准科学性能比较

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

  • 一个使用 WGS84 (长/纬度) (文件大小: 389 kb)
  • 一个在节点中使用带有普通 d3.geoAlbers 转换的 geoproject (文件大小: 386 kb)
  • 一个在具有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);

最新更新