☰
@turf/collect 详解:使用 Turf 将点要素属性按多边形空间聚合
2026/9/25 2:49:52 网站建设 项目流程
  • 数据分析

【免费下载链接】turf

A modular geospatial engine written in JavaScript and TypeScript

项目地址:https://gitcode.com/gh_mirrors/tu/turf
点击查看免费下载

导读

@turf/collect是 Turf 地理引擎中的一个空间聚合模块,它解决的是一个非常典型且高频的地理数据分析问题:统计落在每个多边形区域内的点,并把点的某个属性值批量收集到该多边形的属性中。例如把每个小区(多边形)内所有居民点(点)的population(人口)属性收集成一个数组,挂到小区的values属性上。阅读本文后,你将掌握collect的完整参数用法、可运行的示例代码、R-tree 空间索引加"粗筛 + 精确判定"两阶段过滤的底层实现原理,以及它的边界行为与测试验证方式。

一、模块定位:点 → 多边形的属性归并

collect属于 Turf 的"空间统计/聚合"类工具,其核心职责可以用一句话概括:读取一张点要素集合(FeatureCollection of Point)的指定属性,将其按空间归属关系汇总到一张多边形要素集合(FeatureCollection of Polygon)上。

它的工作流程是:

  1. 给定点上的一个输入属性inProperty(例如人口population);
  2. 对每个多边形,找出落在它内部的全部点;
  3. 把这些点的inProperty取值收集成一个数组;
  4. 将该数组写入多边形的outProperty属性(例如values)。

其官方描述为:Merges a specified property from a FeatureCollection of points into a FeatureCollection of polygons(见 packages/turf-collect/README.md)。从仓库结构看,该模块在聚合类模块(turf-collect)中自成一体,并通过 packages/turf/index.ts 的export { collect } from "@turf/collect";统一导出到@turf/turf全家桶中。

二、安装方式

@turf/collect既可以作为独立模块安装,也可以通过@turf/turf全家桶统一引入:

# 单独安装该模块 $ npm install @turf/collect
# 安装包含全部模块的 @turf/turf $ npm install @turf/turf

安装后,独立模块以命名导出collect的方式使用(ESM 风格,"type": "module",见 packages/turf-collect/package.json):

import { collect } from "@turf/collect";

若安装了@turf/turf,则通过turf.collect(...)调用。

三、API 参数说明

collect的函数签名为:

collect( polygons: FeatureCollection<Polygon>, points: FeatureCollection<Point>, inProperty: string, outProperty: string ): FeatureCollection<Polygon>
参数类型说明
polygonsFeatureCollection<Polygon>承载聚合结果的多边形要素集合,是聚合的目标与返回载体
pointsFeatureCollection<Point>被聚合的点要素集合,属性值来源
inPropertystring从点上读取的嵌套来源属性名("from")
outPropertystring写入多边形上的目标属性名("into"),值为数组

返回值:FeatureCollection<Polygon>——即带有outProperty数组属性的多边形集合。需要注意的是,从源码实现看(见下文原理分析),该函数会原地修改传入的polygons对象并直接返回它,而不是深拷贝后返回新对象。

四、官方示例与运行结果

README 中给出了一个非常直观的示例:构造两个相邻多边形与 5 个带population属性的点,然后执行聚合:

var poly1 = turf.polygon([[[0,0],[10,0],[10,10],[0,10],[0,0]]]); var poly2 = turf.polygon([[[10,0],[20,10],[20,20],[20,0],[10,0]]]); var polyFC = turf.featureCollection([poly1, poly2]); var pt1 = turf.point([5,5], {population: 200}); var pt2 = turf.point([1,3], {population: 600}); var pt3 = turf.point([14,2], {population: 100}); var pt4 = turf.point([13,1], {population: 200}); var pt5 = turf.point([19,7], {population: 300}); var pointFC = turf.featureCollection([pt1, pt2, pt3, pt4, pt5]); var collected = turf.collect(polyFC, pointFC, 'population', 'values'); var values = collected.features[0].properties.values //=values => [200, 600]

