- 数据分析
【免费下载链接】turf
A modular geospatial engine written in JavaScript and TypeScript
导读
@turf/collect是 Turf 地理引擎中的一个空间聚合模块,它解决的是一个非常典型且高频的地理数据分析问题:统计落在每个多边形区域内的点,并把点的某个属性值批量收集到该多边形的属性中。例如把每个小区(多边形)内所有居民点(点)的population(人口)属性收集成一个数组,挂到小区的values属性上。阅读本文后,你将掌握collect的完整参数用法、可运行的示例代码、R-tree 空间索引加"粗筛 + 精确判定"两阶段过滤的底层实现原理,以及它的边界行为与测试验证方式。
一、模块定位:点 → 多边形的属性归并
collect属于 Turf 的"空间统计/聚合"类工具,其核心职责可以用一句话概括:读取一张点要素集合(FeatureCollection of Point)的指定属性,将其按空间归属关系汇总到一张多边形要素集合(FeatureCollection of Polygon)上。
它的工作流程是:
- 给定点上的一个输入属性
inProperty(例如人口population); - 对每个多边形,找出落在它内部的全部点;
- 把这些点的
inProperty取值收集成一个数组; - 将该数组写入多边形的
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>| 参数 | 类型 | 说明 |
|---|---|---|
polygons | FeatureCollection<Polygon> | 承载聚合结果的多边形要素集合,是聚合的目标与返回载体 |
points | FeatureCollection<Point> | 被聚合的点要素集合,属性值来源 |
inProperty | string | 从点上读取的嵌套来源属性名("from") |
outProperty | string | 写入多边形上的目标属性名("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 的断言,可以确认以下行为细节:
- 输入输出多边形数量保持一致:
t.equal(polyFC.features.length, aggregated.features.length)——聚合不会增删多边形要素,仅修改属性; - 每个多边形都会被写入
outProperty数组:即使某个多边形内部没有任何点,其outProperty也不会缺失,而是被赋值为空数组[](测试中poly3位于[100,0]附近、不包含任何点,最终properties.values === [])。这在后续渲染、统计时非常友好,无需再判空; - 缺少属性的点会得到
undefined值:由于源码使用item.properties?.[inProperty],若点的inProperty不存在,undefined会被推入结果数组。需要保证数据整洁时应先清洗点数据; - 原地修改:函数直接操作传入的
polygons对象并返回它(return polygons),没有做深拷贝。如果调用方需要保留原始多边形属性不变,应自行提前克隆(如使用@turf/clone)再传入; - 属性写入语义是"收集成数组"而非求和:
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
相关推荐
OpenSEO v0.0.4 版本深度解析:Lighthouse 迁移 DataForSEO、R2 缓存架构与托管版认证体系
OpenSEO v0.0.4 版本深度解析:Lighthouse 迁移 DataForSEO、R2 缓存架构与托管版认证体系 v0.0.4 是 OpenSEO
数据分析arduino-esp32 OpenThread 实战:CoAP SimpleGet 客户端——仅凭 Network Key 入网并向 Leader RLOC 发起确认式 GET
arduino esp32 OpenThread 实战:CoAP SimpleGet 客户端——仅凭 Network Key 入网并向 Leader RLOC
数据分析PyArrow 读写 Parquet 文件时如何只读取部分列并控制写入选项
PyArrow 读写 Parquet 文件时如何只读取部分列并控制写入选项 用 PyArrow 处理 Parquet 文件时,一个常见需求是:文件里有几十列,但
数据分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考