预投影几何体 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 地球上的点所产生的额外计算成本是:
- 球形数学
看一下简单的地理投影在缩放、居中等之前:
function mercator(x, y) {
return [x, Math.log(Math.tan(Math.PI / 4 + y / 2))];
}
这可能比您在上面建议的转换花费的时间更长。
- 路径
在笛卡尔平面上,两点之间的直线很容易,在球面上,这很困难。画一条从东经 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);
为了提高在线地图的性能,尤其是在智能手机上,我听从了 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 地球上的点所产生的额外计算成本是:
- 球形数学
看一下简单的地理投影在缩放、居中等之前:
function mercator(x, y) {
return [x, Math.log(Math.tan(Math.PI / 4 + y / 2))];
}
这可能比您在上面建议的转换花费的时间更长。
- 路径
在笛卡尔平面上,两点之间的直线很容易,在球面上,这很困难。画一条从东经 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);