turf的pointsWithinPolygon排查
进击的WebGIS(1)。
在WebGIS开发过程中经常使用到turf,最近遇到了一个奇怪的问题,使用pointsWithinPolygon用一个面去过滤点集,有几个点是明显分布在面中的,但最近的结果是一个空的FeatureCollection。
在使用 Turf.js 的 pointsWithinPolygon
时返回空结果,通常有以下几类原因。请逐步排查:
1. 检查数据格式是否正确
-
点和多边形必须是 GeoJSON Feature 或 FeatureCollection
Turf 要求输入为标准的 GeoJSON 结构,例如:
// 点数据格式示例 const point = {type: 'Feature',geometry: {type: 'Point',coordinates: [经度, 纬度] // 注意顺序是 [lng, lat]} };// 多边形数据格式示例(必须闭合) const polygon = {type: 'Feature',geometry: {type: 'Polygon',coordinates: [[[lng1, lat1], [lng2, lat2], [lng3, lat3], [lng1, lat1]]] // 首尾坐标相同} };
-
错误示例:直接传递几何对象(如
geometry: {...}
)而非完整的Feature
对象。
2. 确认坐标顺序 [经度, 纬度]
- GeoJSON 要求坐标顺序为 [经度(lng), 纬度(lat)],而非 [纬度, 经度]。
- 如果顺序颠倒,点可能位于完全不同的位置(例如非洲附近海域),导致结果为 0。
3. 验证多边形是否闭合
-
多边形的坐标数组必须首尾闭合(即第一个点和最后一个点相同)。
-
错误示例:
// 未闭合的多边形(缺少最后一个点) [[[0,0], [1,0], [1,1], [0,1]]] // ❌ 错误
-
正确示例
[[[0,0], [1,0], [1,1], [0,1], [0,0]]] // ✅ 正确
4. 检查点是否实际在多边形内
-
使用 turf.booleanPointInPolygon手动验证单个点:(这一点非常有效且必要,因为源码中pointsWithinPolygon就是遍历使用了booleanPointInPolygon方法,booleanPointInPolygon方法的算法其实是射线法)。
const point = turf.point([lng, lat]); const isInside = turf.booleanPointInPolygon(point, polygon);
console.log(isInside); // true/false
- **注意**:Turf 默认不包含多边形边界上的点(可通过 `{ ignoreBoundary: false }` 调整)。------### 5. **确认参数顺序**- ```
pointsWithinPolygon的第一个参数是 点集合,第二个是 多边形
// ✅ 正确用法
const pointsInside = turf.pointsWithinPolygon(points, polygon);// ❌ 错误用法(参数颠倒)
const pointsInside = turf.pointsWithinPolygon(polygon, points);
6. 检查坐标系一致性
- 确保所有数据使用相同的坐标系(例如 WGS84)。
- 如果数据来自其他来源(如平面坐标系),需先转换为经纬度。
7. 其他可能原因
- Turf 版本问题:升级到最新版本(
npm update @turf/turf
)。 - 数据范围不重叠:确保点和多边形在地理范围内有交集(可先用
turf.bbox
检查边界框)。
8. 常规马虎不易查错误
- 拼写错误:方法名拼写错误、字母顺序错误、其他字符拼写错误。
- 大小写问题:FeatureCollection中{type:“Feature”}中Feature是要大写的,若用feature,尤其在一些早期版本,极容易发生空几何的错误,其实是源码中的大小写判断兼容问题(仅判断Feature)。
快速调试示例
const turf = require('@turf/turf');// 示例多边形(闭合的矩形)
const polygon = turf.polygon([[[0, 0], [10, 0], [10, 10], [0, 10], [0, 0] // 首尾闭合
]]);// 示例点(一个在内部,一个在外部)
const points = turf.featureCollection([turf.point([5, 5]), // 内部turf.point([15, 15]) // 外部
]);// 执行查询
const result = turf.pointsWithinPolygon(points, polygon);
console.log(result.features.length); // 应返回 1points.features.forEach(point=>{let pointInside = turf.booleanPointInPolygon(point, polygon);if(pointInside) console.log("点在面内");
})
如果仍有问题,欢迎留言。
以下是一个完整的、不依赖任何第三方库的 booleanPointInPolygon
实现代码,基于射线法(Ray Casting Algorithm),支持 多边形带孔洞 和 边界条件处理:
/*** 判断点是否在多边形内(射线法)* @param {number[]} point - 待测点坐标 [x, y]* @param {number[][][]} polygon - 多边形坐标(支持带孔),格式为:* [* [[x1,y1], [x2,y2], ..., [x1,y1]], // 外环* [[x1,y1], [x2,y2], ..., [x1,y1]] // 内环(孔洞,可选)* ]* @param {boolean} [ignoreBoundary=true] - 是否忽略边界(点在边上不算内部)* @return {boolean}*/
function booleanPointInPolygon(point, polygon, ignoreBoundary = true) {const [x, y] = point;let inside = false;const epsilon = 1e-6; // 浮点计算容差// 遍历所有环(外环 + 内环)for (const ring of polygon) {for (let i = 0; i < ring.length - 1; i++) {const v1 = ring[i];const v2 = ring[i + 1];const [x1, y1] = v1;const [x2, y2] = v2;// 检查点是否在顶点上if (isEqual(point, v1, epsilon) || isEqual(point, v2, epsilon)) {return !ignoreBoundary;}// 检查点是否在水平边上if (Math.abs(y1 - y2) < epsilon && Math.abs(y - y1) < epsilon) {const minX = Math.min(x1, x2);const maxX = Math.max(x1, x2);if (x >= minX && x <= maxX) return !ignoreBoundary;}// 检查点是否在边所在的直线上,并且在线段内if (isOnSegment(point, v1, v2, epsilon)) {return !ignoreBoundary;}// 射线与边相交判断if ((y1 > y) !== (y2 > y)) {const xIntersect = ((y - y1) * (x2 - x1)) / (y2 - y1) + x1;// 处理浮点误差if (Math.abs(x - xIntersect) < epsilon) {return !ignoreBoundary;}if (x < xIntersect) {inside = !inside;}}}}return inside;
}// 判断两个点是否相等(考虑浮点误差)
function isEqual(a, b, epsilon) {return Math.abs(a[0] - b[0]) < epsilon && Math.abs(a[1] - b[1]) < epsilon;
}// 判断点是否在线段上
function isOnSegment(p, a, b, epsilon) {const [x, y] = p;const [x1, y1] = a;const [x2, y2] = b;// 先判断是否共线const cross = (x - x1) * (y2 - y1) - (y - y1) * (x2 - x1);if (Math.abs(cross) > epsilon) return false;// 再判断是否在矩形范围内const minX = Math.min(x1, x2) - epsilon;const maxX = Math.max(x1, x2) + epsilon;const minY = Math.min(y1, y2) - epsilon;const maxY = Math.max(y1, y2) + epsilon;return x >= minX && x <= maxX && y >= minY && y <= maxY;
}
使用示例
// 定义一个带孔的正方形(外环+内环)
const polygon = [[[0,0], [10,0], [10,10], [0,10], [0,0]], // 外环[[2,2], [8,2], [8,8], [2,8], [2,2]] // 内环(孔洞)
];console.log(booleanPointInPolygon([5,5], polygon)); // true(在外环内,内环外)
console.log(booleanPointInPolygon([5,5], polygon, false)); // false(严格内部)console.log(booleanPointInPolygon([1,1], polygon)); // true
console.log(booleanPointInPolygon([5,5], polygon)); // false(在内环内)console.log(booleanPointInPolygon([5,0], polygon)); // false(在边上,ignoreBoundary=true)
console.log(booleanPointInPolygon([5,0], polygon, false)); // true(在边上,ignoreBoundary=false)
关键特性
- 支持复杂多边形
- 处理带孔洞的多边形(多个环)
- 自动处理内外环的奇偶规则
- 边界条件处理
- 点在边上(精确判断线段是否包含点)
- 点在顶点上
- 水平边特殊情况
- 浮点误差容差
- 使用
epsilon
避免精度问题
- 使用
- 灵活边界控制
- 通过
ignoreBoundary
参数控制是否包含边界
- 通过
注意事项
-
多边形必须闭合:每个环的最后一个点必须与第一个点相同。
-
坐标系方向:假设为笛卡尔坐标系(X 轴向右,Y 轴向上),如需经纬度坐标(Y 轴向下),需反转 Y 轴比较逻辑。
-
输入坐标应为
[[[x,y], ...]]
格式(与 GeoJSON 一致)。- 水平边特殊情况
- 浮点误差容差
- 使用
epsilon
避免精度问题
- 使用
- 灵活边界控制
- 通过
ignoreBoundary
参数控制是否包含边界
- 通过
注意事项
-
多边形必须闭合:每个环的最后一个点必须与第一个点相同。
-
坐标系方向:假设为笛卡尔坐标系(X 轴向右,Y 轴向上),如需经纬度坐标(Y 轴向下),需反转 Y 轴比较逻辑。
-
输入坐标应为
[[[x,y], ...]]
格式(与 GeoJSON 一致)。