cesium-near-view-rectangle 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +93 -0
  2. package/package.json +33 -0
  3. package/src/index.js +152 -0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # cesium-near-view-rectangle
2
+
3
+ Compute the **near-end visible rectangle** of a Cesium camera by screen-space ray sampling.
4
+
5
+ When the camera is tilted (e.g. pitched at −30°), the built-in `camera.computeViewRectangle()` often returns an oversized rectangle that includes the far horizon. This package focuses the rectangle on the **near-end ground** that is actually close to the camera, giving a much tighter and more useful bounding box.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install cesium-near-view-rectangle
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```js
16
+ import { computeNearViewRectangle, computeAutoMaxDistKm } from 'cesium-near-view-rectangle';
17
+
18
+ // Cesium is whatever you already have — window.Cesium (CDN) or `import * as Cesium from 'cesium'`
19
+
20
+ // Basic usage
21
+ const result = computeNearViewRectangle(Cesium, viewer);
22
+
23
+ if (result) {
24
+ const { rect, hitCount, missCount, total } = result;
25
+ console.log('west:', Cesium.Math.toDegrees(rect.west));
26
+ console.log('east:', Cesium.Math.toDegrees(rect.east));
27
+ console.log('south:', Cesium.Math.toDegrees(rect.south));
28
+ console.log('north:', Cesium.Math.toDegrees(rect.north));
29
+ } else {
30
+ console.warn('Camera may be pointing at the sky — no valid ground found.');
31
+ }
32
+
33
+ // Recommended: auto distance filter based on camera height
34
+ const result2 = computeNearViewRectangle(Cesium, viewer, {
35
+ rows: 14,
36
+ cols: 14,
37
+ nearRatio: 0.3,
38
+ maxDistKm: computeAutoMaxDistKm(Cesium, viewer),
39
+ });
40
+ ```
41
+
42
+ ## API
43
+
44
+ ### `computeNearViewRectangle(Cesium, viewer, options?)`
45
+
46
+ | Parameter | Type | Default | Description |
47
+ |---|---|---|---|
48
+ | `Cesium` | `object` | — | Cesium namespace (`window.Cesium` or `import * as Cesium from 'cesium'`) |
49
+ | `viewer` | `Cesium.Viewer` | — | Cesium Viewer instance |
50
+ | `options.rows` | `number` | `12` | Number of sample rows |
51
+ | `options.cols` | `number` | `12` | Number of sample columns |
52
+ | `options.nearRatio` | `number` | `0.35` | Start row as a fraction of screen height (0 = top, 1 = bottom). Higher values sample closer to the camera. |
53
+ | `options.maxDistKm` | `number \| null` | `null` | Distance filter threshold in km. Points farther than this are discarded. Use `computeAutoMaxDistKm(Cesium, viewer)` for automatic tuning. |
54
+
55
+ **Returns** `{ rect, hitCount, missCount, total } | null`
56
+
57
+ | Field | Type | Description |
58
+ |---|---|---|
59
+ | `rect` | `Cesium.Rectangle` | Computed bounding rectangle (radians) |
60
+ | `hitCount` | `number` | Number of screen samples that hit the ground |
61
+ | `missCount` | `number` | Number of samples that missed (sky, too far, etc.) |
62
+ | `total` | `number` | Total number of sample points tried |
63
+
64
+ Returns `null` when fewer than 3 valid ground hits were found (e.g. camera pointing at sky).
65
+
66
+ ---
67
+
68
+ ### `computeAutoMaxDistKm(Cesium, viewer)`
69
+
70
+ Returns a recommended `maxDistKm` value derived from the current camera height:
71
+
72
+ ```
73
+ maxDistKm = max(cameraHeightKm × 3, 200)
74
+ ```
75
+
76
+ ## How it works
77
+
78
+ 1. A grid of screen-space points is generated covering the lower portion of the viewport (controlled by `nearRatio`).
79
+ 2. For each point, a ray is cast from the camera through that pixel:
80
+ - First tries `globe.pick()` (terrain-aware).
81
+ - Falls back to ray–ellipsoid intersection if terrain pick fails.
82
+ 3. Ground positions are filtered by distance and elevation sanity checks.
83
+ 4. `Cesium.Rectangle.fromCartographicArray()` wraps all valid hits into the final rectangle.
84
+
85
+ ## Compatibility
86
+
87
+ - Works with any CesiumJS version that provides `IntersectionTests.rayEllipsoid` (≥ 1.107)
88
+ - No runtime dependencies — bring your own Cesium
89
+ - ESM only
90
+
91
+ ## License
92
+
93
+ MIT
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "cesium-near-view-rectangle",
3
+ "version": "0.0.1",
4
+ "description": "Compute the near-end visible rectangle of a Cesium camera by screen-space ray sampling. More accurate than camera.computeViewRectangle() when the view is tilted.",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "keywords": [
14
+ "cesium",
15
+ "cesiumjs",
16
+ "camera",
17
+ "viewport",
18
+ "rectangle",
19
+ "visible-area",
20
+ "near-view",
21
+ "gis",
22
+ "3d-map"
23
+ ],
24
+ "author": "",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": ""
29
+ },
30
+ "engines": {
31
+ "node": ">=14"
32
+ }
33
+ }
package/src/index.js ADDED
@@ -0,0 +1,152 @@
1
+ /**
2
+ * 射线与 WGS84 椭球体求交(Cesium 1.107+ 兼容)
3
+ * @param {object} Cesium Cesium 命名空间对象
4
+ * @param {object} ray
5
+ * @returns {object | null}
6
+ */
7
+ function rayEllipsoidIntersect(Cesium, ray) {
8
+ const ellipsoid = Cesium.Ellipsoid.WGS84;
9
+ const intersection = Cesium.IntersectionTests.rayEllipsoid(ray, ellipsoid);
10
+ if (!intersection) return null;
11
+ const t = intersection.start >= 0 ? intersection.start : intersection.stop;
12
+ if (t < 0) return null;
13
+ return Cesium.Ray.getPoint(ray, t);
14
+ }
15
+
16
+ /**
17
+ * 从屏幕坐标拾取地面交点
18
+ * 优先 globe.pick(含地形),失败则退回椭球体求交
19
+ * @param {object} Cesium
20
+ * @param {object} screenPos
21
+ * @param {object} camera
22
+ * @param {object} globe
23
+ * @param {object} scene
24
+ * @returns {object | null}
25
+ */
26
+ function pickGroundPoint(Cesium, screenPos, camera, globe, scene) {
27
+ const ray = camera.getPickRay(screenPos);
28
+ if (!ray) return null;
29
+ const cart3 = globe.pick(ray, scene);
30
+ if (cart3) return cart3;
31
+ return rayEllipsoidIntersect(Cesium, ray);
32
+ }
33
+
34
+ /**
35
+ * 生成屏幕采样点网格
36
+ * nearRatio:只采样屏幕 [nearRatio*H, H] 高度范围(下方 = 近端地面)
37
+ * @param {object} Cesium
38
+ * @param {number} rows
39
+ * @param {number} cols
40
+ * @param {number} nearRatio
41
+ * @param {HTMLCanvasElement} canvas
42
+ * @returns {object[]}
43
+ */
44
+ function buildSamplePoints(Cesium, rows, cols, nearRatio, canvas) {
45
+ const W = canvas.width;
46
+ const H = canvas.height;
47
+ const yStart = Math.floor(H * nearRatio);
48
+ const points = [];
49
+
50
+ for (let r = 0; r <= rows; r++) {
51
+ const y = yStart + (H - yStart) * (r / rows);
52
+ for (let c = 0; c <= cols; c++) {
53
+ points.push(new Cesium.Cartesian2(W * (c / cols), y));
54
+ }
55
+ }
56
+ // 强制补四角和中心点
57
+ points.push(new Cesium.Cartesian2(0, 0));
58
+ points.push(new Cesium.Cartesian2(W, 0));
59
+ points.push(new Cesium.Cartesian2(0, H));
60
+ points.push(new Cesium.Cartesian2(W, H));
61
+ points.push(new Cesium.Cartesian2(W / 2, H / 2));
62
+ return points;
63
+ }
64
+
65
+ /**
66
+ * 根据相机高度自动推算合理的最大距离(km)
67
+ *
68
+ * @param {object} Cesium Cesium 命名空间对象(window.Cesium 或 import * as Cesium)
69
+ * @param {object} viewer Cesium Viewer 实例
70
+ * @returns {number}
71
+ */
72
+ export function computeAutoMaxDistKm(Cesium, viewer) {
73
+ const carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(
74
+ viewer.camera.position,
75
+ );
76
+ if (!carto) return 200;
77
+ return Math.max((carto.height / 1000) * 3, 200);
78
+ }
79
+
80
+ /**
81
+ * 计算相机近端可视矩形区域
82
+ *
83
+ * @param {object} Cesium Cesium 命名空间对象(window.Cesium 或 import * as Cesium)
84
+ * @param {object} viewer Cesium Viewer 实例
85
+ * @param {object} [options]
86
+ * @param {number} [options.rows=12] 采样行数
87
+ * @param {number} [options.cols=12] 采样列数
88
+ * @param {number} [options.nearRatio=0.35] 采样起始行比例(0=顶部,1=底部),
89
+ * 值越大越靠近相机近端
90
+ * @param {number|null} [options.maxDistKm=null] 距离过滤阈值(km),null 表示不过滤;
91
+ * 推荐使用 computeAutoMaxDistKm(Cesium, viewer)
92
+ * @returns {{ rect: object, hitCount: number, missCount: number, total: number } | null}
93
+ * 成功时返回矩形结果,视角朝向天空等无效情况返回 null
94
+ */
95
+ export function computeNearViewRectangle(Cesium, viewer, options) {
96
+ options = options || {};
97
+ const rows = options.rows !== undefined ? options.rows : 12;
98
+ const cols = options.cols !== undefined ? options.cols : 12;
99
+ const nearRatio = options.nearRatio !== undefined ? options.nearRatio : 0.35;
100
+ const maxDistKm = options.maxDistKm !== undefined ? options.maxDistKm : null;
101
+
102
+ const camera = viewer.camera;
103
+ const scene = viewer.scene;
104
+ const globe = scene.globe;
105
+ const canvas = viewer.canvas;
106
+
107
+ const samplePts = buildSamplePoints(Cesium, rows, cols, nearRatio, canvas);
108
+ const cartoList = [];
109
+ let hitCount = 0;
110
+ let missCount = 0;
111
+
112
+ for (const sp of samplePts) {
113
+ const cart3 = pickGroundPoint(Cesium, sp, camera, globe, scene);
114
+ if (!cart3) {
115
+ missCount++;
116
+ continue;
117
+ }
118
+
119
+ const carto = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cart3);
120
+ if (!carto) {
121
+ missCount++;
122
+ continue;
123
+ }
124
+
125
+ // 距离过滤:剔除地平线附近的远端点
126
+ if (maxDistKm !== null) {
127
+ const distM = Math.sqrt(
128
+ Math.pow(cart3.x - camera.position.x, 2) +
129
+ Math.pow(cart3.y - camera.position.y, 2) +
130
+ Math.pow(cart3.z - camera.position.z, 2),
131
+ );
132
+ if (distM > maxDistKm * 1000) {
133
+ missCount++;
134
+ continue;
135
+ }
136
+ }
137
+
138
+ // 高度合理性过滤
139
+ if (carto.height > 15000 || carto.height < -500) {
140
+ missCount++;
141
+ continue;
142
+ }
143
+
144
+ cartoList.push(carto);
145
+ hitCount++;
146
+ }
147
+
148
+ if (cartoList.length < 3) return null;
149
+
150
+ const rect = Cesium.Rectangle.fromCartographicArray(cartoList);
151
+ return { rect, hitCount, missCount, total: samplePts.length };
152
+ }