结果解析:

  • poly1(左下角多边形)内部包含pt1(5,5)与pt2(1,3),因此collected.features[0].properties.values === [200, 600];
  • poly2(右侧多边形)内部包含pt3(14,2)、pt4(13,1)、pt5(19,7),对应值为[100, 200, 300];
  • 示例末尾的var addToMap = [pointFC, collected]表示可将原始点与聚合结果一起放入地图可视化(如 Leaflet / Mapbox 的 addToMap 习惯用法)。

使用 ESM + TypeScript 的等价写法

在仓库中同一数据场景以现代 ESM/TypeScript 风格运行的效果完全一致(示例可对照 packages/turf-collect/test.ts):

import { featureCollection, point, polygon } from "@turf/helpers"; import { collect } from "@turf/collect"; const poly1 = polygon([[[0,0],[10,0],[10,10],[0,10],[0,0]]]); const poly2 = polygon([[[10,0],[20,10],[20,20],[20,0],[10,0]]]); const polyFC = featureCollection([poly1, poly2]); const ptFC = featureCollection([ point([5,5], { population: 200 }), point([1,3], { population: 600 }), point([14,2], { population: 100 }), point([13,1], { population: 200 }), point([19,7], { population: 300 }), ]); const aggregated = collect(polyFC, ptFC, "population", "values"); console.log(aggregated.features[0].properties.values); // [200, 600] console.log(aggregated.features[1].properties.values); // [100, 200, 300]

五、源码级实现原理:R-tree 空间索引 + 两阶段过滤

collect之所以能高效处理"大量点 × 大量多边形"的聚合任务,是因为它在 packages/turf-collect/index.ts 中实现了一套经典的空间查询优化流程:

1. 构建点集 R-tree 空间索引

var rtree = new rbush<Entry>(6); var treeItems = points.features.map(function (item) { return { minX: item.geometry.coordinates[0], minY: item.geometry.coordinates[1], maxX: item.geometry.coordinates[0], maxY: item.geometry.coordinates[1], property: item.properties?.[inProperty], }; }); rtree.load(treeItems);
  • 每个点被包装为一个minX/minY/maxX/maxY重合的"退化包围盒"(点的包围盒就是它本身),并携带从item.properties中读取的inProperty取值;
  • rtree.load()一次性批量构建索引,而非逐条插入,性能更优;
  • 构造参数6是 rbush 的maxEntries(每个节点最大条目数),这是为减少树的深度/宽度而选择的常见平衡值;
  • 使用item.properties?.[inProperty]可选链:若某个点没有inProperty属性,其取值为undefined,该undefined仍会被写入结果数组(见第六节的边界行为)。

2. 多边形 bbox 粗筛

polygons.features.forEach(function (poly) { if (!poly.properties) { poly.properties = {}; } var bbox = turfbbox(poly); var potentialPoints = rtree.search({ minX: bbox[0], minY: bbox[1], maxX: bbox[2], maxY: bbox[3], }); ... });
  • 若多边形原本没有properties,先补一个空对象{},保证后续写入不出错;
  • 借助@turf/bbox(依赖声明见 packages/turf-collect/package.json)计算每个多边形的外接包围盒;
  • 用该 bbox 在 R-tree 上执行search,一次性拿到所有可能落在多边形内的候选点——这一阶段把"全量遍历"降级为"只扫包围盒内的点",是性能的关键。

3. 精确点包含判定与属性收集

var values: any[] = []; potentialPoints.forEach(function (pt) { if (booleanPointInPolygon([pt.minX, pt.minY], poly)) { values.push(pt.property); } }); poly.properties[outProperty] = values;
  • 包围盒"粗筛"的结果必然包含部分 bbox 相交但实际在多边形外的点(例如凹多边形、斜边边界的情况),因此必须用@turf/boolean-point-in-polygon做精确的"点在多边形内"判定(该判定模块的完整实现见 packages/turf-boolean-point-in-polygon/index.ts);
  • 只有判定为true的点,其property值才会被push进values;
  • 最后poly.properties[outProperty] = values完成属性写入。

从源码结构可以推断,该算法的时间复杂度大致为:O(点数)(建索引) + O(多边形数 × bbox 内候选点数)(查询与判定)。相比朴素的两重循环(多边形数 × 点数),在"点多、多边形也多"的场景下收益显著。模块依赖的三个核心库@turf/bbox、@turf/boolean-point-in-polygon、@turf/helpers以及空间索引库rbush都在 packages/turf-collect/package.json 的dependencies中明确声明,构建引用关系也体现在 packages/turf-collect/tsconfig.json 的references中。

