- 数据分析
【免费下载链接】turf
A modular geospatial engine written in JavaScript and TypeScript
导读
@turf/rectangle-grid是 Turf 模块化地理空间引擎(项目仓库)中的网格生成模块,用于在指定的地理包围盒(bbox)内生成一组宽度、高度在角度(degrees)维度上保持一致的矩形多边形网格。本文以 packages/turf-rectangle-grid/README.md 为骨架,结合模块源码 index.ts、单元测试 test.ts 与基准测试 bench.ts,系统讲解rectangleGrid的完整 API、单位换算原理、居中算法、掩膜裁剪以及测试与性能表现,帮助你掌握在 Turf 生态中快速生成规则矩形网格的实战能力。
一、模块概览与安装
@turf/rectangle-grid是 Turf 众多模块中专门负责"规则矩形网格"生成的模块。从 package.json 可以看到,它的描述是 "Creates a grid of rectangular polygons with width and height consistent in degrees",关键词为grid、regular、cartesian,当前版本为7.4.0,采用 ESM 模块规范("type": "module"),运行环境要求 Node.js >= 22。
安装有两种方式:
单独安装本模块:
$ npm install @turf/rectangle-grid或安装包含全部模块的聚合包@turf/turf,这样所有模块都会以函数形式挂载到turf对象上:
$ npm install @turf/turf从 package.json 的dependencies可以看出,本模块底层依赖三个 Turf 兄弟模块:@turf/boolean-intersects(用于掩膜相交判定)、@turf/distance与@turf/helpers(提供convertLength、featureCollection、polygon等工具函数),这为后续源码分析提供了线索。
二、rectangleGrid API 详解
rectangleGrid函数接受一个 bbox、单元格宽度、单元格高度以及可选参数,返回一个FeatureCollection<Polygon>类型的多边形网格。
2.1 参数表
| 参数 | 类型 | 说明 |
|---|---|---|
bbox | BBox | 网格范围,按[minX, minY, maxX, maxY]顺序给出;若网格无法完美填满 bbox,结果会居中 |
cellWidth | number | 每个单元格的宽度,单位由options.units指定 |
cellHeight | number | 每个单元格的高度,单位由options.units指定 |
options | Object | 可选参数,默认{} |
options.units | Units | 单元格宽高的单位,支持 Turf 全部合法单位,默认'kilometers' |
options.mask | Feature<Polygon\|MultiPolygon> | 可选;传入 Polygon 或 MultiPolygon 时,只在掩膜内部生成网格多边形 |
options.properties | Object | 传递给网格中每个多边形的属性,默认{} |
返回值:FeatureCollection<Polygon>——一个包含若干矩形多边形的要素集合。
2.2 官方示例
原 README 给出的最小可运行示例(以英里为单位生成矩形网格):
var bbox = [-95, 30 ,-85, 40]; var cellWidth = 50; var cellHeight = 20; var options = {units: 'miles'}; var rectangleGrid = turf.rectangleGrid(bbox, cellWidth, cellHeight, options); //addToMap var addToMap = [rectangleGrid]在 ESM / TypeScript 环境下,也可以直接导入模块化函数:
import rectangleGrid from "@turf/rectangle-grid"; const bbox = [-95, 30, -85, 40]; const grid = rectangleGrid(bbox, 50, 20, { units: "miles" });2.3 options.units:支持的单位与"角度一致"的语义
options.units支持 Turf 定义的全部合法单位。完整的单位清单定义在 packages/turf-helpers/README_UNITS.md,包括:
meters/metresmillimeters/millimetrescentimeters/centimetreskilometers/kilometresmilesnauticalmilesinchesyardsfeetradiansdegrees
这里有一个关键语义需要特别注意(README 与源码注释都反复强调):如果你需要的是在线性单位(如千米)下宽度与高度相等的正方形网格,这个模块并不适合你。因为cellWidth与cellHeight会在内部从给定单位换算为度(degrees),所以最终生成的多边形,其宽高只在"度"这一角度度量上保持一致,而非在地球表面真实的线性距离上保持一致。这一点是使用rectangleGrid时最容易踩的坑。
三、源码级实现原理
3.1 调用链与依赖关系
从 index.ts 的导入语句可见,函数内部依赖@turf/helpers的convertLength、featureCollection、polygon以及@turf/boolean-intersects的booleanIntersects。核心流程为:单位换算 → 行列数计算 → 居中偏移 → 双循环生成多边形 → 掩膜过滤 → 打包成 FeatureCollection。
3.2 单位换算:convertLength
rectangleGrid将宽高统一换算为度:
const cellWidthDeg = convertLength(cellWidth, options.units, "degrees"); const cellHeightDeg = convertLength(cellHeight, options.units, "degrees");convertLength定义在 packages/turf-helpers/index.ts,其实现为:
export function convertLength( length: number, originalUnit: Units = "kilometers", finalUnit: Units = "kilometers" ): number { if (!(length >= 0)) { throw new Error("length must be a positive number"); } return radiansToLength(lengthToRadians(length, originalUnit), finalUnit); }它先将长度转为弧度,再转为目标单位,并且会校验length必须为非负数。这意味着传入负的cellWidth/cellHeight会直接抛出"length must be a positive number"错误。
3.3 行列计算与居中算法
const bboxWidth = east - west; const bboxHeight = north - south; const columns = Math.floor(Math.abs(bboxWidth) / cellWidthDeg); const rows = Math.floor(Math.abs(bboxHeight) / cellHeightDeg); // 若网格无法完美填满 bbox,将其居中 const deltaX = (bboxWidth - columns * cellWidthDeg) / 2; const deltaY = (bboxHeight - rows * cellHeightDeg) / 2; let currentX = west + deltaX; for (let column = 0; column < columns; column++) { let currentY = south + deltaY; for (let row = 0; row < rows; row++) { // 构造 [西,南,东,北] 对应的五坐标闭合环 const cellPoly = polygon( [ [ [currentX, currentY], [currentX, currentY + cellHeightDeg], [currentX + cellWidthDeg, currentY + cellHeightDeg], [currentX + cellWidthDeg, currentY], [currentX, currentY], ], ], options.properties ); ... currentY += cellHeightDeg; } currentX += cellWidthDeg; }可以看到实现的关键点:
- 行列数向下取整:
Math.floor保证了生成的网格不会超出 bbox 边界; - 居中策略:当
bboxWidth % cellWidthDeg !== 0时,剩余宽度的一半被作为deltaX偏移量加到起始 X 上,deltaY同理,使网格整体在 bbox 内居中,而不是从 bbox 的西南角平铺;这正是 README 中"If the grid does not fill the bbox perfectly, it is centered"的源码实现; - 单元格为闭合五坐标环:每个矩形由西南角起、按逆时针方向依次经过西北、东北、东南、再回到西南角共 5 个坐标构成闭合环(首尾坐标相同),符合 GeoJSON Polygon 的线性环闭合要求;
- 属性透传:
options.properties会作为第二个参数传入polygon(),因此每个生成的单元格都会携带相同的自定义属性。
3.4 掩膜(mask)过滤机制
当传入options.mask时,源码使用booleanIntersects做相交测试,只有与掩膜相交的单元格才被保留:
if (options.mask) { if (intersect(options.mask, cellPoly)) { results.push(cellPoly); } } else { results.push(cellPoly); }需要注意:这里采用的是**相交(intersects)而非包含(contains)**判定,即只要单元格与掩膜多边形有任意重叠就会被保留。因此当掩膜是复杂的不规则多边形(例如澳大利亚边界)时,边界处的单元格往往会被部分截断成不完整矩形,输出结果是"被掩膜裁剪过的网格集合"。
最后,所有保留的单元格通过featureCollection(results)打包返回。
四、实战案例:来自测试夹具的完整用法
模块的 test 目录 提供了 6 组真实测试夹具,覆盖了不同单位、不同 bbox 大小与掩膜场景,是理解 API 用法的第一手资料:
| 夹具文件 | bbox | cellWidth | cellHeight | units | 说明 |
|---|---|---|---|---|---|
| 10x10-1degree.json | 局部范围 | 10 | 10 | degrees | 以度为单位的等宽高网格 |
| victoria-20x100-km.json | [141, -39, 150, -34] | 20 | 100 | kilometers | 千米单位、宽高不对称网格 |
| fiji-10x5-miles.json | 斐济范围 | 10 | 5 | miles | 英里单位网格 |
| big-bbox-500x100-miles.json | [-220.78125, -80.647, -29.53125, 78.349] | 500 | 100 | miles | 跨越大范围的大 bbox |
| global-grid.json | [-180, -90, 180, 90] | 10 | 10 | degrees | 全球范围网格 |
| australia-mask.json | [110, 0, 160, -50] | 1 | 2 | degrees | 带澳大利亚多边形掩膜 |
例如使用维多利亚州范围的千米单位网格:
{ "bbox": [141, -39, 150, -34], "cellWidth": 20, "cellHeight": 100, "units": "kilometers" }调用方式即:
const grid = rectangleGrid([141, -39, 150, -34], 20, 100, { units: "kilometers" });再如带掩膜的用例(掩膜为澳大利亚大陆的 Polygon,1×2 度单元格):
{ "bbox": [110, 0, 160, -50], "cellWidth": 1, "cellHeight": 2, "units": "degrees", "mask": { "type": "Feature", "properties": {}, "geometry": { "type": "Polygon", "coordinates": [...] } } }调用方式:
const grid = rectangleGrid([110, 0, 160, -50], 1, 2, { units: "degrees", mask: maskFeature, // 澳大利亚多边形 });4.1 如何运行测试
测试脚本定义在 package.json 中:
pnpm test:tape # 等价于 tsx test.tstest.ts 会读取test/in下每个夹具,以夹具 JSON 中的bbox、cellWidth、cellHeight、units、properties、mask字段调用rectangleGrid,再用@turf/truncate截断坐标精度后与test/out目录中的期望输出做深度相等断言;同时会把 bbox(红色)与掩膜(蓝色)作为样式要素追加进结果,便于可视化核对。若设置环境变量REGEN,则会将实际结果写回test/out重新生成期望文件。
4.2 性能表现
bench.ts 内置了三个量级的基准测试(均在[-95, 30, -85, 40]的 bbox 上):
| 场景 | 单元格宽高 | 单元格数量 | 吞吐量 |
|---|---|---|---|
| highres | 1 × 2 英里 | 约 206310 个 | 约 5.99 ops/sec |
| midres | 10 × 20 英里 | 约 2006 个 | 约 3388 ops/sec |
| lowres | 100 × 200 英里 | 15 个 | 约 466370 ops/sec |
运行基准:
pnpm bench # 等价于 tsx bench.ts该结果(注释中记录的实测值)直观地表明:性能随单元格数量线性下降,单元格数量是决定耗时的最主要因素,因此在大范围、高分辨率网格场景下应评估数据规模与渲染/存储成本。
五、使用建议与注意事项
- 明确"度"的语义:
rectangleGrid的单元格宽高最终以度为单位一致,生成的矩形在赤道附近接近真实等距,在高纬度地区线性距离会被拉长。如需线性单位下真正等距的正方形网格,应改用@turf/square-grid或@turf/point-grid等按目标投影计算的方案。 - bbox 顺序不可颠倒:
bbox必须严格按[minX, minY, maxX, maxY]传入;源码直接以west = bbox[0]、south = bbox[1]、east = bbox[2]、north = bbox[3]解构,顺序错误会导致网格方向与预期不符。 - 宽高为非负数:
convertLength会对负数抛出"length must be a positive number"异常,请保证cellWidth、cellHeight为正数。 - 掩膜是相交判定:
options.mask按booleanIntersects过滤,掩膜边界处的单元格会被保留但呈不完整形态,若要精确裁剪单元格本身,需对结果另行做@turf/intersect处理。 - 属性透传:
options.properties会原样附加到每个单元格多边形上,可用于为网格附加业务字段(如索引编号、区域分类),无需事后二次遍历赋值。
六、小结
rectangleGrid是一个设计简洁、语义清晰的网格生成工具:三个必选参数加上units、mask、properties三个可选参数,即可在任意 bbox 内生成角度一致的矩形网格。其源码(index.ts)以"单位换算 → 取整分行列 → 居中偏移 → 双循环构造闭合环 → 掩膜相交过滤 → 打包 FeatureCollection"为主线,配合 test.ts 的多组真实夹具与 bench.ts 的性能基准,构成了从 API 到实现的完整闭环。在需要快速生成规则格网用于空间索引、热力分区、采样布点等场景时,本模块是最直接的选择;而需要线性单位等距网格时,请选择 Turf 的其他网格模块。
- 数据分析
【免费下载链接】turf
A modular geospatial engine written in JavaScript and TypeScript
相关推荐
@turf/difference 多边形差集运算完全指南:从 API 到源码级实现解析
@turf/difference 多边形差集运算完全指南:从 API 到源码级实现解析 在 Turf.js 的几何运算体系中, difference (差集)是
数据分析用 Turf 从点集生成凹包(Concave Hull):@turf/concave 参数原理与实战指南
用 Turf 从点集生成凹包(Concave Hull):@turf/concave 参数原理与实战指南 本文围绕 Turf 生态中的 @turf/concav
数据分析Turf squareGrid 完全指南:基于 @turf/square-grid 生成经纬度一致的方形网格
Turf squareGrid 完全指南:基于 @turf/square grid 生成经纬度一致的方形网格 导读 @turf/square grid 是 Tu
数据分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考