当前位置: 首页 > news >正文

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)

关键特性

  1. 支持复杂多边形
    • 处理带孔洞的多边形(多个环)
    • 自动处理内外环的奇偶规则
  2. 边界条件处理
    • 点在边上(精确判断线段是否包含点)
    • 点在顶点上
    • 水平边特殊情况
  3. 浮点误差容差
    • 使用 epsilon 避免精度问题
  4. 灵活边界控制
    • 通过 ignoreBoundary 参数控制是否包含边界

注意事项

  • 多边形必须闭合:每个环的最后一个点必须与第一个点相同。

  • 坐标系方向:假设为笛卡尔坐标系(X 轴向右,Y 轴向上),如需经纬度坐标(Y 轴向下),需反转 Y 轴比较逻辑。

  • 输入坐标应为 [[[x,y], ...]] 格式(与 GeoJSON 一致)。

    • 水平边特殊情况
  1. 浮点误差容差
    • 使用 epsilon 避免精度问题
  2. 灵活边界控制
    • 通过 ignoreBoundary 参数控制是否包含边界

注意事项

  • 多边形必须闭合:每个环的最后一个点必须与第一个点相同。

  • 坐标系方向:假设为笛卡尔坐标系(X 轴向右,Y 轴向上),如需经纬度坐标(Y 轴向下),需反转 Y 轴比较逻辑。

  • 输入坐标应为 [[[x,y], ...]] 格式(与 GeoJSON 一致)。

在这里插入图片描述

相关文章:

  • C++(2)关键字+数据类型 +数据类型输入
  • Linux云计算训练营笔记day11【Linux CentOS7(cat、less、head、tail、lscpu、lsblk、hostname、vim、which、mount、alias)】
  • 技术决策缺乏团队参与,如何增强执行力?
  • YoloV9改进策略:卷积篇|风车卷积|即插即用
  • windows服务器部署jenkins工具(一)
  • 接口排查不能靠猜:实战中如何用抓包工具精准定位问题(含 Charles 使用示例)
  • 【实战教程】如何添加git仓库的子模块
  • V4L2应用程序开发-01数据采集流程
  • MCP与通讯模式:理论与实战体验
  • 【集成电路】集成电路导论知识点
  • MFC 编程中 OnInitDialog 函数
  • 电子电路原理第十六章(负反馈)
  • Mergekit——任务向量合并算法Ties解析
  • 电机控制杂谈(25)——为什么对于一般PMSM系统而言相电流五、七次谐波电流会比较大?
  • 嵌入式51单片机:C51
  • 【Python-Day 15】深入探索 Python 字典 (下):常用方法、遍历、推导式与嵌套实战
  • `asyncio.gather()` 是什么
  • Chrome插件学习笔记
  • 专题五:floodfill算法(太平洋大西洋水流问题)
  • POJ3107树的重心
  • 《中华人民共和国经济史(1949—1978年)》教材出版发行
  • 盲人不能刷脸认证、营业厅拒人工核验,央媒:别让刷脸困住尊严
  • 河南发布高温橙警:郑州、洛阳等地最高气温将达40℃以上
  • 蒲慕明院士:未来数十年不是AI取代人,而是会用AI的人取代不会用的
  • 《歌手》回归,人均技术流,00后整顿职场
  • 高瓴、景林旗下公司美股持仓揭晓:双双增持中概股