做地图可视化这行,谁没被投影算法折磨过?尤其是刚上手D3的时候,觉得d3.geo.mercator简直是万能钥匙,两行代码就能把经纬度转成像素坐标,爽得飞起。但真到了生产环境,特别是处理高精度业务数据时,你会发现这玩意儿简直是“变形刺客”。
记得去年给某跨境电商做全球物流热力图,老板要求必须准确反映各地区的业务占比。我一开始图省事,直接上了d3.geo.mercator。结果导出PDF给客户看,客户指着格陵兰岛说:“这地方怎么比整个非洲还大?”我当时脸都绿了。虽然Mercator投影在航海图上很经典,但在展示面积占比这种业务场景下,它会把高纬度地区无限放大。格陵兰岛在Mercator投影下看起来比南美洲还大,但实际上南美洲面积是它的八倍多。这种视觉误导在商业汇报里是致命的,客户会觉得你很不专业,甚至怀疑数据造假。
后来我换了思路,用了d3.geo.albersUsa或者针对全球数据的d3.geo.conicEqualArea。但这中间有个大坑:默认的中心点和比例尺根本不对。很多新手包括以前的我,总是盯着经纬度转坐标那一步,却忘了设置投影的中心点。比如你要重点展示亚洲区域,却用默认的[0,0]做中心,那亚洲就被挤到屏幕边缘,留一大片空白海洋,用户体验极差。
我有个真实案例,之前帮一个做国际物流的朋友重构地图。他用的是标准的d3.geo.mercator,结果发现北欧和俄罗斯北部的数据点全都挤在一起,根本看不清细节。这是因为Mercator在极地附近的拉伸效应太严重了。我们最后调整了投影参数,把中心点设在东经105度,北纬35度左右,这是中国的大致中心,同时也兼顾了欧亚大陆的平衡。同时,我们手动计算了缩放比例,让地图在容器里自适应。
这里分享一个关键技巧:不要迷信默认值。在调用d3.geo.mercator()之后,一定要链式调用.center()和.scale()。比如:
var projection = d3.geo.mercator()
.center([105, 35])
.scale(800)
.translate([width / 2, height / 2]);
这个translate参数很多人会漏掉,导致地图偏到屏幕外面去。还有,scale的值不是固定的,它取决于你的SVG容器大小和数据覆盖范围。我一般先设个初值,比如800,然后根据实际渲染效果微调。有时候你会发现,明明代码没错,但地图就是很小,或者很大,这时候就要检查你的geojson数据是否完整,或者是否有空值导致投影计算出错。
另外,关于d3.geo.mercator的性能问题。如果你要渲染成千上万个数据点,比如全球航班轨迹,直接用path.projection()可能会卡顿。这时候可以考虑预计算坐标,或者使用WebGL加速。不过对于大多数中小规模的数据展示,d3.geo.mercator还是够用的,只要你不介意那些高纬度地区的形状失真。
最后想说,选投影算法没有绝对的好坏,只有适不适合。如果你的业务重点是展示地理位置的相对方向,比如航线、物流路径,d3.geo.mercator是个不错的选择,因为它能保持等角性质,航线在图上看起来是直线。但如果重点是展示面积、人口密度等业务指标,请务必换用等积投影。别为了省事而牺牲数据的准确性,毕竟在B端业务里,准确性比美观更重要。
希望这些踩坑经验能帮到你,少走弯路。毕竟,头发已经够少了,别再为投影算法掉头发了。