预先投影的几何体v使浏览器执行此操作(又称效率v灵活性) [英] Pre-projected geometry v getting the browser to do it (aka efficiency v flexibility)

查看:98
本文介绍了预先投影的几何体v使浏览器执行此操作(又称效率v灵活性)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为提高在线地图(尤其是在智能手机上)的性能,我遵循Mike Bostock的建议,在将地理数据上传到服务器之前尽可能地对其进行准备(根据他的

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 geoProjections的球形性质

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

D3将经度纬度点视为它们:三维椭球上的点.这会增加计算成本,但会带来其他好处-例如沿着大圆弧路线选择路径段.

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

  1. 球形数学

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

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

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

  1. 路径

在笛卡尔平面上,两点之间的线很容易,在球面上,这很困难.画一条从东经179度延伸到西经179度的线-像对待它们一样在笛卡尔平面上轻松对待它们-在整个地球上画一条线.在球形地球上,线穿过反子午线.

因此,在展平路径时,需要沿路径进行采样,点之间的大圆距离需要弯曲,因此也需要附加点.我不确定在d3中进行此处理,但肯定会发生这种情况.

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

投影后的操作

一旦投影,诸如.fitSize之类的内容将强制执行其他工作,这些工作实际上就是您使用d3.geoTransform()建议的工作:功能需要根据其投影位置和大小进行转换和缩放>.

在自动定中心要素时,这在d3v3中非常明显(在fitSize()之前):计算涉及投影要素的svg范围.


基本准科学性能比较

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

  • 使用WGS84(长/宽)的文件(文件大小:389 kb)
  • 一个在节点上使用geoproject且具有简单d3.geoAlbers转换(文件大小:386 kb)的
  • 一个在d3.geoAlbers().fitSize([500,500],d)(文件大小385 kb)的节点中使用geoproject的人

速度的黄金标准应为选项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);

To improve the performance of my online maps, especially on smartphones, I'm following Mike Bostock's advice to prepare the geodata as much as possible before uploading it to the server (as per his command-line cartography). For example, I'm projecting the TopoJSON data, usually via d3.geoConicEqualArea(), at the command line rather than making the viewer's browser do this grunt work when loading the map.

However, I also want to use methods like .scale, .fitSize, .fitExtent and .translate dynamically, which means I can't "bake" the scale or translate values into the TopoJSON file beforehand.

Bostock recommends using d3.geoTransform() as a proxy for projections like d3.geoConicEqualArea() if you're working with already-projected data but still want to scale or translate it. For example, to flip a projection on the y-axis, he suggests:

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

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

My question: 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?

解决方案

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?

Short Answer: You are overestimating the amount of work required to transform projected data.


Spherical Nature of D3 geoProjections

A d3 geoProjection is relatively unique. Many platforms, tools, or libraries take points consisting of latitude and longitude pairs and treat them as though they are on a Cartesian plane. This simplifies the math to a huge extent, but comes at a cost: paths follow Cartesian routing.

D3 treats longitude latitude points as what they are: points on a three dimensional ellipsoid. This costs more computationally but provides other benefits - such as routing path segments along great circle routes.

The extra computational costs d3 incurs in treating coordinates as points on a 3d globe are:

  1. Spherical Math

Take a look at a simple geographic projection before scaling, centering, etc:

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

This is likely to take longer than the transform you propose above.

  1. Pathing

On a Cartesian plane, lines between two points are easy, on a sphere, this is difficult. Take a line stretching from 179 degrees East to 179 degrees West - treating these as though they were on a Cartesian plane that is easy - draw a line across the earth. On a spherical earth, the line crosses the anti-meridian.

Consequently, in flattening the paths, sampling is required along the route, great circle distance between points requires bends, and therefore additional points.I'm not certain on the process of this in d3, but it certainly occurs.

Points on a cartesian plane don't require additional sampling - they are already flat, lines between points are straight. There is no need to detect if lines wrap around the earth another way.

Operations post Projection

Once projected, something like .fitSize will force additional work that is essentially what you are proposing with the d3.geoTransform(): the features need to be transformed and scaled based on their projected location and size.

This is very visible in d3v3 (before there was fitSize()) when autocentering features: calculations involve the svg extent of the projected features.


Basic Quasi Scientific Performance Comparison

Using a US census bureau shapefile of the United States, I created three geojson files:

  • One using WGS84 (long/lat) (file size: 389 kb)
  • One using geoproject in node with a plain d3.geoAlbers transform (file size: 386 kb)
  • One using geoproject in node with d3.geoAlbers().fitSize([500,500],d) (file size 385 kb)

The gold standard of speed should be option 3, the data is scaled and centered based on an anticipated display extent, no transform is required here and I will use a null projection to test it

I proceeded to project these to a 500x500 svg using:

//  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()

Running this a few hundred times, I got average rendering times (data was preloaded) of:

  • 71 ms: WGS84
  • 33 ms: Projected but unscaled and uncentered
  • 21 ms: Projected, scaled, and centered

I feel safe in saying there is a significant performance bump in pre-projecting the data, regardless of if it is actually centered and scaled.

Note I used d3.geoIdentity() as opposed to d3.geoTransform() as it allows the use of fitSize(), and you can reflect if needed on the y: .reflectY(true);

这篇关于预先投影的几何体v使浏览器执行此操作(又称效率v灵活性)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