huweili-cesium 1.1.13 → 1.1.14

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.
package/index.vue CHANGED
@@ -205,6 +205,9 @@ const initCesium = async () => {
205
205
  // 开启/关闭 动态大气光照从太阳开始
206
206
  map.scene.globe.dynamicAtmosphereLightingFromSun = mapOptions.scene.globe.dynamicAtmosphereLightingFromSun;
207
207
 
208
+ // 设置场景模式(2D/3D)
209
+ const sceneMode = mapOptions.scene.sceneMode || '3D';
210
+ mapStore.setSceneMode(sceneMode, props.mapId);
208
211
  // 设置场景模式(2D/3D)时候的 正交投影(2D)还是透视投影(3D)
209
212
  switch(mapOptions.scene.sceneMode){
210
213
  case '2D':
@@ -229,10 +232,6 @@ const initCesium = async () => {
229
232
  alt: mapOptions.scene.center.alt,
230
233
  }, props.mapId)
231
234
  mapStore.setMap(map, props.mapId)
232
-
233
- // 设置场景模式(2D/3D)
234
- const sceneMode = mapOptions.scene.sceneMode || '3D';
235
- mapStore.setSceneMode(sceneMode, props.mapId);
236
235
 
237
236
  // 初始化半球区域
238
237
  const center = mapOptions.scene.center || {}
package/js/basis.js CHANGED
@@ -10,19 +10,6 @@
10
10
  */
11
11
  import * as Cesium from 'cesium'
12
12
  import { useMapStore } from './stores/mapStore.js'
13
- import {
14
- bindFrustumConstructors,
15
- createOrthographicFrustum,
16
- createPerspectiveFrustum,
17
- ensure3DCompatibleFrustum,
18
- detectDuplicateCesium,
19
- getCanvasAspectRatio,
20
- isOrthographicFrustum,
21
- isPerspectiveFrustum,
22
- isValid3DFrustum,
23
- warnDuplicateCesiumOnce,
24
- } from './utils/frustumCompat.js'
25
- import { getCesium } from './utils/cesiumContext.js'
26
13
 
27
14
  export function basicConfig() {
28
15
 
@@ -135,7 +122,7 @@ export function basicConfig() {
135
122
  const finalTargetAlt = targetAlt !== undefined ? targetAlt : (centerInfo.targetAlt || 0)
136
123
 
137
124
  // 检查当前是正交投影(2D)还是透视投影(3D)
138
- const isOrthographic = isOrthographicFrustum(map.camera.frustum)
125
+ const isOrthographic = map.camera.frustum instanceof Cesium.OrthographicFrustum
139
126
 
140
127
  if (isOrthographic) {
141
128
  const orthoWidth = computeOrthoWidthFromHeight(map, finalAltitude)
@@ -201,20 +188,12 @@ export function basicConfig() {
201
188
  const restorePerspectiveFrustum = (map) => {
202
189
  if (!map) return
203
190
 
204
- bindFrustumConstructors(map)
205
191
  const currentFrustum = map.camera.frustum
206
-
207
- if (isPerspectiveFrustum(currentFrustum)) {
208
- currentFrustum.fov = getPerspectiveVerticalFovRadians()
209
- currentFrustum.aspectRatio = getCanvasAspectRatio(map)
210
- return
211
- }
212
-
213
- const perspectiveFrustum = createPerspectiveFrustum(map, currentFrustum)
192
+ const perspectiveFrustum = new Cesium.PerspectiveFrustum()
214
193
  perspectiveFrustum.fov = getPerspectiveVerticalFovRadians()
215
- perspectiveFrustum.aspectRatio = getCanvasAspectRatio(map)
216
- perspectiveFrustum.near = currentFrustum?.near || 1.0
217
- perspectiveFrustum.far = currentFrustum?.far || 10000000.0
194
+ perspectiveFrustum.aspectRatio = map.canvas.clientWidth / map.canvas.clientHeight
195
+ perspectiveFrustum.near = currentFrustum.near || 1.0
196
+ perspectiveFrustum.far = currentFrustum.far || 10000000.0
218
197
  map.camera.frustum = perspectiveFrustum
219
198
  }
220
199
 
@@ -259,36 +238,28 @@ export function basicConfig() {
259
238
  const applyOrthographicFrustum = (map, viewHeight, orthoWidth) => {
260
239
  if (!map) return
261
240
 
262
- bindFrustumConstructors(map)
263
- warnDuplicateCesiumOnce(map)
264
- // 双份 cesium 时勿替换为正交视锥(instanceof 与引擎不一致);保持透视 + 俯视姿态
265
- if (detectDuplicateCesium(map, getCesium())) {
266
- return
267
- }
268
-
269
- const current = map.camera.frustum
241
+ const canvas = map.canvas
242
+ const frustum = new Cesium.OrthographicFrustum()
243
+ const width = canvas.clientWidth
244
+ const height = canvas.clientHeight
270
245
 
271
246
  let widthValue
272
247
  if (Number.isFinite(orthoWidth) && orthoWidth > 0) {
273
248
  widthValue = orthoWidth
274
249
  } else if (Number.isFinite(viewHeight) && viewHeight > 0) {
275
250
  widthValue = computeOrthoWidthFromHeight(map, viewHeight)
276
- } else if (isPerspectiveFrustum(current)) {
277
- const cameraHeight = map.camera.positionCartographic?.height ?? 10000
251
+ } else if (map.scene.camera.frustum instanceof Cesium.PerspectiveFrustum) {
252
+ const cameraHeight = map.scene.camera.positionCartographic?.height ?? 10000
278
253
  widthValue = computeOrthoWidthFromHeight(map, cameraHeight)
279
254
  } else {
280
- widthValue = current?.width || 20000
255
+ widthValue = map.scene.camera.frustum.width || 20000
281
256
  }
282
257
 
283
- const frustum = isOrthographicFrustum(current)
284
- ? current
285
- : createOrthographicFrustum()
286
-
287
258
  frustum.width = widthValue
288
- frustum.aspectRatio = getCanvasAspectRatio(map)
259
+ frustum.aspectRatio = width / height
289
260
  frustum.near = 0.1
290
261
  frustum.far = 10000000.0
291
- map.camera.frustum = frustum
262
+ map.scene.camera.frustum = frustum
292
263
  }
293
264
 
294
265
  /**
@@ -421,7 +392,7 @@ export function basicConfig() {
421
392
  }
422
393
 
423
394
  const camera = map.camera
424
- const isOrthographic = isOrthographicFrustum(camera.frustum)
395
+ const isOrthographic = camera.frustum instanceof Cesium.OrthographicFrustum
425
396
 
426
397
  let currentMode
427
398
  if (isOrthographic) {
@@ -448,7 +419,7 @@ export function basicConfig() {
448
419
  */
449
420
  const getEffective2DOrthoWidth = (map) => {
450
421
  const frustum = map?.camera?.frustum
451
- if (!isOrthographicFrustum(frustum)) return 0
422
+ if (!(frustum instanceof Cesium.OrthographicFrustum)) return 0
452
423
  if (frustum.width > 0) return frustum.width
453
424
 
454
425
  const cameraHeight = map?.camera?.positionCartographic?.height
@@ -612,7 +583,7 @@ export function basicConfig() {
612
583
  const { distance2D, distance3D } = options
613
584
 
614
585
  const camera = map.camera
615
- const isOrthographic = isOrthographicFrustum(camera.frustum)
586
+ const isOrthographic = camera.frustum instanceof Cesium.OrthographicFrustum
616
587
 
617
588
  let currentMode
618
589
  if (isOrthographic) {
@@ -626,35 +597,6 @@ export function basicConfig() {
626
597
  return { success: true, currentMode }
627
598
  }
628
599
 
629
- /**
630
- * 场景模式切换(如 Cesium 自带 sceneModePicker)后修复不兼容的视锥体
631
- * @param {import('cesium').Viewer} map
632
- * @param {string=} mapId
633
- */
634
- const setupFrustumGuard = (map, mapId) => {
635
- if (!map?.scene?.morphComplete) return
636
-
637
- map.scene.morphComplete.addEventListener(() => {
638
- const mode = map.scene.mode
639
- if (
640
- mode !== Cesium.SceneMode.SCENE3D &&
641
- mode !== Cesium.SceneMode.COLUMBUS_VIEW
642
- ) {
643
- return
644
- }
645
-
646
- if (isValid3DFrustum(map.camera.frustum)) return
647
-
648
- const preferOrtho = mapStore.getSceneMode(mapId) === '2D'
649
- ensure3DCompatibleFrustum(map, preferOrtho)
650
- if (preferOrtho) {
651
- applyOrthographicFrustum(map)
652
- } else {
653
- restorePerspectiveFrustum(map)
654
- }
655
- })
656
- }
657
-
658
600
  return {
659
601
  isInChina,
660
602
  mouseController,
@@ -675,8 +617,6 @@ export function basicConfig() {
675
617
  set2DView,
676
618
  set3DView,
677
619
  applyOrthographicFrustum,
678
- restorePerspectiveFrustum,
679
- bindFrustumConstructors,
680
- setupFrustumGuard,
620
+ restorePerspectiveFrustum
681
621
  }
682
622
  }
package/js/hemisphere.js CHANGED
@@ -1,360 +1,358 @@
1
- /**
2
- * 半球体实体管理模块
3
- *
4
- * 提供在Cesium地图上创建、更新、移动和删除半球体实体的功能
5
- * 支持自定义半球体的位置、半径、颜色等属性
6
- *
7
- * @author huweili
8
- * @email czxyhuweili@163.com
9
- * @version 1.0.0
10
- */
11
- import * as Cesium from 'cesium'
12
- import { BaseConfig } from './config/index.js';
13
- import { useMapStore } from './stores/mapStore.js'
14
- import { createViewerColor, getCesium } from './utils/cesiumContext.js'
15
-
16
- /**
17
- * 半球体实体管理配置函数
18
- * 初始化半球体管理模块,返回半球体操作相关方法
19
- *
20
- * @returns {Object} 半球体操作方法集合
21
- */
22
- export function hemisphereConfig() {
23
-
24
- // 获取地图store实例
25
- const mapStore = useMapStore()
26
-
27
- // 从配置中心读取 labelEntity 应该处于的角度,并确保在0到90度之间
28
- const radians = Cesium.Math.toRadians(BaseConfig.hemisphereLabelAngle);
29
- const hemisphereLabelAngle = Math.max(0, Math.min(Math.PI / 2, radians));
30
-
31
- /**
32
- * 格式化半球半径标签
33
- * 将米为单位的半径值转换为千米,并添加km单位
34
- * @param {number|string} radius - 半径值(米)
35
- * @returns {string} - 格式化后的半径标签,如 "3km"
36
- */
37
- const formatHemisphereRadiusLabel = (radius) => {
38
- const radiusValue = Number(radius);
39
- if (!Number.isFinite(radiusValue)) {
40
- return '';
41
- }
42
- return `${radiusValue / 1000}km`;
43
- }
44
-
45
- /**
46
- * 获取半球标签文本
47
- * 组合标签名称和半径信息,生成完整的半球标签显示文本
48
- * @param {string} label - 半球标签名称
49
- * @param {number|string} radius - 半径值(米)
50
- * @returns {string} - 完整的标签文本,如 "探测区\n半径:3km"
51
- */
52
- const getHemisphereLabelText = (label, radius) => {
53
- const radiusLabel = formatHemisphereRadiusLabel(radius);
54
- return radiusLabel ? `${label}\n${radiusLabel}` : label;
55
- }
56
-
57
- /**
58
- * 【创建并设置半球体实体】
59
- * 在指定位置创建一个半球体实体,支持自定义半径和颜色
60
- * 半球体默认紧贴地面(高度为0),并设置为半透明效果
61
- *
62
- * @param {Object} options 半球体配置参数
63
- * @param {string} options.id 半球体唯一标识
64
- * @param {number[]} options.center 半球体中心经纬度坐标 [longitude, latitude]
65
- * @param {number} options.radius 半球体半径(米)
66
- * @param {string} options.sphericalColor 半球体颜色(CSS颜色字符串)
67
- * @param {string} options.label 半球体标签文本(可选)
68
- * @param {string} options.labelBgColor 半球体标签背景颜色(CSS颜色字符串)
69
- * @returns {Cesium.Entity|null} 创建的半球体实体,若创建失败则返回null
70
- */
71
- const setHemisphere = (options) => {
72
- const { mapId } = options
73
- const map = mapStore.getMap(mapId)
74
- if (!map) {
75
- console.error('地图实例不存在')
76
- return null
77
- }
78
-
79
- if (mapStore.getGraphicMap(`${options.id}`, mapId)) {
80
- console.warn(`id: ${options.id} 半球体实体已存在`)
81
- return null
82
- }
83
-
84
- // 经纬度转Cesium笛卡尔坐标(球心定位)
85
- // 确保球心的高度为0,紧贴地面
86
- const Cs = getCesium(mapId)
87
- const centerCartesian = Cs.Cartesian3.fromDegrees(options.center[0], options.center[1], 0);
88
-
89
- // 使用Entity方式创建半球体(更稳定可靠)
90
- const hemisphereEntity = map.entities.add({
91
- position: centerCartesian,
92
- ellipsoid: {
93
- radii: new Cs.Cartesian3(options.radius, options.radius, options.radius),
94
- material: createViewerColor(mapId, options.sphericalColor),
95
- // 使用maximumCone和minimumCone创建半球
96
- maximumCone: 0, // 上半球(z轴正方向)
97
- minimumCone: Math.PI / 2 // 从z轴正方向到水平面(90度)
98
- }
99
- });
100
-
101
- // 保存原始参数,用于后面计算
102
- hemisphereEntity._originalOptions = { ...options };
103
-
104
- // 计算标签位置(半球体表面右侧)
105
- // 使用笛卡尔坐标系确保标签始终在半球体外侧
106
- // 使用全局变量 hemisphereLabelAngle 控制仰角
107
- const localX = options.radius * Math.cos(hemisphereLabelAngle);
108
- const localZ = options.radius * Math.sin(hemisphereLabelAngle);
109
-
110
- // 确保最小高度
111
- const safeZ = Math.max(localZ, 50);
112
-
113
- // 创建局部偏移向量
114
- const localOffset = new Cesium.Cartesian3(localX, 0, safeZ);
115
-
116
- // 将局部偏移转换到世界坐标系
117
- const enuToFixed = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian, map.scene.globe.ellipsoid);
118
-
119
- // 计算最终标签位置:球心 + 偏移向量
120
- const labelPosition = Cesium.Matrix4.multiplyByPoint(enuToFixed, localOffset, new Cesium.Cartesian3());
121
-
122
- // 创建独立的文本框Entity
123
- let labelEntity = null;
124
- if (options.label) {
125
- labelEntity = map.entities.add({
126
- position: new Cesium.CallbackProperty(() => {
127
- return labelPosition;
128
- }, false),
129
- label: {
130
- text: getHemisphereLabelText(options.label, options.radius),
131
- font: '10px Microsoft YaHei',
132
- fillColor: Cesium.Color.WHITE,
133
- // style: Cesium.LabelStyle.FILL_AND_OUTLINE,
134
- // 确保背景显示的关键属性
135
- showBackground: true,
136
- backgroundColor: Cesium.Color.fromCssColorString(options.labelBgColor), // 背景颜色
137
- backgroundPadding: new Cesium.Cartesian2(3, 3),
138
- // 定位属性 - 标签在右手边
139
- verticalOrigin: Cesium.VerticalOrigin.CENTER,
140
- horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
141
- pixelOffset: new Cesium.Cartesian2(10, 0),
142
- // 其他属性
143
- eyeOffset: new Cesium.Cartesian3(0, 0, 0),
144
- // disableDepthTestDistance: Number.POSITIVE_INFINITY,
145
- distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 100000)
146
- }
147
- });
148
- // 将文本框Entity也缓存到store中,使用id+_label作为键
149
- mapStore.setGraphicMap(`${options.id}_label`, labelEntity, mapId);
150
- }
151
-
152
- // 将半球体缓存到 graphicMap 中,防止重复创建
153
- mapStore.setGraphicMap(`${options.id}`, hemisphereEntity, mapId);
154
-
155
- // 返回创建的半球体实体,方便调用者使用
156
- return hemisphereEntity;
157
- }
158
-
159
- /**
160
- * 【移动半球体实体位置】
161
- * 更新指定ID的半球体实体到新的经纬度位置
162
- *
163
- * @param {Object} options 移动配置参数
164
- * @param {string} options.hemisphereId 半球体唯一标识
165
- * @param {number[]} options.center 新的中心经纬度坐标 [longitude, latitude]
166
- * @returns {null} 无返回值
167
- */
168
- const moveHemisphere = (options) => {
169
- const { mapId } = options
170
- const map = mapStore.getMap(mapId)
171
- if (!map) {
172
- console.error('地图实例不存在')
173
- return null
174
- }
175
-
176
- const hemisphereEntity = mapStore.getGraphicMap(`${options.hemisphereId}`, mapId)
177
- if (!hemisphereEntity) {
178
- console.warn(`id: ${options.hemisphereId} 半球体实体不存在`)
179
- return null
180
- }
181
-
182
- // 更新半球体位置
183
- const newPosition = Cesium.Cartesian3.fromDegrees(options.center[0], options.center[1], 0);
184
- hemisphereEntity.position = newPosition;
185
-
186
- // 检查是否存在独立的文本框Entity
187
- const labelEntity = mapStore.getGraphicMap(`${options.hemisphereId}_label`, mapId);
188
- if (labelEntity) {
189
- // 从保存的原始参数中获取半径值
190
- let radius = 1000; // 默认半径值
191
- if (hemisphereEntity._originalOptions && hemisphereEntity._originalOptions.radius) {
192
- radius = hemisphereEntity._originalOptions.radius;
193
- }
194
- // 计算标签新位置(半球体表面右侧)
195
- const localX = radius * Math.cos(hemisphereLabelAngle);
196
- const localZ = radius * Math.sin(hemisphereLabelAngle);
197
- const safeZ = Math.max(localZ, 50);
198
-
199
- const localOffset = new Cesium.Cartesian3(localX, 0, safeZ);
200
- const currentPosition = hemisphereEntity.position.getValue(Cesium.JulianDate.now());
201
-
202
- const enuToFixed = Cesium.Transforms.eastNorthUpToFixedFrame(currentPosition, map.scene.globe.ellipsoid);
203
- const newLabelPosition = Cesium.Matrix4.multiplyByPoint(enuToFixed, localOffset, new Cesium.Cartesian3());
204
-
205
- // 使用CallbackProperty确保位置平滑更新
206
- labelEntity.position = new Cesium.CallbackProperty(() => {
207
- return newLabelPosition;
208
- }, false);
209
- }
210
- }
211
-
212
- /**
213
- * 【更新半球体实体半径】
214
- * 修改指定ID的半球体实体的半径大小
215
- *
216
- * @param {Object} options 更新配置参数
217
- * @param {string} options.mapId 地图实例ID
218
- * @param {string} options.hemisphereId 半球体唯一标识
219
- * @param {number} options.radius 新的半球体半径(米)
220
- * @returns {null} 无返回值
221
- */
222
- const updateHemisphere = (options) => {
223
- const { mapId } = options
224
- const map = mapStore.getMap(mapId)
225
- if (!map) {
226
- console.error('地图实例不存在')
227
- return null
228
- }
229
-
230
- const hemisphereEntity = mapStore.getGraphicMap(`${options.hemisphereId}`, mapId)
231
- if (!hemisphereEntity) {
232
- console.error(`id: ${options.hemisphereId} 半球体实体不存在`)
233
- return null
234
- }
235
-
236
- // 更新半球体半径
237
- hemisphereEntity.ellipsoid.radii = new Cesium.Cartesian3(options.radius, options.radius, options.radius);
238
-
239
- // 更新保存的原始参数中的半径值
240
- if (hemisphereEntity._originalOptions) {
241
- hemisphereEntity._originalOptions.radius = options.radius;
242
- }
243
-
244
- // 检查是否存在独立的文本框Entity
245
- const labelEntity = mapStore.getGraphicMap(`${options.hemisphereId}_label`, mapId);
246
- if (labelEntity) {
247
- // 更新标签中的半径值
248
- const originalLabel = hemisphereEntity._originalOptions?.label;
249
- if (originalLabel && labelEntity.label) {
250
- labelEntity.label.text = getHemisphereLabelText(originalLabel, options.radius);
251
- }
252
-
253
- // 计算标签新位置(半球体表面右侧)
254
- const localX = options.radius * Math.cos(hemisphereLabelAngle);
255
- const localZ = options.radius * Math.sin(hemisphereLabelAngle);
256
- const safeZ = Math.max(localZ, 50);
257
-
258
- const localOffset = new Cesium.Cartesian3(localX, 0, safeZ);
259
- const currentPosition = hemisphereEntity.position.getValue(Cesium.JulianDate.now());
260
-
261
- const enuToFixed = Cesium.Transforms.eastNorthUpToFixedFrame(currentPosition, map.scene.globe.ellipsoid);
262
- const newLabelPosition = Cesium.Matrix4.multiplyByPoint(enuToFixed, localOffset, new Cesium.Cartesian3());
263
-
264
- // 使用CallbackProperty确保位置平滑更新
265
- labelEntity.position = new Cesium.CallbackProperty(() => {
266
- return newLabelPosition;
267
- }, false);
268
- }
269
- }
270
-
271
- /**
272
- * 解析半球唯一标识(与 setHemisphere id 一致,兼容 hemisphereId
273
- */
274
- const resolveHemisphereId = (options) => options.id ?? options.hemisphereId
275
-
276
- /**
277
- * 【控制半球体显示/隐藏】
278
- * 根据 mapId 与半球唯一标识,同时控制半球体实体及其标签的可见性
279
- *
280
- * @param {Object} options
281
- * @param {string} options.mapId 地图实例ID
282
- * @param {string} options.id 半球体唯一标识(与 setHemisphere 的 id 一致)
283
- * @param {string} [options.hemisphereId] id,二选一
284
- * @param {boolean} [options.visible=true] 是否显示
285
- * @returns {{ success: boolean }} 操作结果
286
- */
287
- const setHemisphereVisible = (options) => {
288
- const { mapId, visible = true } = options
289
- const hemisphereId = resolveHemisphereId(options)
290
-
291
- if (!hemisphereId) {
292
- console.error('缺少半球体唯一标识 id / hemisphereId')
293
- return { success: false }
294
- }
295
-
296
- const map = mapStore.getMap(mapId)
297
- if (!map) {
298
- console.error('地图实例不存在')
299
- return { success: false }
300
- }
301
-
302
- const hemisphereEntity = mapStore.getGraphicMap(hemisphereId, mapId)
303
- if (!hemisphereEntity) {
304
- console.warn(`id: ${hemisphereId} 半球体实体不存在`)
305
- return { success: false }
306
- }
307
-
308
- hemisphereEntity.show = visible
309
-
310
- const labelEntity = mapStore.getGraphicMap(`${hemisphereId}_label`, mapId)
311
- if (labelEntity) {
312
- labelEntity.show = visible
313
- }
314
-
315
- return { success: true }
316
- }
317
-
318
- /**
319
- * 【删除半球体实体】
320
- * 移除指定ID的半球体实体,并从缓存中清除记录
321
- *
322
- * @param {Object} options 删除配置参数
323
- * @param {string} options.hemisphereId 半球体唯一标识
324
- * @returns {null} 无返回值
325
- */
326
- const removeHemisphere = (options) => {
327
- const { mapId } = options
328
- const map = mapStore.getMap(mapId)
329
- if (!map) {
330
- console.error('地图实例不存在')
331
- return null
332
- }
333
-
334
- const hemisphereEntity = mapStore.getGraphicMap(`${options.hemisphereId}`, mapId)
335
- if (!hemisphereEntity) {
336
- console.error(`id: ${options.hemisphereId} 半球体实体不存在`)
337
- return null
338
- }
339
-
340
- // 删除半球体实体
341
- map.entities.remove(hemisphereEntity);
342
- // 从缓存中移除
343
- mapStore.removeGraphicMap(`${options.hemisphereId}`, mapId);
344
-
345
- // 检查并删除独立的文本框Entity
346
- const labelEntity = mapStore.getGraphicMap(`${options.hemisphereId}_label`, mapId);
347
- if (labelEntity) {
348
- map.entities.remove(labelEntity);
349
- mapStore.removeGraphicMap(`${options.hemisphereId}_label`, mapId);
350
- }
351
- }
352
-
353
- return {
354
- setHemisphere,
355
- updateHemisphere,
356
- removeHemisphere,
357
- moveHemisphere,
358
- setHemisphereVisible
359
- }
1
+ /**
2
+ * 半球体实体管理模块
3
+ *
4
+ * 提供在Cesium地图上创建、更新、移动和删除半球体实体的功能
5
+ * 支持自定义半球体的位置、半径、颜色等属性
6
+ *
7
+ * @author huweili
8
+ * @email czxyhuweili@163.com
9
+ * @version 1.0.0
10
+ */
11
+ import * as Cesium from 'cesium'
12
+ import { BaseConfig } from './config/index.js';
13
+ import { useMapStore } from './stores/mapStore.js'
14
+
15
+ /**
16
+ * 半球体实体管理配置函数
17
+ * 初始化半球体管理模块,返回半球体操作相关方法
18
+ *
19
+ * @returns {Object} 半球体操作方法集合
20
+ */
21
+ export function hemisphereConfig() {
22
+
23
+ // 获取地图store实例
24
+ const mapStore = useMapStore()
25
+
26
+ // 从配置中心读取 labelEntity 应该处于的角度,并确保在0到90度之间
27
+ const radians = Cesium.Math.toRadians(BaseConfig.hemisphereLabelAngle);
28
+ const hemisphereLabelAngle = Math.max(0, Math.min(Math.PI / 2, radians));
29
+
30
+ /**
31
+ * 格式化半球半径标签
32
+ * 将米为单位的半径值转换为千米,并添加km单位
33
+ * @param {number|string} radius - 半径值(米)
34
+ * @returns {string} - 格式化后的半径标签,如 "3km"
35
+ */
36
+ const formatHemisphereRadiusLabel = (radius) => {
37
+ const radiusValue = Number(radius);
38
+ if (!Number.isFinite(radiusValue)) {
39
+ return '';
40
+ }
41
+ return `${radiusValue / 1000}km`;
42
+ }
43
+
44
+ /**
45
+ * 获取半球标签文本
46
+ * 组合标签名称和半径信息,生成完整的半球标签显示文本
47
+ * @param {string} label - 半球标签名称
48
+ * @param {number|string} radius - 半径值(米)
49
+ * @returns {string} - 完整的标签文本,如 "探测区\n半径:3km"
50
+ */
51
+ const getHemisphereLabelText = (label, radius) => {
52
+ const radiusLabel = formatHemisphereRadiusLabel(radius);
53
+ return radiusLabel ? `${label}\n${radiusLabel}` : label;
54
+ }
55
+
56
+ /**
57
+ * 【创建并设置半球体实体】
58
+ * 在指定位置创建一个半球体实体,支持自定义半径和颜色
59
+ * 半球体默认紧贴地面(高度为0),并设置为半透明效果
60
+ *
61
+ * @param {Object} options 半球体配置参数
62
+ * @param {string} options.id 半球体唯一标识
63
+ * @param {number[]} options.center 半球体中心经纬度坐标 [longitude, latitude]
64
+ * @param {number} options.radius 半球体半径(米)
65
+ * @param {string} options.sphericalColor 半球体颜色(CSS颜色字符串)
66
+ * @param {string} options.label 半球体标签文本(可选)
67
+ * @param {string} options.labelBgColor 半球体标签背景颜色(CSS颜色字符串)
68
+ * @returns {Cesium.Entity|null} 创建的半球体实体,若创建失败则返回null
69
+ */
70
+ const setHemisphere = (options) => {
71
+ const { mapId } = options
72
+ const map = mapStore.getMap(mapId)
73
+ if (!map) {
74
+ console.error('地图实例不存在')
75
+ return null
76
+ }
77
+
78
+ if (mapStore.getGraphicMap(`${options.id}`, mapId)) {
79
+ console.warn(`id: ${options.id} 半球体实体已存在`)
80
+ return null
81
+ }
82
+
83
+ // 经纬度转Cesium笛卡尔坐标(球心定位)
84
+ // 确保球心的高度为0,紧贴地面
85
+ const centerCartesian = Cesium.Cartesian3.fromDegrees(options.center[0], options.center[1], 0);
86
+
87
+ // 使用Entity方式创建半球体(更稳定可靠)
88
+ const hemisphereEntity = map.entities.add({
89
+ position: centerCartesian,
90
+ ellipsoid: {
91
+ radii: new Cesium.Cartesian3(options.radius, options.radius, options.radius),
92
+ material: Cesium.Color.fromCssColorString(options.sphericalColor), // 设置半透明效果,0.5为透明度值(范围0-1)
93
+ // 使用maximumCone和minimumCone创建半球
94
+ maximumCone: 0, // 上半球(z轴正方向)
95
+ minimumCone: Math.PI / 2 // 从z轴正方向到水平面(90度)
96
+ }
97
+ });
98
+
99
+ // 保存原始参数,用于后面计算
100
+ hemisphereEntity._originalOptions = { ...options };
101
+
102
+ // 计算标签位置(半球体表面右侧)
103
+ // 使用笛卡尔坐标系确保标签始终在半球体外侧
104
+ // 使用全局变量 hemisphereLabelAngle 控制仰角
105
+ const localX = options.radius * Math.cos(hemisphereLabelAngle);
106
+ const localZ = options.radius * Math.sin(hemisphereLabelAngle);
107
+
108
+ // 确保最小高度
109
+ const safeZ = Math.max(localZ, 50);
110
+
111
+ // 创建局部偏移向量
112
+ const localOffset = new Cesium.Cartesian3(localX, 0, safeZ);
113
+
114
+ // 将局部偏移转换到世界坐标系
115
+ const enuToFixed = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian, map.scene.globe.ellipsoid);
116
+
117
+ // 计算最终标签位置:球心 + 偏移向量
118
+ const labelPosition = Cesium.Matrix4.multiplyByPoint(enuToFixed, localOffset, new Cesium.Cartesian3());
119
+
120
+ // 创建独立的文本框Entity
121
+ let labelEntity = null;
122
+ if (options.label) {
123
+ labelEntity = map.entities.add({
124
+ position: new Cesium.CallbackProperty(() => {
125
+ return labelPosition;
126
+ }, false),
127
+ label: {
128
+ text: getHemisphereLabelText(options.label, options.radius),
129
+ font: '10px Microsoft YaHei',
130
+ fillColor: Cesium.Color.WHITE,
131
+ // style: Cesium.LabelStyle.FILL_AND_OUTLINE,
132
+ // 确保背景显示的关键属性
133
+ showBackground: true,
134
+ backgroundColor: Cesium.Color.fromCssColorString(options.labelBgColor), // 背景颜色
135
+ backgroundPadding: new Cesium.Cartesian2(3, 3),
136
+ // 定位属性 - 标签在右手边
137
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
138
+ horizontalOrigin: Cesium.HorizontalOrigin.LEFT,
139
+ pixelOffset: new Cesium.Cartesian2(10, 0),
140
+ // 其他属性
141
+ eyeOffset: new Cesium.Cartesian3(0, 0, 0),
142
+ // disableDepthTestDistance: Number.POSITIVE_INFINITY,
143
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, 100000)
144
+ }
145
+ });
146
+ // 将文本框Entity也缓存到store中,使用id+_label作为键
147
+ mapStore.setGraphicMap(`${options.id}_label`, labelEntity, mapId);
148
+ }
149
+
150
+ // 将半球体缓存到 graphicMap 中,防止重复创建
151
+ mapStore.setGraphicMap(`${options.id}`, hemisphereEntity, mapId);
152
+
153
+ // 返回创建的半球体实体,方便调用者使用
154
+ return hemisphereEntity;
155
+ }
156
+
157
+ /**
158
+ * 【移动半球体实体位置】
159
+ * 更新指定ID的半球体实体到新的经纬度位置
160
+ *
161
+ * @param {Object} options 移动配置参数
162
+ * @param {string} options.hemisphereId 半球体唯一标识
163
+ * @param {number[]} options.center 新的中心经纬度坐标 [longitude, latitude]
164
+ * @returns {null} 无返回值
165
+ */
166
+ const moveHemisphere = (options) => {
167
+ const { mapId } = options
168
+ const map = mapStore.getMap(mapId)
169
+ if (!map) {
170
+ console.error('地图实例不存在')
171
+ return null
172
+ }
173
+
174
+ const hemisphereEntity = mapStore.getGraphicMap(`${options.hemisphereId}`, mapId)
175
+ if (!hemisphereEntity) {
176
+ console.warn(`id: ${options.hemisphereId} 半球体实体不存在`)
177
+ return null
178
+ }
179
+
180
+ // 更新半球体位置
181
+ const newPosition = Cesium.Cartesian3.fromDegrees(options.center[0], options.center[1], 0);
182
+ hemisphereEntity.position = newPosition;
183
+
184
+ // 检查是否存在独立的文本框Entity
185
+ const labelEntity = mapStore.getGraphicMap(`${options.hemisphereId}_label`, mapId);
186
+ if (labelEntity) {
187
+ // 从保存的原始参数中获取半径值
188
+ let radius = 1000; // 默认半径值
189
+ if (hemisphereEntity._originalOptions && hemisphereEntity._originalOptions.radius) {
190
+ radius = hemisphereEntity._originalOptions.radius;
191
+ }
192
+ // 计算标签新位置(半球体表面右侧)
193
+ const localX = radius * Math.cos(hemisphereLabelAngle);
194
+ const localZ = radius * Math.sin(hemisphereLabelAngle);
195
+ const safeZ = Math.max(localZ, 50);
196
+
197
+ const localOffset = new Cesium.Cartesian3(localX, 0, safeZ);
198
+ const currentPosition = hemisphereEntity.position.getValue(Cesium.JulianDate.now());
199
+
200
+ const enuToFixed = Cesium.Transforms.eastNorthUpToFixedFrame(currentPosition, map.scene.globe.ellipsoid);
201
+ const newLabelPosition = Cesium.Matrix4.multiplyByPoint(enuToFixed, localOffset, new Cesium.Cartesian3());
202
+
203
+ // 使用CallbackProperty确保位置平滑更新
204
+ labelEntity.position = new Cesium.CallbackProperty(() => {
205
+ return newLabelPosition;
206
+ }, false);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * 【更新半球体实体半径】
212
+ * 修改指定ID的半球体实体的半径大小
213
+ *
214
+ * @param {Object} options 更新配置参数
215
+ * @param {string} options.mapId 地图实例ID
216
+ * @param {string} options.hemisphereId 半球体唯一标识
217
+ * @param {number} options.radius 新的半球体半径(米)
218
+ * @returns {null} 无返回值
219
+ */
220
+ const updateHemisphere = (options) => {
221
+ const { mapId } = options
222
+ const map = mapStore.getMap(mapId)
223
+ if (!map) {
224
+ console.error('地图实例不存在')
225
+ return null
226
+ }
227
+
228
+ const hemisphereEntity = mapStore.getGraphicMap(`${options.hemisphereId}`, mapId)
229
+ if (!hemisphereEntity) {
230
+ console.error(`id: ${options.hemisphereId} 半球体实体不存在`)
231
+ return null
232
+ }
233
+
234
+ // 更新半球体半径
235
+ hemisphereEntity.ellipsoid.radii = new Cesium.Cartesian3(options.radius, options.radius, options.radius);
236
+
237
+ // 更新保存的原始参数中的半径值
238
+ if (hemisphereEntity._originalOptions) {
239
+ hemisphereEntity._originalOptions.radius = options.radius;
240
+ }
241
+
242
+ // 检查是否存在独立的文本框Entity
243
+ const labelEntity = mapStore.getGraphicMap(`${options.hemisphereId}_label`, mapId);
244
+ if (labelEntity) {
245
+ // 更新标签中的半径值
246
+ const originalLabel = hemisphereEntity._originalOptions?.label;
247
+ if (originalLabel && labelEntity.label) {
248
+ labelEntity.label.text = getHemisphereLabelText(originalLabel, options.radius);
249
+ }
250
+
251
+ // 计算标签新位置(半球体表面右侧)
252
+ const localX = options.radius * Math.cos(hemisphereLabelAngle);
253
+ const localZ = options.radius * Math.sin(hemisphereLabelAngle);
254
+ const safeZ = Math.max(localZ, 50);
255
+
256
+ const localOffset = new Cesium.Cartesian3(localX, 0, safeZ);
257
+ const currentPosition = hemisphereEntity.position.getValue(Cesium.JulianDate.now());
258
+
259
+ const enuToFixed = Cesium.Transforms.eastNorthUpToFixedFrame(currentPosition, map.scene.globe.ellipsoid);
260
+ const newLabelPosition = Cesium.Matrix4.multiplyByPoint(enuToFixed, localOffset, new Cesium.Cartesian3());
261
+
262
+ // 使用CallbackProperty确保位置平滑更新
263
+ labelEntity.position = new Cesium.CallbackProperty(() => {
264
+ return newLabelPosition;
265
+ }, false);
266
+ }
267
+ }
268
+
269
+ /**
270
+ * 解析半球唯一标识(与 setHemisphere 的 id 一致,兼容 hemisphereId)
271
+ */
272
+ const resolveHemisphereId = (options) => options.id ?? options.hemisphereId
273
+
274
+ /**
275
+ * 【控制半球体显示/隐藏】
276
+ * 根据 mapId 与半球唯一标识,同时控制半球体实体及其标签的可见性
277
+ *
278
+ * @param {Object} options
279
+ * @param {string} options.mapId 地图实例ID
280
+ * @param {string} options.id 半球体唯一标识(与 setHemisphere 的 id 一致)
281
+ * @param {string} [options.hemisphereId] 同 id,二选一
282
+ * @param {boolean} [options.visible=true] 是否显示
283
+ * @returns {{ success: boolean }} 操作结果
284
+ */
285
+ const setHemisphereVisible = (options) => {
286
+ const { mapId, visible = true } = options
287
+ const hemisphereId = resolveHemisphereId(options)
288
+
289
+ if (!hemisphereId) {
290
+ console.error('缺少半球体唯一标识 id / hemisphereId')
291
+ return { success: false }
292
+ }
293
+
294
+ const map = mapStore.getMap(mapId)
295
+ if (!map) {
296
+ console.error('地图实例不存在')
297
+ return { success: false }
298
+ }
299
+
300
+ const hemisphereEntity = mapStore.getGraphicMap(hemisphereId, mapId)
301
+ if (!hemisphereEntity) {
302
+ console.warn(`id: ${hemisphereId} 半球体实体不存在`)
303
+ return { success: false }
304
+ }
305
+
306
+ hemisphereEntity.show = visible
307
+
308
+ const labelEntity = mapStore.getGraphicMap(`${hemisphereId}_label`, mapId)
309
+ if (labelEntity) {
310
+ labelEntity.show = visible
311
+ }
312
+
313
+ return { success: true }
314
+ }
315
+
316
+ /**
317
+ * 【删除半球体实体】
318
+ * 移除指定ID的半球体实体,并从缓存中清除记录
319
+ *
320
+ * @param {Object} options 删除配置参数
321
+ * @param {string} options.hemisphereId 半球体唯一标识
322
+ * @returns {null} 无返回值
323
+ */
324
+ const removeHemisphere = (options) => {
325
+ const { mapId } = options
326
+ const map = mapStore.getMap(mapId)
327
+ if (!map) {
328
+ console.error('地图实例不存在')
329
+ return null
330
+ }
331
+
332
+ const hemisphereEntity = mapStore.getGraphicMap(`${options.hemisphereId}`, mapId)
333
+ if (!hemisphereEntity) {
334
+ console.error(`id: ${options.hemisphereId} 半球体实体不存在`)
335
+ return null
336
+ }
337
+
338
+ // 删除半球体实体
339
+ map.entities.remove(hemisphereEntity);
340
+ // 从缓存中移除
341
+ mapStore.removeGraphicMap(`${options.hemisphereId}`, mapId);
342
+
343
+ // 检查并删除独立的文本框Entity
344
+ const labelEntity = mapStore.getGraphicMap(`${options.hemisphereId}_label`, mapId);
345
+ if (labelEntity) {
346
+ map.entities.remove(labelEntity);
347
+ mapStore.removeGraphicMap(`${options.hemisphereId}_label`, mapId);
348
+ }
349
+ }
350
+
351
+ return {
352
+ setHemisphere,
353
+ updateHemisphere,
354
+ removeHemisphere,
355
+ moveHemisphere,
356
+ setHemisphereVisible
357
+ }
360
358
  }
@@ -43,7 +43,6 @@ export const useMapStore = defineStore('map', () => {
43
43
  trailTime: 30, // 轨迹时间
44
44
  sceneMode: '3D', // 场景模式:'2D' 或 '3D'
45
45
  viewOrthoWidth: null, // 2D/3D 切换时保持稳定的正交可视宽度(米)
46
- cesiumModule: null, // 与 Viewer 同一份 Cesium(由宿主传入,避免双份 cesium)
47
46
  rtkData: {
48
47
  latitude: 0, // RTK纬度
49
48
  longitude: 0, // RTK经度
@@ -153,13 +152,6 @@ export const useMapStore = defineStore('map', () => {
153
152
  getContext(mapId).map.value = mapInstance
154
153
  }
155
154
 
156
- /** @param {typeof import('cesium')} cesiumModule */
157
- const setCesiumModule = (cesiumModule, mapId) => {
158
- getContext(mapId).cesiumModule = cesiumModule
159
- }
160
-
161
- const getCesiumModule = (mapId) => getContext(mapId).cesiumModule
162
-
163
155
  // 获取地图对象
164
156
  const getMap = (mapId) => {
165
157
  return getContext(mapId).map.value
@@ -377,8 +369,6 @@ export const useMapStore = defineStore('map', () => {
377
369
  clearAllGroundLinks,
378
370
  setMap,
379
371
  getMap,
380
- setCesiumModule,
381
- getCesiumModule,
382
372
  clearLayer,
383
373
  resetLayer,
384
374
  setTrailTime,
@@ -9,7 +9,6 @@
9
9
  */
10
10
  import * as Cesium from 'cesium'
11
11
  import { useMapStore } from '../stores/mapStore.js'
12
- import { isOrthographicFrustum } from '../utils/frustumCompat.js'
13
12
 
14
13
  /**
15
14
  * 创建缩放控制器
@@ -38,7 +37,7 @@ export function createZoomController() {
38
37
  if (!mapId) return
39
38
  if (isOrthographic) {
40
39
  const frustum = camera.frustum
41
- if (isOrthographicFrustum(frustum) && frustum.width > 0) {
40
+ if (frustum instanceof Cesium.OrthographicFrustum && frustum.width > 0) {
42
41
  mapStore.setViewOrthoWidth(frustum.width, mapId)
43
42
  }
44
43
  return
@@ -76,7 +75,7 @@ export function createZoomController() {
76
75
  if (!map) return
77
76
 
78
77
  const camera = map.camera
79
- const isOrthographic = isOrthographicFrustum(camera.frustum)
78
+ const isOrthographic = camera.frustum instanceof Cesium.OrthographicFrustum
80
79
 
81
80
  if (isOrthographic) {
82
81
  const position = camera.positionCartographic
@@ -86,7 +85,7 @@ export function createZoomController() {
86
85
  const newHeight = Math.max(100, currentHeight * factor)
87
86
  const heightRatio = currentHeight > 0 ? newHeight / currentHeight : factor
88
87
 
89
- if (isOrthographicFrustum(frustum) && frustum.width > 0) {
88
+ if (frustum instanceof Cesium.OrthographicFrustum && frustum.width > 0) {
90
89
  frustum.width = Math.max(100, frustum.width * heightRatio)
91
90
  }
92
91
 
@@ -139,7 +138,7 @@ export function createZoomController() {
139
138
  if (!map) return
140
139
 
141
140
  const camera = map.camera
142
- const isOrthographic = isOrthographicFrustum(camera.frustum)
141
+ const isOrthographic = camera.frustum instanceof Cesium.OrthographicFrustum
143
142
 
144
143
  if (isOrthographic) {
145
144
  const position = camera.positionCartographic
@@ -149,7 +148,7 @@ export function createZoomController() {
149
148
  const newHeight = Math.min(1000000, currentHeight * factor)
150
149
  const heightRatio = currentHeight > 0 ? newHeight / currentHeight : factor
151
150
 
152
- if (isOrthographicFrustum(frustum) && frustum.width > 0) {
151
+ if (frustum instanceof Cesium.OrthographicFrustum && frustum.width > 0) {
153
152
  frustum.width = Math.min(10000000, frustum.width * heightRatio)
154
153
  }
155
154
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.1.13",
3
+ "version": "1.1.14",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -1,36 +0,0 @@
1
- /**
2
- * 按 mapId 使用与 Viewer 同一份 Cesium 命名空间,避免 Vite 双份 cesium 导致 Color / Frustum instanceof 失败
3
- */
4
- import * as defaultCesium from 'cesium'
5
- import { useMapStore } from '../stores/mapStore.js'
6
-
7
- export function getCesium(mapId) {
8
- return useMapStore().getCesiumModule(mapId) || defaultCesium
9
- }
10
-
11
- /** @param {typeof import('cesium')} cesiumModule */
12
- export function toCssColorString(color) {
13
- if (color == null) return 'rgba(0,191,255,0.85)'
14
- if (typeof color === 'string') return color
15
- if (typeof color.toCssColorString === 'function') return color.toCssColorString()
16
- if (typeof color.toCssHexString === 'function') {
17
- const hex = color.toCssHexString()
18
- const a = color.alpha ?? 1
19
- return a < 1 ? `rgba(${Math.round(color.red * 255)},${Math.round(color.green * 255)},${Math.round(color.blue * 255)},${a})` : hex
20
- }
21
- if (color.red !== undefined) {
22
- const { red, green, blue, alpha = 1 } = color
23
- return `rgba(${Math.round(red * 255)},${Math.round(green * 255)},${Math.round(blue * 255)},${alpha})`
24
- }
25
- return String(color)
26
- }
27
-
28
- /**
29
- * 创建与当前 Viewer 同模块的 Color(用于 entity material / point.color)
30
- * @param {string=} mapId
31
- * @param {import('cesium').Color | string | object} value
32
- */
33
- export function createViewerColor(mapId, value) {
34
- const Cs = getCesium(mapId)
35
- return Cs.Color.fromCssColorString(toCssColorString(value))
36
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * 与 Viewer 使用同一套 Cesium 构造视锥体,避免 Vite 双份 cesium 导致 Camera.update 报错
3
- */
4
- import * as Cesium from 'cesium'
5
-
6
- let PerspectiveFrustumCtor = Cesium.PerspectiveFrustum
7
- let OrthographicFrustumCtor = Cesium.OrthographicFrustum
8
-
9
- export function getFrustumTypeName(frustum) {
10
- return frustum?.constructor?.name ?? ''
11
- }
12
-
13
- export function isPerspectiveFrustum(frustum) {
14
- return getFrustumTypeName(frustum) === 'PerspectiveFrustum'
15
- }
16
-
17
- export function isOrthographicFrustum(frustum) {
18
- return getFrustumTypeName(frustum) === 'OrthographicFrustum'
19
- }
20
-
21
- export function isValid3DFrustum(frustum) {
22
- const name = getFrustumTypeName(frustum)
23
- return name === 'PerspectiveFrustum' || name === 'OrthographicFrustum'
24
- }
25
-
26
- export function getCanvasAspectRatio(map) {
27
- const canvas = map?.canvas
28
- if (!canvas) return 1
29
- const w = canvas.clientWidth || 1
30
- const h = canvas.clientHeight || 1
31
- return w / h
32
- }
33
-
34
- /**
35
- * 从已创建的 Viewer 上绑定与引擎一致的 Frustum 构造函数
36
- * @param {import('cesium').Viewer} map
37
- */
38
- export function bindFrustumConstructors(map) {
39
- const frustum = map?.camera?.frustum
40
- if (!frustum?.constructor) return
41
-
42
- const name = getFrustumTypeName(frustum)
43
- if (name === 'PerspectiveFrustum') {
44
- PerspectiveFrustumCtor = frustum.constructor
45
- } else if (name === 'OrthographicFrustum') {
46
- OrthographicFrustumCtor = frustum.constructor
47
- }
48
- }
49
-
50
- export function createPerspectiveFrustum(map, sourceFrustum) {
51
- if (isPerspectiveFrustum(sourceFrustum)) {
52
- return sourceFrustum
53
- }
54
- const frustum = new PerspectiveFrustumCtor()
55
- frustum.near = sourceFrustum?.near ?? 1.0
56
- frustum.far = sourceFrustum?.far ?? 10000000.0
57
- return frustum
58
- }
59
-
60
- export function createOrthographicFrustum() {
61
- return new OrthographicFrustumCtor()
62
- }
63
-
64
- export function ensure3DCompatibleFrustum(map, preferOrthographic = false) {
65
- if (!map?.camera) return
66
- const current = map.camera.frustum
67
- if (isValid3DFrustum(current)) return
68
-
69
- if (preferOrthographic && detectDuplicateCesium(map)) {
70
- preferOrthographic = false
71
- }
72
-
73
- map.camera.frustum = preferOrthographic
74
- ? createOrthographicFrustum()
75
- : createPerspectiveFrustum(map, current)
76
- }
77
-
78
- let duplicateCesiumWarned = false
79
-
80
- /** Vite 未 dedupe cesium 时,库内 new 的 Frustum 与 Viewer 不是同一类 */
81
- export function detectDuplicateCesium(map, cesiumModule) {
82
- const Cs = cesiumModule || Cesium
83
- const frustum = map?.camera?.frustum
84
- if (!frustum) return false
85
- return (
86
- getFrustumTypeName(frustum) === 'PerspectiveFrustum' &&
87
- !(frustum instanceof Cs.PerspectiveFrustum)
88
- )
89
- }
90
-
91
- export function warnDuplicateCesiumOnce(map) {
92
- if (duplicateCesiumWarned || !detectDuplicateCesium(map)) return
93
- duplicateCesiumWarned = true
94
- console.warn(
95
- '[huweili-cesium] 检测到两份 cesium 副本,正交视锥可能触发 Camera.update 报错。请在 vite.config 设置 resolve.dedupe: ["cesium"],并将 cesium、huweili-cesium 加入 optimizeDeps.include。'
96
- )
97
- }
package/postinstall.mjs DELETED
@@ -1,79 +0,0 @@
1
- /**
2
- * 依赖安装完成后在终端打印欢迎信息(npm / pnpm / yarn 均会触发 postinstall)
3
- * 放在包根目录,避免 .npmignore 的 scripts/ 导致发布包缺失
4
- */
5
- import { readFileSync } from 'node:fs'
6
- import { dirname, resolve } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
-
9
- const __dirname = dirname(fileURLToPath(import.meta.url))
10
- const pkgRoot = resolve(__dirname)
11
-
12
- if (
13
- process.env.CI === 'true' ||
14
- process.env.CI === '1' ||
15
- process.env.SKIP_POSTINSTALL_WELCOME === '1'
16
- ) {
17
- process.exit(0)
18
- }
19
-
20
- const initCwd = process.env.INIT_CWD ? resolve(process.env.INIT_CWD) : ''
21
- if (initCwd && initCwd === pkgRoot) {
22
- process.exit(0)
23
- }
24
-
25
- const pkg = JSON.parse(readFileSync(resolve(pkgRoot, 'package.json'), 'utf8'))
26
- const { version = '?', name = 'huweili-cesium' } = pkg
27
-
28
- const npmUrl = `https://www.npmjs.com/package/${name}`
29
-
30
- function normalizeRepoUrl(repo) {
31
- if (!repo) return ''
32
- const raw = typeof repo === 'string' ? repo : repo.url
33
- if (!raw) return ''
34
- return raw
35
- .replace(/^git\+/, '')
36
- .replace(/^git:\/\//, 'https://')
37
- .replace(/^git@github\.com:/, 'https://github.com/')
38
- .replace(/\.git$/, '')
39
- }
40
-
41
- const repoUrl = normalizeRepoUrl(pkg.homepage || pkg.repository)
42
-
43
- const c = {
44
- reset: '\x1b[0m',
45
- bold: '\x1b[1m',
46
- dim: '\x1b[2m',
47
- cyan: '\x1b[36m',
48
- green: '\x1b[32m',
49
- yellow: '\x1b[33m',
50
- magenta: '\x1b[35m',
51
- blue: '\x1b[34m',
52
- }
53
-
54
- const banner = `
55
- ${c.cyan}${c.bold} ╔══════════════════════════════════════════════════════════╗
56
- ║ ║
57
- ║ 🌍 huweili-cesium ${c.green}v${version}${c.cyan}${c.bold} — Cesium 三维地图工具库已就绪 ║
58
- ║ ║
59
- ╚══════════════════════════════════════════════════════════╝${c.reset}
60
-
61
- ${c.yellow}${c.bold} 快速开始${c.reset}
62
- ${c.dim} ─────────────────────────────────────────────────────────${c.reset}
63
- ${c.green}组件${c.reset} import CesiumMap from ${c.cyan}'huweili-cesium'${c.reset}
64
- ${c.green}工具${c.reset} import { basicConfig } from ${c.cyan}'huweili-cesium'${c.reset}
65
- ${c.green}按钮${c.reset} import { createCustomToolbarButtons } from ${c.cyan}'huweili-cesium/customToolbarButtons'${c.reset}
66
-
67
- ${c.yellow}${c.bold} 使用前请确认${c.reset}
68
- ${c.dim}·${c.reset} 已安装 peer:${c.magenta}cesium · vue · pinia · mitt · qrcode${c.reset}
69
- ${c.dim}·${c.reset} 已加载 ${c.cyan}constants.js${c.reset}(MapConfig / DroneConfig 等)
70
- ${c.dim}·${c.reset} Vite:${c.cyan}resolve.dedupe: ['cesium']${c.reset} 或组件传 ${c.cyan}:cesium="Cesium"${c.reset}
71
-
72
- ${c.yellow}${c.bold} 链接${c.reset}
73
- ${c.dim}·${c.reset} npm ${c.blue}${npmUrl}${c.reset}${repoUrl ? `\n ${c.dim}·${c.reset} 仓库 ${c.blue}${repoUrl}${c.reset}` : ''}
74
-
75
- ${c.dim} 文档见包内 README · 祝绘制愉快 🚁${c.reset}
76
- ${c.dim} 关闭欢迎: set SKIP_POSTINSTALL_WELCOME=1${c.reset}
77
- `
78
-
79
- console.log(banner)