为了提高我的在线地图的性能,特别是在智能手机上,我遵循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 会产生额外的计算成本:
- 球面数学
在缩放、居中等之前,先看一下简单的地理投影:
function mercator(x, y) {
return [x, Math.log(Math.tan(Math.PI / 4 + y / 2))];
}
这可能需要比您上面建议的转换更长的时间。
- 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);