问题:
在cesium地表上绘制 PolygonGeometry 的GroundPrimitive,即贴地多边形时,存在拾取错误的问题,可能是因为cesium射线检测误拾取到了多个位置不同的多边形构成的大包围盒,也可能是刚加载完。数据还没有变换完成导致了误拾取。测试重现 偏向是第一种原因。
解决:
精确判断点击的点是否在GroundPrimitive多边形内,对拾取到的GroundPrimitive Polygon 进行精确拾取检测
/**
* 判断三维空间中的点是否在多边形内(支持复杂多边形和孔洞)
* @param position 待检测点
* @param polygon 多边形
* @param scene
* @returns 是否在多边形内
*/
isPointInGeometry (position: Cartesian3, polygon: any, scene: Scene) {
const ray = scene.camera.getPickRay(position)
if (!ray) {
console.error('const ray = _scene.camera.getPickRay(position) 点击位置发出的射线失败')
return false
}
try {
// 获取待检测点(世界坐标)
const worldPosition = scene.globe.pick(ray, scene) // 查找射线与渲染的地球表面之间的交点。射线必须以世界坐标给出。
if (!worldPosition) {
console.error('const worldPosition = _scene.globe.pick(ray, _scene) 未能获得点击位置射线的世界坐标')
return false
}
// 获取多边形坐标
const vertices = getPolygonCoordinates(polygon)
// ----------------------------
// 步骤1: 坐标转换
// ----------------------------
// 将世界坐标转换为经纬度
const pointCarto = Cartographic.fromCartesian(worldPosition)
const pointLonLat = [pointCarto.longitude, pointCarto.latitude]
// console.log('坐标转换 pointCarto', pointCarto)
// ----------------------------
// 步骤2: 提取几何体顶点
// ----------------------------
const polygonLonLat: number[][] = []
for (let i = 0; i < vertices.length; i++) {
// 提取顶点笛卡尔坐标
const vertex = vertices[i]
// 转换为经纬度
const vertexCarto = Cartographic.fromCartesian(vertex)
polygonLonLat.push([vertexCarto.longitude, vertexCarto.latitude])
}
// console.log('提取几何体顶点 polygonLonLat', polygonLonLat)
// 空顶点直接返回
if (polygonLonLat.length === 0) return false
// ----------------------------
// 步骤3: 确保多边形闭合
// ----------------------------
const first = polygonLonLat[0]
const last = polygonLonLat[polygonLonLat.length - 1]
if (!Math.equalsEpsilon(first[0], last[0], 1e-10) ||
!Math.equalsEpsilon(first[1], last[1], 1e-10)) {
polygonLonLat.push([first[0], first[1]])
}
// console.log('确保多边形闭合 polygonLonLat', polygonLonLat)
// console.groupEnd()
// ----------------------------
// 步骤4: 使用射线法判断包含性
// ----------------------------
return rayCasting(pointLonLat, polygonLonLat)
} catch (error) {
console.error('Geometry containment check failed:', error)
return false
}
}
}
/**
* 获取多边形坐标(支持带洞的多边形)
* @param polygon 多边形
* @returns
*/
function getPolygonCoordinates (polygon: PolygonGraphics) {
// 获取 PolygonHierarchy 对象
const hierarchy = polygon.hierarchy?.getValue(JulianDate.now())
if (!hierarchy) return []
// 递归提取所有层级的坐标
const extractPositions = (hierarchy: PolygonHierarchy): Cartesian3[] => {
const positions = [...hierarchy.positions]
if (hierarchy.holes) {
hierarchy.holes.forEach((hole) => positions.push(...extractPositions(hole)))
}
return positions
}
return extractPositions(hierarchy)
}
/**
* 射线法判断点是否在多边形内;从待检测点向右发射一条水平射线,统计射线与多边形边的交点数量
* y轴
* ______1________
* 4| |
* | .------------>
* | |2
* |______________| x轴
* 3
* 1、3 Y坐标不在边的Y范围内(F F);4射线y坐标下的x值小于交点X (F);2 射线y坐标下的x值大于交点X (T)
* @param point 点坐标 [经度, 纬度](弧度)
* @param polygon 多边形顶点 [[经度, 纬度], ...](弧度)
*/
function rayCasting (point: number[], polygon: number[][]): boolean {
let inside = false
const [x, y] = point
// 遍历多边形的每条边
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const [xi, yi] = polygon[i]
const [xj, yj] = polygon[j]
// 计算射线与边的交点
const intersect = (
(yi > y) !== (yj > y) && // 点Y坐标在边的Y范围内
x < ((xj - xi) * (y - yi)) / (yj - yi) + xi // 交点X坐标在射线右侧
)
if (intersect) inside = !inside
}
return inside
}