- 数据分析
【免费下载链接】turf
A modular geospatial engine written in JavaScript and TypeScript
@turf/isobands是 Turf 模块化地理空间引擎中负责**填充等值面(filled contour isobands)**生成的模块。它以带 z 值的点网格(FeatureCollection of Point)和一组数值断点(breaks)为输入,输出一组表示"值落于某个区间"的面状 MultiPolygon。本文从 packages/turf-isobands/README.md 出发,结合 index.ts 源码、内部工具库与测试用例,完整讲解参数语义、网格输入约束、marching squares 核心算法、坐标重缩放原理、错误处理与测试验证方法,读者读完可直接在自己的项目中用它生成高程分层、人口密度、温度区间等填充等值面。
一、isobands 是什么:与 isolines 的关系
等值线族分为两类:**等值线(isolines)**输出线状要素(MultiLineString),刻画"值恰好等于某个阈值"的边界;等值面(isobands)则输出面状要素(MultiPolygon),刻画"值落于两个相邻断点之间"的连续区域,通常以渐变填充色渲染。
从源码结构看,两者共用同一套 marching squares 分段思路,但 isobands 的核心差异在于 index.ts 中createContourLines的注释所强调的:"Using segments from two different breaks[], and enforcing closed polygons are the two major difference between the implementation of @turf/isolines and @turf/isobands"——isobands 需要同时使用相邻两个断点的分段来闭合多边形,从而形成"带"状填充区域,而 isolines 只需单层分段。
在 package.json 中该模块被描述为:"Takes a grid of values (GeoJSON format) and a set of threshold ranges. It outputs polygons that group areas within those ranges, effectively creating filled contour isobands."
二、安装与引入
官方 README 提供了两种安装方式。当前仓库packages/turf-isobands的package.json声明"version": "7.4.0"、"type": "module",且"engines": { "node": ">=22" },安装使用环境需满足该 Node 版本要求。
单独安装本模块:
$ npm install @turf/isobands或安装包含全部模块的一体化包(所有模块以函数形式挂载):
$ npm install @turf/turf在 ES Module 环境中导入:
import { isobands } from "@turf/isobands"; // 使用一体化包时 import * as turf from "@turf/turf"; turf.isobands(pointGrid, breaks, options);三、函数签名与参数详解
README 中给出的isobands完整签名如下,参数含义、默认值与类型约束是本文重点:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
pointGrid | FeatureCollection<Point> | 是 | — | 输入点网格,必须是正方形或矩形、已规则网格化,x/y 维度一致且至少 2x2 |
breaks | Array<number> | 是 | — | 等值面分段的断点数组,决定在哪里切分等高带 |
options | Object | 否 | {} | 输出选项 |
options.zProperty | string | 否 | 'elevation' | 从点的 properties 中读取 z 值的属性名 |
options.commonProperties | Object | 否 | {} | 传递给所有等值面要素的公共 GeoJSON 属性 |
options.breaksProperties | Array<Object> | 否 | [] | 按断点顺序依次传递给对应等值面要素的属性对象 |
返回值:FeatureCollection<MultiPolygon>,即一组代表等值面的 MultiPolygon 要素。
源码 index.ts 对可选参数做了如下兜底与校验,理解这些有助于排查问题:
options = options || {}; if (!isObject(options)) throw new Error("options is invalid"); const zProperty = options.zProperty || "elevation"; const commonProperties = options.commonProperties || {}; const breaksProperties = options.breaksProperties || []; // Validation collectionOf(pointGrid, "Point", "Input must contain Points"); if (!breaks) throw new Error("breaks is required"); if (!Array.isArray(breaks)) throw new Error("breaks is not an Array"); if (!isObject(commonProperties)) throw new Error("commonProperties is not an Object"); if (!Array.isArray(breaksProperties)) throw new Error("breaksProperties is not an Array");pointGrid必须全部为 Point 要素,否则抛错"Input must contain Points";breaks为必填且必须是数组;commonProperties必须是对象、breaksProperties必须是数组;- 遍历生成每个等值面时还会校验
breaksProperties中每一项必须是对象(见 index.ts)。
四、快速上手:完整可运行示例
沿用官方 README 的文档约定(README 由源码注释自动生成,示例风格与 turf-isolines/README.md 一致),构造一个 4x4 以上、带随机 z 值的点网格并生成等值面:
import { pointGrid } from "@turf/point-grid"; import { isobands } from "@turf/isobands"; // 1. 生成规则点网格(extent 为 [minX, minY, maxX, maxY]) const extent = [0, 30, 20, 50]; const cellWidth = 100; // 网格间距,单位默认 kilometers const pointGrid = pointGrid(extent, cellWidth, { units: "miles" }); // 2. 为每个点写入 z 值(属性名默认为 elevation) for (let i = 0; i < pointGrid.features.length; i++) { pointGrid.features[i].properties.temperature = Math.random() * 10; } // 3. 定义断点:将生成 N-1 个等值面带 const breaks = [0, 2, 4, 6, 8, 10]; // 4. 生成填充等值面 const bands = isobands(pointGrid, breaks, { zProperty: "temperature", // 指定 z 值属性名 commonProperties: { fill: "#f00" }, // 公共属性,加到所有要素上 breaksProperties: [ { "fill-opacity": 0.2 }, { "fill-opacity": 0.4 }, { "fill-opacity": 0.6 }, { "fill-opacity": 0.8 }, { "fill-opacity": 1 }, ], // 按断点顺序逐个传入 }); // 5. 渲染:bands.features.length 即断点数量减一 console.log(bands.type); // "FeatureCollection" console.log(bands.features[0].geometry.type); // "MultiPolygon"断点数量与输出数量的关系:breaks有 N 个数值时,输出 N-1 个等值面要素。源码createContourLines中循环for (let i = 1; i < breaks.length; i++),每次以breaks[i-1](下界)与breaks[i](上界)构成一个区间带,并在每个输出要素的属性上写入[zProperty] = lowerBand + "-" + upperBand(见 index.ts)。以仓库测试数据 test/in/pointGrid.geojson 为例,breaks: [0, 20, 40, 80, 160]会得到 4 个等值面,其people属性值分别为"0-20"、"20-40"、"40-80"、"80-160",配合breaksProperties中由低到高的fill-opacity(0.5 → 0.8)即可在渲染层实现渐变分层。
五、输入网格的硬性约束
README 明确要求:输入必须是正方形或矩形(square or rectangular)、已经规则网格化(already gridded),即 x、y 维度一致且至少 2x2。源码从两个方面强制校验:
矩阵维度校验(index.ts):
gridToMatrix将点网格转换为二维数值矩阵后,检查matrix.length < 2 || dx < 2则抛错"Matrix of points must be at least 2x2";随后逐行检查每行长度是否一致,不一致则抛错"Matrix of points is not uniform in the x dimension"。内部工具校验:
gridToMatrix(见 lib/grid-to-matrix.ts)按纬度分组、组内按经度排序生成矩阵;测试用例 test.ts 专门构造了"一个点的经度是1e-10、与其他点几乎相同却不足以构成规则网格"的输入,验证此时isobands会抛出异常——这从侧面说明:网格点必须严格按行列规则对齐,否则排序后无法形成均匀矩阵。
关于矩阵方向:源码在注释中提醒,矩阵坐标系的"上/下"与地理坐标相反——南半球用负数表示纬度,因此矩阵 Y 为 0 处实际是底部,Y 为dy - 1处是顶部(见 index.ts)。gridToMatrix默认按纬度从大到小排序,而 isobands 内部以flip: true调用,得到自下而上的矩阵,保证 marching squares 输出的多边形绕向符合 GeoJSON 的右手(逆时针)约定。
六、输出结构与属性语义
- geometry 类型:每个输出要素为
MultiPolygon,即使只有一个外环也会以 MultiPolygon 形式返回; - z 值区间属性:每个要素的
properties[zProperty]为形如"20-40"的字符串(下界-上界),可以直接用于图例文本; - 属性合并顺序:
contourProperties = { ...commonProperties, ...breaksProperties[index] }(index.ts),即breaksProperties中的同名属性会覆盖commonProperties,最后再写入zProperty区间值; - 环的组织:每个 MultiPolygon 内部按"外环在前、内环(洞)在后"的 GeoJSON 规则组织,避免渲染时出现重叠错误。源码
orderByArea用@turf/area计算各环面积并降序排列(index.ts),groupNestedRings再借助@turf/boolean-point-in-polygon与@turf/explode将互相包含的环分组(index.ts)。
七、核心算法原理:marching squares 源码级解读
isobands 的实现是一条清晰的流水线:gridToMatrix→createContourLines(内含getSegments、assembleRings、orderByArea、groupNestedRings)→rescaleContours→ 包装为 MultiPolygon 要素。
7.1 网格转矩阵:gridToMatrix
lib/grid-to-matrix.ts 中的sortPointsByLatLng将点按纬度分成多行、每行按经度升序排列;矩阵元素取properties[zProperty],缺失该属性时填入 0(else row.push(0),见 grid-to-matrix.ts)。这一点在使用时需要注意:z 属性缺失会被静默当作 0 参与计算。
7.2 分段提取:getSegments 与 16 种单元格情形
getSegments(index.ts)对矩阵的每个 2x2 单元计算四个角(tl 左上、tr 右上、br 右下、bl 左下)是否超过阈值,组合成 0~15 的二进制掩码:
let grid = (tl >= threshold ? 8 : 0) | (tr >= threshold ? 4 : 0) | (br >= threshold ? 2 : 0) | (bl >= threshold ? 1 : 0);掩码为 0(全部低于)或 15(全部高于)时跳过;其余情形在单元格边上产生一段逆时针线段。关键细节:
- 线性插值
frac:线段落点用阈值在相邻两角值间的比例插值,t = (threshold - z0) / (z1 - z0),并裁剪到 [0,1];若z0 === z1取 0.5(index.ts)。这保证了等值面边界平滑,而不是生硬地切在单元格角点上。 - 鞍点处理(case 5 与 10):当对角线两角同高(如 tl、br 高于阈值而 tr、bl 低于)时存在歧义,源码取四角平均值
avg决定分段连接方向,以保证整体环的逆时针绕向一致(index.ts、index.ts)。
7.3 组装闭环:assembleRings
assembleRings(index.ts)将上一断点的分段(prevSegments)与当前断点分段(reverseSegments)拼接:当前断点分段来自更高阈值,从低带视角看为顺时针,因此先reverse再与低带分段合并,以维持统一的逆时针顺序。随后:
- 将首尾相连的连续分段拼成 contour;
- 未闭合的 contour 必然触及矩阵边界,沿矩阵四边逆时针寻找相邻 contour 或拐角,最终闭合为环;
- 过滤掉长度小于 4 的"零面积环"。
7.4 边界情形:整带覆盖网格
如果某个区间带内没有任何闭合多边形,且矩阵原点值恰好落于该区间内(matrix[0][0] < upperBand && matrix[0][0] >= lowerBand),说明整个网格的值都落在该区间,此时直接以整个网格边界构造一个外环(index.ts)。此逻辑对应 issue #1797/#2956 的修复,测试用例isobands - flat data, from issue #1797(test.ts)验证了全等值(所有点elevation: 1)时仍能输出有效多边形。
7.5 坐标重缩放:rescaleContours
marching squares 假定网格点间距为 1 个单位,因此结果需要映射回真实地理坐标。rescaleContours(index.ts)完成这一步:
- 用
@turf/bbox计算输入点网格的包围盒[minX, minY, maxX, maxY],得到真实宽度与高度; - 以包围盒左下角为原点,除以矩阵格数得到
scaleX、scaleY; - 对每个坐标执行
x * scaleX + x0、y * scaleY + y0。
这就是为什么即使输入网格不规则间距也能正确还原到地图坐标的原因:只要点网格是规则矩形排列,包围盒与矩阵格数即可推导出精确缩放。
八、错误处理速查表
结合源码校验逻辑与 test.ts 的 throws 用例,常见报错与触发条件如下:
| 报错信息 | 触发条件 | 源码位置 |
|---|---|---|
options is invalid | options 不是对象 | index.ts |
Input must contain Points | 输入含非 Point 要素 | index.ts |
breaks is required/breaks is not an Array | breaks 缺失或非数组 | index.ts |
commonProperties is not an Object | commonProperties 非对象 | index.ts |
breaksProperties is not an Array | breaksProperties 非数组 | index.ts |
Each mappedProperty is required to be an Object | breaksProperties 中某项非对象 | index.ts |
Matrix of points must be at least 2x2 | 网格不足 2x2 | index.ts |
Matrix of points is not uniform in the x dimension | 各行点数不一致 | index.ts |
九、从矩阵直接生成网格:matrixToGrid 与测试输入
仓库在 lib/matrix-to-grid.ts 提供了逆向工具matrixToGrid:给定二维数值矩阵、左下角原点origin、cellSize(单位可用units指定,支持 Turf 全部 Units,如kilometers、miles),借助@turf/rhumb-destination逐行逐列生成规则点网格。这正是测试与基准中"矩阵输入"的用法。
仓库测试数据 test/in/matrix1.json 展示了一个可直接运行的输入示例:
{ "matrix": [ [1, 1, 1, 1, 1, 1, 1], [1, 5, 5, 5, 5, 5, 1], [1, 5, 15, 15, 15, 5, 1], [1, 5, 10, 10, 10, 5, 1], [1, 5, 5, 5, 5, 5, 1], [1, 1, 1, 1, 1, 1, 1] ], "origin": [10.8, 44.1], "cellSize": 20, "breaks": [2, 4, 8, 12], "zProperty": "temperature" }配合matrixToGrid(matrix, [10.8, 44.1], 20, { zProperty: "temperature" })即可得到isobands的标准输入。测试用例isobands(test.ts)对test/in/下全部 fixture(matrix1、matrix2、bigMatrix、1084、2956、pointGrid)逐一执行:若输入自带 GeoJSON properties 则直接使用,否则先用matrixToGrid转换;输出经@turf/truncate截断坐标后与test/out/中的期望结果做deepEqual断言,并附加包围盒调试线(envelope)。
十、性能与基准
bench.ts 基于benchmark对同一组 fixture 做吞吐测试,文件头注释记录了作者在本仓库环境的参考运行结果(数据仅供参考,实际性能取决于机器与输入规模):
bigMatrix x 73.43 ops/sec ±2.12% (62 runs sampled) matrix1 x 5,205 ops/sec ±3.13% (78 runs sampled) matrix2 x 2,333 ops/sec ±9.38% (71 runs sampled) pointGrid x 3,201 ops/sec ±1.81% (78 runs sampled)可以推断:性能主要受网格规模(矩阵格数)影响,bigMatrix这类大矩阵吞吐量显著下降;同时 bench.ts 也演示了"矩阵输入"(matrixToGrid转换)与"GeoJSON 输入"两种调用路径的统一处理方式。
十一、应用场景与模块协同
isobands 常用于以下场景,均可直接复用本模块输出:
- 高程分层:以 DEM 采样点网格 +
breaks生成地形分层设色面; - 气象/环境插值:温度、降水、污染物浓度等连续场量的区间可视化;
- 人口/统计密度:将聚合到网格点的计数(如 test/in/pointGrid.geojson 的
people属性)转为色阶面; - 与 isolines 叠加:
@turf/isolines输出边界线、@turf/isobands输出填充带,两者叠加即为完整的等值线图。输入网格可由@turf/point-grid、@turf/hex-grid等网格工具生成,插值可配合@turf/interpolate。
十二、小结
@turf/isobands是一条结构清晰的算法流水线:规则点网格 → 二维数值矩阵 → marching squares 分段 → 相邻断点闭合环 → 按面积分组嵌套 → 坐标重缩放 → MultiPolygon 要素集合。使用时的三个关键约束是:网格必须规则且至少 2x2、breaks必须为升序数值数组、z 属性缺失会按 0 处理。掌握这些约束与源码中的校验逻辑,即可在任意 Turf 项目中稳定、高效地生成填充等值面。
- 数据分析
【免费下载链接】turf
A modular geospatial engine written in JavaScript and TypeScript
相关推荐
Qwen3-Coder 评测集解读:DevQualityEval v0.5.0 中 Mistral 7B Instruct v0.3 的代码开发质量报告
Qwen3 Coder 评测集解读:DevQualityEval v0.5.0 中 Mistral 7B Instruct v0.3 的代码开发质量报告 本文围
数据分析Juice 主题实战指南:基于 Zola 搭建优雅的产品官网
Juice 主题实战指南:基于 Zola 搭建优雅的产品官网 Juice 是一个定位为 产品站点(product sites) 而设计的 Zola 主题,主打直
数据分析Cilium IPsec 透明加密实战指南:基于 Kubernetes Secret 的密钥分发、密钥轮换与 XFRM 排障
Cilium IPsec 透明加密实战指南:基于 Kubernetes Secret 的密钥分发、密钥轮换与 XFRM 排障 在 Kubernetes 环境中,
数据分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考