cesium 海流限制范围

在 Cesium 三维可视化开发中,洋流(海流)的可视化是海洋相关应用的重要组成部分。然而,有时我们需要将洋流的显示限制在特定的地理区域内,比如某个海域或湾区,而不是整个海洋。本文将介绍如何通过多边形边界结合点在多边形内的判断算法,实现 Cesium 中洋流的区域限制显示。
在海洋可视化场景中,我们通常会加载全球或区域的洋流数据,但实际应用中可能只关注特定区域的洋流情况。如果不做区域限制,会出现以下问题:

  1. 数据计算量大,影响性能
  2. 视觉上不够聚焦,难以凸显重点区域
  3. 洋流可能显示在陆地或不相关的水域上

因此,我们需要一种方法,将洋流的显示限制在指定的多边形区域内

技术方案

我们的解决方案基于以下核心思路:

  1. 定义一个多边形区域作为洋流显示的边界
  2. 使用点在多边形内的判断算法(射线算法)
  3. 在洋流粒子渲染前进行位置判断,仅渲染位于多边形内的粒子

这种方法既保证了视觉效果,又优化了性能。

核心算法:点在多边形内的判断

判断一个点是否在多边形内是计算几何中的经典问题。我们采用的是射线算法(Ray Casting Algorithm),也称为奇偶规则(Even-Odd Rule)。该算法的思路是:

  1. 从判断点向任意固定方向(通常选择水平向右)引一条射线
  2. 计算这条射线与多边形边界的交点数量
  3. 如果交点数为奇数,则点在多边形内;如果为偶数,则点在多边形外

下面是这个算法的详细实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function isPointInPolygon(point, polygon) {
// 初始化结果为false(假设点在多边形外)
let inside = false;
// 获取待检测点的坐标
let x = point[0],
y = point[1];

// 遍历多边形的所有边
// i是当前点,j是前一个点,这样就能形成一条边
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
// 获取多边形当前边的两个端点坐标
let xi = polygon[i][0],
yi = polygon[i][1]; // 当前点
let xj = polygon[j][0],
yj = polygon[j][1]; // 前一个点

// 这行是算法的核心,检查射线与边的相交情况
let intersect =
yi > y !== yj > y && // 第一个条件:确保边跨过射线
x < ((xj - xi) * (y - yi)) / (yj - yi) + xi; // 第二个条件:确保交点在点的右侧

// 每次找到一个有效交点,就翻转inside的值
if (intersect) inside = !inside;
}

return inside;
}

算法原理解析

这个算法看起来很简洁,但蕴含了精妙的几何原理:

  1. 射线法的核心思想:从点发出一条水平射线,统计它与多边形边界的交点数。奇数交点表示在内部,偶数交点表示在外部。

  2. 判断边是否跨过射线:yi > y !== yj > y 这个条件确保了当前边的一个端点在射线上方,另一个在射线下方,即这条边跨过了水平射线。

  3. 计算交点位置:x < ((xj - xi) * (y - yi)) / (yj - yi) + xi 这个表达式计算了边与射线的交点的 x 坐标,然后判断这个交点是否在原点的右侧。

  4. 奇偶切换:每找到一个交点,就翻转 inside 的状态,最终得到点的内外状态。

在 Cesium 中应用

在 Cesium 中应用这个算法,我们需要解决两个问题:

  1. 如何获取屏幕坐标对应的地理坐标
  2. 如何结合洋流数据与多边形区域判断

获取地理坐标

通过 Cesium 提供的 API,我们可以将屏幕坐标转换为地球表面的经纬度坐标:

1
2
3
4
5
6
7
8
9
10
// 从相机位置通过屏幕点发射射线,计算与地球表面的交点
const cartesian = viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(x, y),
viewer.scene.globe.ellipsoid
);

// 转换为经纬度坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);

这段代码的作用是将屏幕上的 2D 坐标点(x, y)转换为地球表面上的 3D 坐标点,然后获取其经纬度值。

结合洋流数据与区域判断

最关键的部分是将洋流数据与多边形区域判断结合起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function fieldisDefined(x, y) {
// 首先获取屏幕坐标对应的地理坐标
const cartesian = _oceanmap.viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(x, y),
_oceanmap.viewer.scene.globe.ellipsoid
);

if (!cartesian) return false;

// 转换为经纬度坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);

// 判断点是否在原有海洋区域内
const isInOcean = field(x, y)[2] !== null;

// 判断点是否在指定的多边形区域内
const isInBoundary = isPointInPolygon([lon, lat], _oceanmap.bounds);

// 同时满足两个条件:在海洋中且在边界内
return isInOcean && isInBoundary;
}

这个函数完成了两项重要检查:

  1. 确保点位于海洋中(通过洋流数据判断)
  2. 确保点位于我们指定的多边形区域内

只有同时满足这两个条件,我们才会在该位置渲染洋流粒子。

实施效果

通过这种方法,我们可以将洋流的显示精确限制在我们感兴趣的区域内,如南海特定海域、台湾海峡或者某个港湾等。这不仅提升了视觉效果,还优化了性能,因为只有部分粒子需要计算和渲染。

完整代码实现

下面是创建带区域限制的洋流场的完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 创建洋流场并设置边界
field = earthCtrl.factory.createOceanField({
name: "ocean1",
type: "ocean",
upath: "./data/MIT.0000000001_UVEL_1.dat",
vpath: "./data/MIT.0000000001_VVEL_1.dat",
bounds: [
[112.27127810594834, 19.225753080468763],
[110.03847818240308, 17.427581061438815],
[111.95290379190146, 12.594412699775589],
[118.57050910216816, 13.584956070591218],
[118.77371765142777, 19.065063097417365],
[112.27127810594834, 19.225753080468763],
],
});

// 判断点是否在多边形内的函数
function isPointInPolygon(point, polygon) {
let inside = false;
let x = point[0],
y = point[1];

for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
let xi = polygon[i][0],
yi = polygon[i][1];
let xj = polygon[j][0],
yj = polygon[j][1];

let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}

return inside;
}

// 判断点是否在洋流场定义区域内,并且在指定边界内
function fieldisDefined(x, y) {
// 首先获取屏幕坐标对应的地理坐标
const cartesian = _oceanmap.viewer.camera.pickEllipsoid(
new Cesium.Cartesian2(x, y),
_oceanmap.viewer.scene.globe.ellipsoid
);

if (!cartesian) return false;

// 转换为经纬度坐标
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const lon = Cesium.Math.toDegrees(cartographic.longitude);
const lat = Cesium.Math.toDegrees(cartographic.latitude);

// 判断点是否在原有海洋区域内
const isInOcean = field(x, y)[2] !== null;

// 判断点是否在指定的多边形区域内
const isInBoundary = isPointInPolygon([lon, lat], _oceanmap.bounds);

// 同时满足两个条件:在海洋中且在边界内
return isInOcean && isInBoundary;
}

总结

通过结合点在多边形内的判断算法与 Cesium 的坐标转换功能,我们成功实现了洋流的区域限制显示。这种方法既保证了视觉效果的聚焦,又优化了性能。在实际应用中,可以根据需要调整多边形边界,灵活控制洋流的显示区域。

作者

wangyawei

发布于

2025-03-16

更新于

2025-03-21

许可协议