六、边界行为与注意事项

结合 packages/turf-collect/test.ts 的断言,可以确认以下行为细节:

  1. 输入输出多边形数量保持一致:t.equal(polyFC.features.length, aggregated.features.length)——聚合不会增删多边形要素,仅修改属性;
  2. 每个多边形都会被写入outProperty数组:即使某个多边形内部没有任何点,其outProperty也不会缺失,而是被赋值为空数组[](测试中poly3位于[100,0]附近、不包含任何点,最终properties.values === [])。这在后续渲染、统计时非常友好,无需再判空;
  3. 缺少属性的点会得到undefined值:由于源码使用item.properties?.[inProperty],若点的inProperty不存在,undefined会被推入结果数组。需要保证数据整洁时应先清洗点数据;
  4. 原地修改:函数直接操作传入的polygons对象并返回它(return polygons),没有做深拷贝。如果调用方需要保留原始多边形属性不变,应自行提前克隆(如使用@turf/clone)再传入;
  5. 属性写入语义是"收集成数组"而非求和:collect只负责归并原始值。若要进一步得到sum、avg、max/min等统计量,需要在拿到values数组后自行聚合计算。

七、测试与基准验证

测试用例

tests 使用tape编写,覆盖了上述全部关键断言:

import { featureCollection, point, polygon } from "@turf/helpers"; import { collect } from "./index.js"; test("turf collect module", (t) => { // ...构造 3 个多边形与 5 个点 const aggregated = collect(polyFC, ptFC, "population", "values"); t.equal(polyFC.features.length, aggregated.features.length); // 数量不变 t.deepEqual(aggregated.features[0].properties.values, [200, 600]); // poly1 两个点 t.deepEqual(aggregated.features[1].properties.values, [100, 200, 300]); // poly2 三个点 t.deepEqual(aggregated.features[2].properties.values, []); // 空多边形得到 [] t.end(); });

运行测试:pnpm test(对应tsx test.ts),基准脚本运行:pnpm bench。

基准脚本

packages/turf-collect/bench.ts 使用benchmark构建了turf-collect的基准套件,对同样的两个多边形 + 5 个点数据反复执行collect(polyFC, ptFC, "population", "outPopulation"),输出吞吐率(ops/sec)供性能回归比对。它验证了在中小规模数据上collect的调用开销处于稳定、可接受的水平。

八、典型应用场景

  • 人口 / 统计数据分析:把普查点数据聚合到行政区块多边形,为每个区划生成人口数值数组;
  • POI 归类统计:将商铺、医院、学校等点要素按商圈/网格多边形归并,得到每个区域内的 POI 列表;
  • 传感器数据汇总:将监测点读数按覆盖区域(多边形)收集,供后续求均值、极值或绘制热力统计;
  • 空间 join 预处理:作为"点落区"关系的批量求解器,为下游的 buffer、dissolve、intersect 等复杂空间分析提供基础数据。

九、继续深入

  • 函数实现与 R-tree 细节:packages/turf-collect/index.ts
  • 完整测试断言:packages/turf-collect/test.ts
  • 性能基准:packages/turf-collect/bench.ts
  • 依赖与脚本声明:packages/turf-collect/package.json
  • 全家桶导出入口:packages/turf/index.ts
  • 精确点包含判定:packages/turf-boolean-point-in-polygon/index.ts
  • 包围盒计算:packages/turf-bbox/(依赖@turf/bbox)

如果你需要"按多边形求和/平均"而非收集原始数组,建议在collect结果之上自行对values做归约计算;如果你需要把结果用于地图渲染,可直接沿用示例中的addToMap = [pointFC, collected]约定,将原始点与聚合多边形一并叠加展示。

  • 数据分析

【免费下载链接】turf

A modular geospatial engine written in JavaScript and TypeScript

项目地址:https://gitcode.com/gh_mirrors/tu/turf
点击查看免费下载
上一篇:突破性能极限:Gunyah超虚拟器打造极致安全的实时虚拟化平台
下一篇:Fooocus 离线文生图怎么用:3 步启动,4GB 显存就能跑

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询