做数据可视化,地图是最难啃的骨头。很多人用 d3.js 画地图,结果要么比例失调,要么数据对不上号。这篇文不讲虚的,直接说怎么解决 d3.js geo scale 转换时的坐标偏移和缩放难题。看完你至少能避开我踩过的三个大坑。
先说背景。我最近接了个活儿,要给某省做人口密度热力图。用的是 D3 v7。数据是标准的 GeoJSON,没问题。但一渲染,好家伙,地图缩成一团,或者拉伸得不成样子。数据点全挤在左上角,根本没法看。
这其实就是 d3.js geo scale 没搞对。很多人以为只要有个投影就行,其实投影只是第一步。scale 才是控制地图在屏幕上的大小和位置的关键。
我之前的错误做法。是直接给 projection 设置一个固定的 scale 值。比如 500。看着挺大,但不同地区的 GeoJSON 边界框差异巨大。有的地区跨度大,有的小。固定值根本适配不了所有情况。
后来我查了文档,发现有个更聪明的办法。利用 projection 的 bounds 方法。这个方法能算出地理边界在投影后的像素边界。
具体怎么操作呢?先看代码逻辑。
第一步,加载 GeoJSON 数据。别用 fetch 直接塞进去,最好用 topojson 转换一下,减少数据量。
第二步,初始化投影。比如 d3.geoMercator()。这时候 scale 还是默认的 150。
第三步,关键来了。计算边界。
var bounds = projection.bounds(geojson);
var h = height;
var w = width;
var scale = 0.9 / Math.max(
(bounds[1][0] - bounds[0][0]) / w,
(bounds[1][1] - bounds[0][1]) / h
);
这段代码看着有点绕。其实就是算出地图在 X 轴和 Y 轴上的跨度。然后取最大值,用屏幕尺寸除以它,再乘个 0.9 的系数。0.9 是为了留点边距,不然地图贴边太紧,看着难受。
算出 scale 后,再调用 projection.scale(scale)。
这时候地图大小就自适应了。不管你的 GeoJSON 是全省的还是全市的,都能完美填充容器。
但是,光有 scale 还不够。位置也要对。
很多人忘了设置 center。如果 center 不对,地图就算大小合适,也可能偏到屏幕外面去。
怎么算 center?还是用 bounds。
var center = [
(bounds[0][0] + bounds[1][0]) / 2,
(bounds[0][1] + bounds[1][1]) / 2
];
projection.center(center);
这样,地图就既居中,又大小合适。这就是 d3.js geo scale 的核心用法。
我有个真实案例。之前给客户做全国地图。数据量很大,包含了几百个县级单位。一开始用固定 scale 1000。结果西部省份缩成小黑点,东部省份溢出屏幕。客户骂得狗血淋头。
后来我用了上面的动态计算法。每次加载新的 GeoJSON,都重新计算 bounds 和 scale。问题瞬间解决。地图在移动端和桌面端都能完美展示。
这里有个小细节要注意。如果你的 GeoJSON 包含多个不相连的区域,比如群岛。bounds 可能会算出很大的空隙,导致地图被拉得很大。
这时候,你可以只取主要陆地区域的 bounds。或者手动调整 scale 系数。别死板地完全依赖自动计算。
还有一点,projection 的精度问题。有时候你会发现,缩放后边缘有锯齿。这是因为 canvas 渲染的精度限制。
解决办法是,在创建 canvas 时,设置 devicePixelRatio。
var canvas = document.createElement('canvas');
canvas.width = width * window.devicePixelRatio;
canvas.height = height * window.devicePixelRatio;
var ctx = canvas.getContext('2d');
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
这样画出来的地图边缘会清晰很多。虽然代码多了几行,但效果提升明显。
总结一下。用 d3.js geo scale 做地图,核心就是动态计算。别用固定值。利用 bounds 算出合适的 scale 和 center。这样你的地图才能既美观又准确。
当然,实际操作中可能会遇到各种奇葩数据。比如坐标系统一的问题。确保你的 GeoJSON 是 WGS84 坐标系。如果不是,得先转换。否则一切白搭。
希望这些经验能帮到你。地图可视化确实坑多,但搞通了逻辑,也就那么回事。多试几次,你就能找到最适合你数据的参数。
记住,代码是死的,数据是活的。灵活调整 d3.js geo scale 的参数,才能做出让人眼前一亮的作品。别怕报错,报错是常态,解决报错才是本事。