性能与大数据
Turf 是纯 JavaScript 的同步计算库,跑在主线程上。 数据量到十万级时,瓶颈不是算法复杂度,而是每次调用产生的对象分配与遍历。 优化方向很明确:减少调用次数、缩小数据范围、把计算移出主线程。
先看规模
| 规模 | 可以怎么做 | 注意 |
|---|---|---|
| 万次以内 | 主线程直接算 | 注意不要在 mousemove 里调用 |
| 一万到十万 | 包围盒预筛、批量 coordEach | 避免逐要素构造 Feature |
| 十万到百万 | Web Worker + 分块 | 主线程只负责渲染,不参与几何计算 |
| 百万以上 | 后端空间数据库、预计算瓦片 | 前端只取聚合后的结果 |
先用外包矩形缩小候选集
点面判断的成本主要在多边形顶点数。先用外包矩形筛掉明显不相交的点, 再用精确判断,通常能去掉 90% 以上的调用。
var zone = turf.polygon([/* 上百个顶点 */]);
var box = turf.bbox(zone); // [西, 南, 东, 北]
function inBox(lng, lat) {
return lng >= box[0] && lng <= box[2] &&
lat >= box[1] && lat <= box[3];
}
var candidates = [];
turf.coordEach(pointsFC, function (coord) {
if (inBox(coord[0], coord[1])) candidates.push(coord);
});
var hits = [];
for (var i = 0; i < candidates.length; i++) {
if (turf.booleanPointInPolygon(candidates[i], zone)) {
hits.push(candidates[i]);
}
}
console.log('候选', candidates.length, '命中', hits.length);
booleanPointInPolygon 接受坐标数组而不只是 Point 要素,
省掉包装对象。面的引用在整个循环里保持一致,避免重复解析。
把计算放进 Web Worker
Worker 里无法直接使用主线程的变量,但可以引入同一份 Turf。 主线程只发送坐标与参数,Worker 回传结果,渲染与计算彻底分开。
// main.js
var worker = new Worker('geo-worker.js');
worker.onmessage = function (e) {
if (e.data.type === 'buffer') {
map.getSource('zone').setData(e.data.result);
}
};
worker.postMessage({
type: 'buffer',
center: [116.391263, 39.907359],
radiusKm: 1.2
});
// geo-worker.js
importScripts('https://cdn.jsdelivr.net/npm/@turf/turf@7/turf.min.js');
self.onmessage = function (e) {
var msg = e.data;
if (msg.type !== 'buffer') return;
var pt = turf.point(msg.center);
var zone = turf.buffer(pt, msg.radiusKm, { units: 'kilometers' });
self.postMessage({ type: 'buffer', result: zone });
};
传大数据时要留意结构化克隆的成本:十万个点的 FeatureCollection 在两个线程之间复制一次可能需要上百毫秒。能用 TypedArray 传坐标数组, 就别传整份 GeoJSON。
分批处理,别一次占满一帧
必须在主线程算时,把任务切成小块,每块结束让出一帧。 这样地图的拖动与缩放不会被长时间阻塞。
function processInBatches(items, size, handle, done) {
var i = 0;
function step() {
var end = Math.min(i + size, items.length);
for (; i < end; i++) handle(items[i], i);
if (i < items.length) {
requestAnimationFrame(step);
} else {
done();
}
}
step();
}
processInBatches(samples, 500, function (pt) {
pt.properties.near = turf.pointToLineDistance(pt, route, {
units: 'kilometers'
});
}, function () {
console.log('全部完成');
});
mutate 与 clone 的取舍
默认情况下函数返回新对象,十万次调用就是十万份深拷贝。
确定不再需要原数据时,传 mutate: true 能显著减少分配。
| 做法 | 开销 | 风险 |
|---|---|---|
| 默认(不 mutate) | 每次深拷贝入参 | 内存峰值高,GC 频繁 |
mutate: true | 原地修改 | 原始数据被改写,无法回退 |
先 turf.clone 再 mutate | 一次拷贝 + 原地改 | 只适合需要保留一份副本时 |
注意事项
在渲染回调里调用 Turf
map.on('mousemove') 或动画循环里做 buffer、
intersect,会把帧率拖到个位数。这类计算按事件结束触发,
或加防抖到 100 毫秒以上。
用 clone 代替构造
需要给每个点补一个属性时,turf.clone(feature) 再改,
比新建 turf.point(coord, props) 慢。坐标本来就来自原数据,
直接构造新要素更省。
忽视结果的几何类型
大批量 intersect 中,有些返回 null、
有些返回 GeometryCollection。直接喂给渲染层会让整批失败。
批量处理时按 result && result.geometry 过滤。
经纬志(2026)。《性能与大数据》。https://www.jingweizhi.com/turf/tutorial/performance.html(访问日期 2026-11-03)。