@jorgmoritz/gis-manager 0.1.31 → 0.1.33
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/README.md +76 -76
- package/dist/index.cjs +545 -659
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +120 -173
- package/dist/index.d.ts +120 -173
- package/dist/index.js +545 -659
- package/dist/index.js.map +1 -1
- package/dist/vue/index.cjs +181 -657
- package/dist/vue/index.cjs.map +1 -1
- package/dist/vue/index.js +181 -657
- package/dist/vue/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -13,7 +13,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "@jorgmoritz/gis-manager",
|
|
16
|
-
version: "0.1.
|
|
16
|
+
version: "0.1.33"};
|
|
17
17
|
|
|
18
18
|
// src/utils/version.ts
|
|
19
19
|
var version = package_default.version;
|
|
@@ -1330,12 +1330,10 @@ var StateManager = class {
|
|
|
1330
1330
|
__publicField(this, "editing", false);
|
|
1331
1331
|
__publicField(this, "editingTarget");
|
|
1332
1332
|
__publicField(this, "previousView");
|
|
1333
|
-
__publicField(this, "flightPreviewing", false);
|
|
1334
1333
|
// Emitters
|
|
1335
1334
|
__publicField(this, "onSelectionChange", new Emitter());
|
|
1336
1335
|
__publicField(this, "onEditingChange", new Emitter());
|
|
1337
1336
|
__publicField(this, "onPreviousViewChange", new Emitter());
|
|
1338
|
-
__publicField(this, "onFlightPreviewChange", new Emitter());
|
|
1339
1337
|
}
|
|
1340
1338
|
// Selection
|
|
1341
1339
|
setSelected(next) {
|
|
@@ -1382,35 +1380,6 @@ var StateManager = class {
|
|
|
1382
1380
|
getPreviousCameraView() {
|
|
1383
1381
|
return this.previousView;
|
|
1384
1382
|
}
|
|
1385
|
-
// Flight preview mode
|
|
1386
|
-
/**
|
|
1387
|
-
* 开始飞行预览模式(锁定交互)
|
|
1388
|
-
*/
|
|
1389
|
-
startFlightPreview() {
|
|
1390
|
-
if (this.flightPreviewing) return;
|
|
1391
|
-
this.flightPreviewing = true;
|
|
1392
|
-
this.onFlightPreviewChange.emit({ previewing: true });
|
|
1393
|
-
}
|
|
1394
|
-
/**
|
|
1395
|
-
* 停止飞行预览模式(恢复交互)
|
|
1396
|
-
*/
|
|
1397
|
-
stopFlightPreview() {
|
|
1398
|
-
if (!this.flightPreviewing) return;
|
|
1399
|
-
this.flightPreviewing = false;
|
|
1400
|
-
this.onFlightPreviewChange.emit({ previewing: false });
|
|
1401
|
-
}
|
|
1402
|
-
/**
|
|
1403
|
-
* 检查是否处于飞行预览模式
|
|
1404
|
-
*/
|
|
1405
|
-
isFlightPreviewing() {
|
|
1406
|
-
return this.flightPreviewing;
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* 检查当前是否允许交互(非编辑模式且非飞行预览模式)
|
|
1410
|
-
*/
|
|
1411
|
-
isInteractionAllowed() {
|
|
1412
|
-
return !this.editing && !this.flightPreviewing;
|
|
1413
|
-
}
|
|
1414
1383
|
};
|
|
1415
1384
|
var globalState = new StateManager();
|
|
1416
1385
|
|
|
@@ -2864,6 +2833,10 @@ var AirplaneCursor = class {
|
|
|
2864
2833
|
__publicField(this, "pyramidEntities", []);
|
|
2865
2834
|
// 临时数据源,用于存储金字塔实体(避免污染宿主图层)
|
|
2866
2835
|
__publicField(this, "pyramidLayer");
|
|
2836
|
+
// 缓存的缩放因子,仅在相机变化时更新
|
|
2837
|
+
__publicField(this, "cachedScaleFactor", 1);
|
|
2838
|
+
// 相机变化事件监听器
|
|
2839
|
+
__publicField(this, "cameraChangedListener");
|
|
2867
2840
|
const C = this.CesiumNS;
|
|
2868
2841
|
this.opts = opts;
|
|
2869
2842
|
this.pose = { position: startPosition, heading: 0, pitch: -10, roll: 0 };
|
|
@@ -2883,14 +2856,39 @@ var AirplaneCursor = class {
|
|
|
2883
2856
|
this.viewer.dataSources.add(this.pyramidLayer);
|
|
2884
2857
|
}
|
|
2885
2858
|
const modelHeadingOffset = 270;
|
|
2886
|
-
const
|
|
2887
|
-
const
|
|
2888
|
-
const
|
|
2859
|
+
const baseVerticalOffset = 10;
|
|
2860
|
+
const baseForwardOffset = 5;
|
|
2861
|
+
const baseRightOffset = -9;
|
|
2862
|
+
const baseScale = 0.15;
|
|
2863
|
+
const minScale = 0.025;
|
|
2864
|
+
const maxScale = 1.5;
|
|
2865
|
+
const referenceDistance = 800;
|
|
2866
|
+
const computeScaleFactor = () => {
|
|
2867
|
+
try {
|
|
2868
|
+
const cameraPos = this.viewer.camera.positionWC;
|
|
2869
|
+
const modelPos = this.pose.position;
|
|
2870
|
+
const distance = C.Cartesian3.distance(cameraPos, modelPos);
|
|
2871
|
+
const rawScale = baseScale * (distance / referenceDistance);
|
|
2872
|
+
const clampedScale = Math.max(minScale, Math.min(maxScale, rawScale));
|
|
2873
|
+
return clampedScale / baseScale;
|
|
2874
|
+
} catch {
|
|
2875
|
+
return 1;
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
this.cachedScaleFactor = computeScaleFactor();
|
|
2879
|
+
this.cameraChangedListener = this.viewer.camera.changed.addEventListener(() => {
|
|
2880
|
+
this.cachedScaleFactor = computeScaleFactor();
|
|
2881
|
+
this.viewer.scene?.requestRender?.();
|
|
2882
|
+
});
|
|
2889
2883
|
this.modelEntity = this.pyramidLayer.entities.add({
|
|
2890
2884
|
position: new C.CallbackProperty(() => {
|
|
2891
2885
|
const pos = this.pose.position;
|
|
2892
2886
|
const headingRad = C.Math.toRadians(this.pose.heading);
|
|
2893
2887
|
const pitchRad = C.Math.toRadians(this.pose.pitch);
|
|
2888
|
+
const scaleFactor = this.cachedScaleFactor;
|
|
2889
|
+
const modelVerticalOffset = baseVerticalOffset * scaleFactor;
|
|
2890
|
+
const modelForwardOffset = baseForwardOffset * scaleFactor;
|
|
2891
|
+
const modelRightOffset = baseRightOffset * scaleFactor;
|
|
2894
2892
|
const enu = C.Transforms.eastNorthUpToFixedFrame(pos);
|
|
2895
2893
|
const east = C.Cartesian3.fromCartesian4(C.Matrix4.getColumn(enu, 0, new C.Cartesian4()));
|
|
2896
2894
|
const north = C.Cartesian3.fromCartesian4(C.Matrix4.getColumn(enu, 1, new C.Cartesian4()));
|
|
@@ -2931,9 +2929,10 @@ var AirplaneCursor = class {
|
|
|
2931
2929
|
}, false),
|
|
2932
2930
|
model: {
|
|
2933
2931
|
uri: wurenji_default,
|
|
2934
|
-
scale:
|
|
2935
|
-
|
|
2936
|
-
|
|
2932
|
+
scale: new C.CallbackProperty(() => {
|
|
2933
|
+
return baseScale * this.cachedScaleFactor;
|
|
2934
|
+
}, false),
|
|
2935
|
+
minimumPixelSize: 24,
|
|
2937
2936
|
silhouetteColor: C.Color.CYAN,
|
|
2938
2937
|
silhouetteSize: 1
|
|
2939
2938
|
},
|
|
@@ -3067,20 +3066,7 @@ var AirplaneCursor = class {
|
|
|
3067
3066
|
moved = true;
|
|
3068
3067
|
}
|
|
3069
3068
|
if (this.keysPressed.has("z")) {
|
|
3070
|
-
|
|
3071
|
-
const cartographic = C.Cartographic.fromCartesian(newPos);
|
|
3072
|
-
if (cartographic) {
|
|
3073
|
-
const terrainHeight = this.viewer.scene.globe.getHeight(cartographic) ?? 0;
|
|
3074
|
-
const minAltitude = terrainHeight + 1;
|
|
3075
|
-
if (cartographic.height < minAltitude) {
|
|
3076
|
-
cartographic.height = minAltitude;
|
|
3077
|
-
setPos(C.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, cartographic.height));
|
|
3078
|
-
} else {
|
|
3079
|
-
setPos(newPos);
|
|
3080
|
-
}
|
|
3081
|
-
} else {
|
|
3082
|
-
setPos(newPos);
|
|
3083
|
-
}
|
|
3069
|
+
setPos(addVec(pose.position, C.Cartesian3.multiplyByScalar(u3, -step, new C.Cartesian3())));
|
|
3084
3070
|
moved = true;
|
|
3085
3071
|
}
|
|
3086
3072
|
if (this.keysPressed.has("q")) {
|
|
@@ -3308,6 +3294,11 @@ var AirplaneCursor = class {
|
|
|
3308
3294
|
} catch {
|
|
3309
3295
|
}
|
|
3310
3296
|
this.pyramidLayer = void 0;
|
|
3297
|
+
try {
|
|
3298
|
+
this.cameraChangedListener?.();
|
|
3299
|
+
} catch {
|
|
3300
|
+
}
|
|
3301
|
+
this.cameraChangedListener = void 0;
|
|
3311
3302
|
try {
|
|
3312
3303
|
this.frustum?.destroy();
|
|
3313
3304
|
} catch {
|
|
@@ -4626,34 +4617,6 @@ var PathEditingEventHandler = class {
|
|
|
4626
4617
|
* 🆕 处理快速编辑模式的点击
|
|
4627
4618
|
*/
|
|
4628
4619
|
handleQuickEditClick(movement) {
|
|
4629
|
-
const C = this.CesiumNS;
|
|
4630
|
-
try {
|
|
4631
|
-
const picked = this.viewer.scene.pick?.(movement.position);
|
|
4632
|
-
const entity = picked?.id;
|
|
4633
|
-
if (entity?.properties) {
|
|
4634
|
-
const props = entity.properties;
|
|
4635
|
-
const vertexIndex = props._vertexIndex?.getValue?.() ?? props._vertexIndex;
|
|
4636
|
-
const type = props._type?.getValue?.() ?? props._type;
|
|
4637
|
-
if (typeof vertexIndex === "number" && (type === "path-vertex" || type === "vertex-label")) {
|
|
4638
|
-
if (vertexIndex !== this.hiddenClimbIndex && vertexIndex !== 0) {
|
|
4639
|
-
console.log("[QuickEdit] \u70B9\u51FB\u5230\u5DF2\u5B58\u5728\u822A\u70B9\uFF0C\u9009\u4E2D\u800C\u975E\u65B0\u589E:", vertexIndex);
|
|
4640
|
-
if (this.callbacks.onVertexSelect) {
|
|
4641
|
-
this.callbacks.onVertexSelect(vertexIndex);
|
|
4642
|
-
}
|
|
4643
|
-
if (this.callbacks.onVertexSelectDetail) {
|
|
4644
|
-
try {
|
|
4645
|
-
const detailInfo = this.buildVertexDetailInfo(vertexIndex);
|
|
4646
|
-
this.callbacks.onVertexSelectDetail(detailInfo);
|
|
4647
|
-
} catch (error) {
|
|
4648
|
-
console.error("Error building vertex detail info:", error);
|
|
4649
|
-
}
|
|
4650
|
-
}
|
|
4651
|
-
return;
|
|
4652
|
-
}
|
|
4653
|
-
}
|
|
4654
|
-
}
|
|
4655
|
-
} catch (error) {
|
|
4656
|
-
}
|
|
4657
4620
|
const position = this.getPositionFromMouse(movement);
|
|
4658
4621
|
if (!position) {
|
|
4659
4622
|
console.warn("[QuickEdit] \u65E0\u6CD5\u83B7\u53D6\u70B9\u51FB\u4F4D\u7F6E");
|
|
@@ -4662,6 +4625,7 @@ var PathEditingEventHandler = class {
|
|
|
4662
4625
|
const quickEditOptions = this.callbacks.getQuickEditOptions?.();
|
|
4663
4626
|
if (!quickEditOptions) return;
|
|
4664
4627
|
const { climbHeight, altitudeMode } = quickEditOptions;
|
|
4628
|
+
const C = this.CesiumNS;
|
|
4665
4629
|
const originalCarto = C.Cartographic.fromCartesian(position);
|
|
4666
4630
|
const originalHeight = originalCarto.height;
|
|
4667
4631
|
const newPosition = this.applyClimbHeight(position, climbHeight, altitudeMode);
|
|
@@ -5516,10 +5480,6 @@ function startPathEditing(CesiumNS, viewer, entityOrId, options) {
|
|
|
5516
5480
|
);
|
|
5517
5481
|
const cleanupSession = () => {
|
|
5518
5482
|
setActiveIndex(void 0);
|
|
5519
|
-
try {
|
|
5520
|
-
preview?.destroy();
|
|
5521
|
-
} catch {
|
|
5522
|
-
}
|
|
5523
5483
|
try {
|
|
5524
5484
|
handles.forEach((handle) => {
|
|
5525
5485
|
if (handle) {
|
|
@@ -5929,6 +5889,13 @@ var ArrowShape = class {
|
|
|
5929
5889
|
__publicField(this, "baseAlpha");
|
|
5930
5890
|
__publicField(this, "outline");
|
|
5931
5891
|
__publicField(this, "outlineColor");
|
|
5892
|
+
// 动态缩放相关
|
|
5893
|
+
__publicField(this, "dynamicScale");
|
|
5894
|
+
__publicField(this, "viewer");
|
|
5895
|
+
__publicField(this, "referenceDistance");
|
|
5896
|
+
__publicField(this, "minScale");
|
|
5897
|
+
__publicField(this, "maxScale");
|
|
5898
|
+
__publicField(this, "cachedScaleFactor", 1);
|
|
5932
5899
|
const C = this.CesiumNS;
|
|
5933
5900
|
this.shaftHeight = opts.shaftHeight ?? 17;
|
|
5934
5901
|
this.headHeight = opts.headHeight ?? 8;
|
|
@@ -5936,12 +5903,17 @@ var ArrowShape = class {
|
|
|
5936
5903
|
this.shaftRadius = opts.shaftRadius ?? 3;
|
|
5937
5904
|
this.headRadius = opts.headRadius ?? 6;
|
|
5938
5905
|
this.baseRadius = opts.baseRadius ?? 8;
|
|
5939
|
-
this.color = opts.color ? C.Color.fromCssColorString(opts.color) : C.Color.fromCssColorString("#
|
|
5906
|
+
this.color = opts.color ? C.Color.fromCssColorString(opts.color) : C.Color.fromCssColorString("#FFD700");
|
|
5940
5907
|
this.shaftAlpha = opts.shaftAlpha ?? 1;
|
|
5941
5908
|
this.headAlpha = opts.headAlpha ?? 1;
|
|
5942
|
-
this.baseAlpha = opts.baseAlpha ??
|
|
5909
|
+
this.baseAlpha = opts.baseAlpha ?? 0.65;
|
|
5943
5910
|
this.outline = opts.outline ?? false;
|
|
5944
5911
|
this.outlineColor = opts.outlineColor ? C.Color.fromCssColorString(opts.outlineColor) : this.color.darken(0.3, new C.Color());
|
|
5912
|
+
this.dynamicScale = opts.dynamicScale ?? false;
|
|
5913
|
+
this.viewer = opts.viewer;
|
|
5914
|
+
this.referenceDistance = opts.referenceDistance ?? 500;
|
|
5915
|
+
this.minScale = opts.minScale ?? 0.3;
|
|
5916
|
+
this.maxScale = opts.maxScale ?? 3;
|
|
5945
5917
|
}
|
|
5946
5918
|
/**
|
|
5947
5919
|
* Create arrow entities at given position
|
|
@@ -5959,22 +5931,40 @@ var ArrowShape = class {
|
|
|
5959
5931
|
const carto = C.Cartographic.fromCartesian(position);
|
|
5960
5932
|
const lon = C.Math.toDegrees(carto.longitude);
|
|
5961
5933
|
const lat = C.Math.toDegrees(carto.latitude);
|
|
5962
|
-
const
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5934
|
+
const computeScaleFactor = () => {
|
|
5935
|
+
if (!this.dynamicScale || !this.viewer) return 1;
|
|
5936
|
+
try {
|
|
5937
|
+
const cameraPos = this.viewer.camera.positionWC;
|
|
5938
|
+
const distance = C.Cartesian3.distance(cameraPos, position);
|
|
5939
|
+
const rawScale = distance / this.referenceDistance;
|
|
5940
|
+
return Math.max(this.minScale, Math.min(this.maxScale, rawScale));
|
|
5941
|
+
} catch {
|
|
5942
|
+
return 1;
|
|
5943
|
+
}
|
|
5944
|
+
};
|
|
5945
|
+
if (this.dynamicScale && this.viewer) {
|
|
5946
|
+
this.viewer.scene.preRender.addEventListener(() => {
|
|
5947
|
+
this.cachedScaleFactor = computeScaleFactor();
|
|
5948
|
+
});
|
|
5949
|
+
}
|
|
5968
5950
|
const hpr = new C.HeadingPitchRoll(0, 0, 0);
|
|
5969
5951
|
const baseOrientation = C.Transforms.headingPitchRollQuaternion(position, hpr);
|
|
5970
5952
|
const fixedOrientation = new C.ConstantProperty(baseOrientation);
|
|
5953
|
+
const getShaftPosition = () => {
|
|
5954
|
+
const scale = this.cachedScaleFactor;
|
|
5955
|
+
return C.Cartesian3.fromDegrees(lon, lat, baseHeight + this.shaftHeight * scale / 2);
|
|
5956
|
+
};
|
|
5957
|
+
const getHeadPosition = () => {
|
|
5958
|
+
const scale = this.cachedScaleFactor;
|
|
5959
|
+
return C.Cartesian3.fromDegrees(lon, lat, baseHeight + this.shaftHeight * scale + this.headHeight * scale / 2);
|
|
5960
|
+
};
|
|
5971
5961
|
const shaftEntity = layer.entities.add({
|
|
5972
|
-
position: new C.ConstantPositionProperty(
|
|
5962
|
+
position: this.dynamicScale ? new C.CallbackProperty(getShaftPosition, false) : new C.ConstantPositionProperty(C.Cartesian3.fromDegrees(lon, lat, baseHeight + this.shaftHeight / 2)),
|
|
5973
5963
|
orientation: fixedOrientation,
|
|
5974
5964
|
cylinder: {
|
|
5975
|
-
length: this.shaftHeight,
|
|
5976
|
-
topRadius: this.shaftRadius,
|
|
5977
|
-
bottomRadius: this.shaftRadius,
|
|
5965
|
+
length: this.dynamicScale ? new C.CallbackProperty(() => this.shaftHeight * this.cachedScaleFactor, false) : this.shaftHeight,
|
|
5966
|
+
topRadius: this.dynamicScale ? new C.CallbackProperty(() => this.shaftRadius * this.cachedScaleFactor, false) : this.shaftRadius,
|
|
5967
|
+
bottomRadius: this.dynamicScale ? new C.CallbackProperty(() => this.shaftRadius * this.cachedScaleFactor, false) : this.shaftRadius,
|
|
5978
5968
|
material: this.color.withAlpha(this.shaftAlpha),
|
|
5979
5969
|
outline: this.outline,
|
|
5980
5970
|
outlineColor: this.outlineColor,
|
|
@@ -5987,13 +5977,13 @@ var ArrowShape = class {
|
|
|
5987
5977
|
});
|
|
5988
5978
|
entities.push(shaftEntity);
|
|
5989
5979
|
const headEntity = layer.entities.add({
|
|
5990
|
-
position: new C.ConstantPositionProperty(
|
|
5980
|
+
position: this.dynamicScale ? new C.CallbackProperty(getHeadPosition, false) : new C.ConstantPositionProperty(C.Cartesian3.fromDegrees(lon, lat, baseHeight + this.shaftHeight + this.headHeight / 2)),
|
|
5991
5981
|
orientation: fixedOrientation,
|
|
5992
5982
|
cylinder: {
|
|
5993
|
-
length: this.headHeight,
|
|
5983
|
+
length: this.dynamicScale ? new C.CallbackProperty(() => this.headHeight * this.cachedScaleFactor, false) : this.headHeight,
|
|
5994
5984
|
topRadius: 0,
|
|
5995
5985
|
// Creates cone shape
|
|
5996
|
-
bottomRadius: this.headRadius,
|
|
5986
|
+
bottomRadius: this.dynamicScale ? new C.CallbackProperty(() => this.headRadius * this.cachedScaleFactor, false) : this.headRadius,
|
|
5997
5987
|
material: this.color.withAlpha(this.headAlpha),
|
|
5998
5988
|
outline: this.outline,
|
|
5999
5989
|
outlineColor: this.outlineColor,
|
|
@@ -6008,8 +5998,8 @@ var ArrowShape = class {
|
|
|
6008
5998
|
const baseEntity = layer.entities.add({
|
|
6009
5999
|
position,
|
|
6010
6000
|
ellipse: {
|
|
6011
|
-
semiMajorAxis: this.baseRadius,
|
|
6012
|
-
semiMinorAxis: this.baseRadius,
|
|
6001
|
+
semiMajorAxis: this.dynamicScale ? new C.CallbackProperty(() => this.baseRadius * this.cachedScaleFactor, false) : this.baseRadius,
|
|
6002
|
+
semiMinorAxis: this.dynamicScale ? new C.CallbackProperty(() => this.baseRadius * this.cachedScaleFactor, false) : this.baseRadius,
|
|
6013
6003
|
height: baseHeight,
|
|
6014
6004
|
material: this.color.withAlpha(this.baseAlpha),
|
|
6015
6005
|
outline: this.outline,
|
|
@@ -6148,7 +6138,13 @@ function startPathDrawing(CesiumNS, viewer, options, onComplete) {
|
|
|
6148
6138
|
color: "#FFD700",
|
|
6149
6139
|
shaftAlpha: 1,
|
|
6150
6140
|
headAlpha: 1,
|
|
6151
|
-
baseAlpha: 0.5
|
|
6141
|
+
baseAlpha: 0.5,
|
|
6142
|
+
// 启用动态缩放,和无人机模型一样根据相机距离调整大小
|
|
6143
|
+
dynamicScale: true,
|
|
6144
|
+
viewer,
|
|
6145
|
+
referenceDistance: 300,
|
|
6146
|
+
minScale: 0.3,
|
|
6147
|
+
maxScale: 2
|
|
6152
6148
|
});
|
|
6153
6149
|
const arrowEntities = arrowShape.createEntities(
|
|
6154
6150
|
layer,
|
|
@@ -6665,7 +6661,13 @@ function renderFlightPath(CesiumNS, viewer, options) {
|
|
|
6665
6661
|
color: "#FFD700",
|
|
6666
6662
|
shaftAlpha: 0.98,
|
|
6667
6663
|
headAlpha: 0.98,
|
|
6668
|
-
baseAlpha: 0.65
|
|
6664
|
+
baseAlpha: 0.65,
|
|
6665
|
+
// 启用动态缩放
|
|
6666
|
+
dynamicScale: true,
|
|
6667
|
+
viewer,
|
|
6668
|
+
referenceDistance: 300,
|
|
6669
|
+
minScale: 0.3,
|
|
6670
|
+
maxScale: 2
|
|
6669
6671
|
});
|
|
6670
6672
|
arrowShape.createEntities(
|
|
6671
6673
|
layer,
|
|
@@ -6789,515 +6791,6 @@ function renderFlightPathPreview(CesiumNS, viewer, options) {
|
|
|
6789
6791
|
return { entity, controller };
|
|
6790
6792
|
}
|
|
6791
6793
|
|
|
6792
|
-
// src/core/path-manager/FlightPreviewController.ts
|
|
6793
|
-
var FlightPreviewController = class {
|
|
6794
|
-
constructor(CesiumNS, viewer, options) {
|
|
6795
|
-
this.CesiumNS = CesiumNS;
|
|
6796
|
-
this.viewer = viewer;
|
|
6797
|
-
this.options = options;
|
|
6798
|
-
// ========== 状态 ==========
|
|
6799
|
-
__publicField(this, "state", "stopped");
|
|
6800
|
-
__publicField(this, "destroyed", false);
|
|
6801
|
-
// ========== 配置 ==========
|
|
6802
|
-
__publicField(this, "speed");
|
|
6803
|
-
__publicField(this, "loop");
|
|
6804
|
-
__publicField(this, "showFrustum");
|
|
6805
|
-
__publicField(this, "trackHighlightOptions");
|
|
6806
|
-
// ========== 路径数据 ==========
|
|
6807
|
-
__publicField(this, "waypoints", []);
|
|
6808
|
-
__publicField(this, "totalDistance", 0);
|
|
6809
|
-
// ========== 动画状态 ==========
|
|
6810
|
-
__publicField(this, "currentDistance", 0);
|
|
6811
|
-
// 当前已飞行距离
|
|
6812
|
-
__publicField(this, "animationFrameId");
|
|
6813
|
-
__publicField(this, "lastFrameTime");
|
|
6814
|
-
// ========== 可视化组件 ==========
|
|
6815
|
-
__publicField(this, "layer");
|
|
6816
|
-
__publicField(this, "droneEntity");
|
|
6817
|
-
__publicField(this, "frustum");
|
|
6818
|
-
__publicField(this, "pathPreview");
|
|
6819
|
-
// ========== 轨迹高亮 ==========
|
|
6820
|
-
__publicField(this, "traveledPathEntity");
|
|
6821
|
-
__publicField(this, "remainingPathEntity");
|
|
6822
|
-
// ========== 事件 ==========
|
|
6823
|
-
__publicField(this, "onStateChange", new Emitter());
|
|
6824
|
-
__publicField(this, "onProgressChange", new Emitter());
|
|
6825
|
-
this.speed = options.speed ?? 10;
|
|
6826
|
-
this.loop = options.loop ?? true;
|
|
6827
|
-
this.showFrustum = options.showFrustum ?? true;
|
|
6828
|
-
this.trackHighlightOptions = {
|
|
6829
|
-
enabled: true,
|
|
6830
|
-
traveledWidth: 6,
|
|
6831
|
-
remainingWidth: 4,
|
|
6832
|
-
...options.trackHighlight
|
|
6833
|
-
};
|
|
6834
|
-
this.initLayer();
|
|
6835
|
-
this.parseWaylineData();
|
|
6836
|
-
this.initVisualization();
|
|
6837
|
-
console.log("[FlightPreviewController] \u521D\u59CB\u5316\u5B8C\u6210", {
|
|
6838
|
-
waypointCount: this.waypoints.length,
|
|
6839
|
-
totalDistance: this.totalDistance,
|
|
6840
|
-
speed: this.speed
|
|
6841
|
-
});
|
|
6842
|
-
}
|
|
6843
|
-
// ==================== 公共方法 ====================
|
|
6844
|
-
/** 开始预览 */
|
|
6845
|
-
start() {
|
|
6846
|
-
if (this.destroyed) return;
|
|
6847
|
-
if (this.state === "playing") return;
|
|
6848
|
-
if (this.state === "stopped") {
|
|
6849
|
-
this.currentDistance = 0;
|
|
6850
|
-
}
|
|
6851
|
-
this.setState("playing");
|
|
6852
|
-
this.lastFrameTime = performance.now();
|
|
6853
|
-
this.startAnimationLoop();
|
|
6854
|
-
globalState.startFlightPreview();
|
|
6855
|
-
this.pathPreview?.show();
|
|
6856
|
-
console.log("[FlightPreviewController] \u5F00\u59CB\u64AD\u653E");
|
|
6857
|
-
}
|
|
6858
|
-
/** 暂停 */
|
|
6859
|
-
pause() {
|
|
6860
|
-
if (this.destroyed) return;
|
|
6861
|
-
if (this.state !== "playing") return;
|
|
6862
|
-
this.setState("paused");
|
|
6863
|
-
this.stopAnimationLoop();
|
|
6864
|
-
console.log("[FlightPreviewController] \u6682\u505C");
|
|
6865
|
-
}
|
|
6866
|
-
/** 继续 */
|
|
6867
|
-
resume() {
|
|
6868
|
-
if (this.destroyed) return;
|
|
6869
|
-
if (this.state !== "paused") return;
|
|
6870
|
-
this.setState("playing");
|
|
6871
|
-
this.lastFrameTime = performance.now();
|
|
6872
|
-
this.startAnimationLoop();
|
|
6873
|
-
console.log("[FlightPreviewController] \u7EE7\u7EED\u64AD\u653E");
|
|
6874
|
-
}
|
|
6875
|
-
/** 停止并重置 */
|
|
6876
|
-
stop() {
|
|
6877
|
-
if (this.destroyed) return;
|
|
6878
|
-
this.setState("stopped");
|
|
6879
|
-
this.stopAnimationLoop();
|
|
6880
|
-
this.currentDistance = 0;
|
|
6881
|
-
globalState.stopFlightPreview();
|
|
6882
|
-
this.updateVisualization();
|
|
6883
|
-
this.pathPreview?.hide();
|
|
6884
|
-
console.log("[FlightPreviewController] \u505C\u6B62");
|
|
6885
|
-
}
|
|
6886
|
-
/** 跳转到指定进度 (0-1) */
|
|
6887
|
-
seek(progress) {
|
|
6888
|
-
if (this.destroyed) return;
|
|
6889
|
-
const clampedProgress = Math.max(0, Math.min(1, progress));
|
|
6890
|
-
this.currentDistance = clampedProgress * this.totalDistance;
|
|
6891
|
-
this.updateVisualization();
|
|
6892
|
-
console.log("[FlightPreviewController] \u8DF3\u8F6C\u5230", (clampedProgress * 100).toFixed(1) + "%");
|
|
6893
|
-
}
|
|
6894
|
-
/** 设置速度 */
|
|
6895
|
-
setSpeed(speed) {
|
|
6896
|
-
this.speed = Math.max(1, speed);
|
|
6897
|
-
console.log("[FlightPreviewController] \u8BBE\u7F6E\u901F\u5EA6:", this.speed, "m/s");
|
|
6898
|
-
}
|
|
6899
|
-
/** 获取当前状态 */
|
|
6900
|
-
getState() {
|
|
6901
|
-
return this.state;
|
|
6902
|
-
}
|
|
6903
|
-
/** 获取当前进度 (0-1) */
|
|
6904
|
-
getProgress() {
|
|
6905
|
-
if (this.totalDistance === 0) return 0;
|
|
6906
|
-
return this.currentDistance / this.totalDistance;
|
|
6907
|
-
}
|
|
6908
|
-
/** 获取当前航点索引 */
|
|
6909
|
-
getCurrentWaypointIndex() {
|
|
6910
|
-
return this.findCurrentSegment().segmentIndex;
|
|
6911
|
-
}
|
|
6912
|
-
/** 销毁 */
|
|
6913
|
-
destroy() {
|
|
6914
|
-
if (this.destroyed) return;
|
|
6915
|
-
this.destroyed = true;
|
|
6916
|
-
this.stopAnimationLoop();
|
|
6917
|
-
globalState.stopFlightPreview();
|
|
6918
|
-
this.destroyVisualization();
|
|
6919
|
-
this.onStateChange.clear();
|
|
6920
|
-
this.onProgressChange.clear();
|
|
6921
|
-
console.log("[FlightPreviewController] \u5DF2\u9500\u6BC1");
|
|
6922
|
-
}
|
|
6923
|
-
// ==================== 私有方法 - 初始化 ====================
|
|
6924
|
-
/** 初始化图层 */
|
|
6925
|
-
initLayer() {
|
|
6926
|
-
const C = this.CesiumNS;
|
|
6927
|
-
if (this.options.layer) {
|
|
6928
|
-
this.layer = this.options.layer;
|
|
6929
|
-
} else {
|
|
6930
|
-
const newLayer = new C.CustomDataSource("FlightPreviewLayer");
|
|
6931
|
-
this.layer = newLayer;
|
|
6932
|
-
this.viewer.dataSources.add(newLayer);
|
|
6933
|
-
}
|
|
6934
|
-
}
|
|
6935
|
-
/** 解析航线数据 */
|
|
6936
|
-
parseWaylineData() {
|
|
6937
|
-
const C = this.CesiumNS;
|
|
6938
|
-
const { waylineData } = this.options;
|
|
6939
|
-
const converted = convertSinoflyWayline(waylineData, { CesiumNS: this.CesiumNS });
|
|
6940
|
-
const waypointData = converted.waypointData;
|
|
6941
|
-
if (!waypointData || waypointData.length < 2) {
|
|
6942
|
-
console.warn("[FlightPreviewController] \u822A\u70B9\u6570\u91CF\u4E0D\u8DB3");
|
|
6943
|
-
return;
|
|
6944
|
-
}
|
|
6945
|
-
let cumulativeDistance = 0;
|
|
6946
|
-
for (let i = 0; i < waypointData.length; i++) {
|
|
6947
|
-
const wp = waypointData[i];
|
|
6948
|
-
let distanceToNext = 0;
|
|
6949
|
-
if (i < waypointData.length - 1) {
|
|
6950
|
-
const nextWp = waypointData[i + 1];
|
|
6951
|
-
distanceToNext = C.Cartesian3.distance(wp.position, nextWp.position);
|
|
6952
|
-
}
|
|
6953
|
-
this.waypoints.push({
|
|
6954
|
-
position: wp.position,
|
|
6955
|
-
heading: wp.heading ?? 0,
|
|
6956
|
-
pitch: wp.pitch ?? -10,
|
|
6957
|
-
roll: wp.roll ?? 0,
|
|
6958
|
-
distanceToNext,
|
|
6959
|
-
cumulativeDistance
|
|
6960
|
-
});
|
|
6961
|
-
cumulativeDistance += distanceToNext;
|
|
6962
|
-
}
|
|
6963
|
-
this.totalDistance = cumulativeDistance;
|
|
6964
|
-
}
|
|
6965
|
-
/** 初始化可视化组件 */
|
|
6966
|
-
initVisualization() {
|
|
6967
|
-
this.initDroneModel();
|
|
6968
|
-
this.initFrustum();
|
|
6969
|
-
this.initTrackHighlight();
|
|
6970
|
-
this.initPathPreview();
|
|
6971
|
-
this.updateVisualization();
|
|
6972
|
-
}
|
|
6973
|
-
/** 初始化无人机模型 */
|
|
6974
|
-
initDroneModel() {
|
|
6975
|
-
if (!this.layer || this.waypoints.length === 0) return;
|
|
6976
|
-
const C = this.CesiumNS;
|
|
6977
|
-
const layer = this.layer;
|
|
6978
|
-
const modelHeadingOffset = 270;
|
|
6979
|
-
const modelVerticalOffset = 35;
|
|
6980
|
-
const modelForwardOffset = 5;
|
|
6981
|
-
const modelRightOffset = -30;
|
|
6982
|
-
this.droneEntity = layer.entities.add({
|
|
6983
|
-
position: new C.CallbackProperty(() => {
|
|
6984
|
-
const { position, heading, pitch } = this.getCurrentPose();
|
|
6985
|
-
return this.applyModelOffset(position, heading, pitch, {
|
|
6986
|
-
forward: modelForwardOffset,
|
|
6987
|
-
right: modelRightOffset,
|
|
6988
|
-
up: modelVerticalOffset
|
|
6989
|
-
});
|
|
6990
|
-
}, false),
|
|
6991
|
-
orientation: new C.CallbackProperty(() => {
|
|
6992
|
-
const { position, heading, pitch, roll } = this.getCurrentPose();
|
|
6993
|
-
const hpr = new C.HeadingPitchRoll(
|
|
6994
|
-
C.Math.toRadians(heading + modelHeadingOffset),
|
|
6995
|
-
C.Math.toRadians(pitch),
|
|
6996
|
-
C.Math.toRadians(roll)
|
|
6997
|
-
);
|
|
6998
|
-
return C.Transforms.headingPitchRollQuaternion(position, hpr);
|
|
6999
|
-
}, false),
|
|
7000
|
-
model: {
|
|
7001
|
-
uri: wurenji_default,
|
|
7002
|
-
scale: 0.5,
|
|
7003
|
-
minimumPixelSize: 32,
|
|
7004
|
-
maximumScale: 100,
|
|
7005
|
-
silhouetteColor: C.Color.CYAN,
|
|
7006
|
-
silhouetteSize: 1
|
|
7007
|
-
},
|
|
7008
|
-
properties: { _type: "flight-preview-drone" }
|
|
7009
|
-
});
|
|
7010
|
-
}
|
|
7011
|
-
/** 应用模型偏移 */
|
|
7012
|
-
applyModelOffset(position, heading, pitch, offset) {
|
|
7013
|
-
const C = this.CesiumNS;
|
|
7014
|
-
const headingRad = C.Math.toRadians(heading);
|
|
7015
|
-
const pitchRad = C.Math.toRadians(pitch);
|
|
7016
|
-
const enu = C.Transforms.eastNorthUpToFixedFrame(position);
|
|
7017
|
-
const east = C.Cartesian3.fromCartesian4(C.Matrix4.getColumn(enu, 0, new C.Cartesian4()));
|
|
7018
|
-
const north = C.Cartesian3.fromCartesian4(C.Matrix4.getColumn(enu, 1, new C.Cartesian4()));
|
|
7019
|
-
const up = C.Cartesian3.fromCartesian4(C.Matrix4.getColumn(enu, 2, new C.Cartesian4()));
|
|
7020
|
-
const horizForward = C.Cartesian3.add(
|
|
7021
|
-
C.Cartesian3.multiplyByScalar(east, Math.sin(headingRad), new C.Cartesian3()),
|
|
7022
|
-
C.Cartesian3.multiplyByScalar(north, Math.cos(headingRad), new C.Cartesian3()),
|
|
7023
|
-
new C.Cartesian3()
|
|
7024
|
-
);
|
|
7025
|
-
const right = C.Cartesian3.add(
|
|
7026
|
-
C.Cartesian3.multiplyByScalar(east, Math.cos(headingRad), new C.Cartesian3()),
|
|
7027
|
-
C.Cartesian3.multiplyByScalar(north, -Math.sin(headingRad), new C.Cartesian3()),
|
|
7028
|
-
new C.Cartesian3()
|
|
7029
|
-
);
|
|
7030
|
-
const forward = C.Cartesian3.add(
|
|
7031
|
-
C.Cartesian3.multiplyByScalar(horizForward, Math.cos(pitchRad), new C.Cartesian3()),
|
|
7032
|
-
C.Cartesian3.multiplyByScalar(up, Math.sin(pitchRad), new C.Cartesian3()),
|
|
7033
|
-
new C.Cartesian3()
|
|
7034
|
-
);
|
|
7035
|
-
const upRotated = C.Cartesian3.add(
|
|
7036
|
-
C.Cartesian3.multiplyByScalar(horizForward, -Math.sin(pitchRad), new C.Cartesian3()),
|
|
7037
|
-
C.Cartesian3.multiplyByScalar(up, Math.cos(pitchRad), new C.Cartesian3()),
|
|
7038
|
-
new C.Cartesian3()
|
|
7039
|
-
);
|
|
7040
|
-
let result = C.Cartesian3.clone(position);
|
|
7041
|
-
result = C.Cartesian3.add(result, C.Cartesian3.multiplyByScalar(forward, offset.forward, new C.Cartesian3()), result);
|
|
7042
|
-
result = C.Cartesian3.add(result, C.Cartesian3.multiplyByScalar(right, offset.right, new C.Cartesian3()), result);
|
|
7043
|
-
result = C.Cartesian3.add(result, C.Cartesian3.multiplyByScalar(upRotated, offset.up, new C.Cartesian3()), result);
|
|
7044
|
-
return result;
|
|
7045
|
-
}
|
|
7046
|
-
/** 初始化视锥体 */
|
|
7047
|
-
initFrustum() {
|
|
7048
|
-
if (!this.showFrustum || !this.layer) return;
|
|
7049
|
-
const C = this.CesiumNS;
|
|
7050
|
-
this.frustum = new FrustumPyramid(this.CesiumNS, this.layer, {
|
|
7051
|
-
fov: 50,
|
|
7052
|
-
length: 500,
|
|
7053
|
-
color: C.Color.CYAN,
|
|
7054
|
-
fillAlpha: 0.25,
|
|
7055
|
-
width: 2
|
|
7056
|
-
});
|
|
7057
|
-
this.frustum.showAtDynamic(
|
|
7058
|
-
() => this.getCurrentPose().position,
|
|
7059
|
-
() => this.getCurrentPose().heading,
|
|
7060
|
-
() => this.getCurrentPose().pitch,
|
|
7061
|
-
() => this.getCurrentPose().roll,
|
|
7062
|
-
() => 50,
|
|
7063
|
-
() => 500
|
|
7064
|
-
);
|
|
7065
|
-
}
|
|
7066
|
-
/** 初始化轨迹高亮 */
|
|
7067
|
-
initTrackHighlight() {
|
|
7068
|
-
if (!this.trackHighlightOptions.enabled || !this.layer) return;
|
|
7069
|
-
if (this.waypoints.length < 2) return;
|
|
7070
|
-
const C = this.CesiumNS;
|
|
7071
|
-
const layer = this.layer;
|
|
7072
|
-
const traveledColor = this.trackHighlightOptions.traveledColor ?? C.Color.fromCssColorString("#00FF88");
|
|
7073
|
-
const remainingColor = this.trackHighlightOptions.remainingColor ?? C.Color.fromCssColorString("#4A90D9").withAlpha(0.7);
|
|
7074
|
-
this.traveledPathEntity = layer.entities.add({
|
|
7075
|
-
polyline: {
|
|
7076
|
-
positions: new C.CallbackProperty(() => this.getTraveledPositions(), false),
|
|
7077
|
-
width: this.trackHighlightOptions.traveledWidth ?? 6,
|
|
7078
|
-
material: traveledColor,
|
|
7079
|
-
clampToGround: false
|
|
7080
|
-
},
|
|
7081
|
-
properties: { _type: "flight-preview-traveled" }
|
|
7082
|
-
});
|
|
7083
|
-
this.remainingPathEntity = layer.entities.add({
|
|
7084
|
-
polyline: {
|
|
7085
|
-
positions: new C.CallbackProperty(() => this.getRemainingPositions(), false),
|
|
7086
|
-
width: this.trackHighlightOptions.remainingWidth ?? 4,
|
|
7087
|
-
material: remainingColor,
|
|
7088
|
-
clampToGround: false
|
|
7089
|
-
},
|
|
7090
|
-
properties: { _type: "flight-preview-remaining" }
|
|
7091
|
-
});
|
|
7092
|
-
}
|
|
7093
|
-
/** 初始化预览窗口 */
|
|
7094
|
-
initPathPreview() {
|
|
7095
|
-
this.pathPreview = new PathPreview(
|
|
7096
|
-
this.CesiumNS,
|
|
7097
|
-
this.viewer,
|
|
7098
|
-
{
|
|
7099
|
-
container: this.options.previewContainer,
|
|
7100
|
-
fov: 50,
|
|
7101
|
-
pitch: -10,
|
|
7102
|
-
useGlobalEventBus: true
|
|
7103
|
-
}
|
|
7104
|
-
);
|
|
7105
|
-
this.pathPreview.hide();
|
|
7106
|
-
}
|
|
7107
|
-
// ==================== 私有方法 - 动画循环 ====================
|
|
7108
|
-
/** 启动动画循环 */
|
|
7109
|
-
startAnimationLoop() {
|
|
7110
|
-
if (this.animationFrameId) return;
|
|
7111
|
-
const animate = (currentTime) => {
|
|
7112
|
-
if (this.state !== "playing" || this.destroyed) {
|
|
7113
|
-
this.animationFrameId = void 0;
|
|
7114
|
-
return;
|
|
7115
|
-
}
|
|
7116
|
-
const deltaTime = this.lastFrameTime ? (currentTime - this.lastFrameTime) / 1e3 : 0;
|
|
7117
|
-
this.lastFrameTime = currentTime;
|
|
7118
|
-
this.currentDistance += this.speed * deltaTime;
|
|
7119
|
-
if (this.currentDistance >= this.totalDistance) {
|
|
7120
|
-
if (this.loop) {
|
|
7121
|
-
this.currentDistance = this.currentDistance % this.totalDistance;
|
|
7122
|
-
} else {
|
|
7123
|
-
this.currentDistance = this.totalDistance;
|
|
7124
|
-
this.setState("stopped");
|
|
7125
|
-
this.stopAnimationLoop();
|
|
7126
|
-
return;
|
|
7127
|
-
}
|
|
7128
|
-
}
|
|
7129
|
-
this.updateVisualization();
|
|
7130
|
-
this.animationFrameId = requestAnimationFrame(animate);
|
|
7131
|
-
};
|
|
7132
|
-
this.animationFrameId = requestAnimationFrame(animate);
|
|
7133
|
-
}
|
|
7134
|
-
/** 停止动画循环 */
|
|
7135
|
-
stopAnimationLoop() {
|
|
7136
|
-
if (this.animationFrameId) {
|
|
7137
|
-
cancelAnimationFrame(this.animationFrameId);
|
|
7138
|
-
this.animationFrameId = void 0;
|
|
7139
|
-
}
|
|
7140
|
-
this.lastFrameTime = void 0;
|
|
7141
|
-
}
|
|
7142
|
-
// ==================== 私有方法 - 路径插值 ====================
|
|
7143
|
-
/** 查找当前所在航段 */
|
|
7144
|
-
findCurrentSegment() {
|
|
7145
|
-
if (this.waypoints.length === 0) {
|
|
7146
|
-
return { segmentIndex: 0, progressInSegment: 0 };
|
|
7147
|
-
}
|
|
7148
|
-
for (let i = 0; i < this.waypoints.length - 1; i++) {
|
|
7149
|
-
const wp = this.waypoints[i];
|
|
7150
|
-
const nextWp = this.waypoints[i + 1];
|
|
7151
|
-
if (this.currentDistance <= nextWp.cumulativeDistance) {
|
|
7152
|
-
const segmentStart = wp.cumulativeDistance;
|
|
7153
|
-
const segmentLength = wp.distanceToNext;
|
|
7154
|
-
const distanceInSegment = this.currentDistance - segmentStart;
|
|
7155
|
-
const progressInSegment = segmentLength > 0 ? distanceInSegment / segmentLength : 0;
|
|
7156
|
-
return { segmentIndex: i, progressInSegment };
|
|
7157
|
-
}
|
|
7158
|
-
}
|
|
7159
|
-
return { segmentIndex: this.waypoints.length - 2, progressInSegment: 1 };
|
|
7160
|
-
}
|
|
7161
|
-
/** 获取当前姿态 */
|
|
7162
|
-
getCurrentPose() {
|
|
7163
|
-
if (this.waypoints.length === 0) {
|
|
7164
|
-
const C = this.CesiumNS;
|
|
7165
|
-
return {
|
|
7166
|
-
position: new C.Cartesian3(),
|
|
7167
|
-
heading: 0,
|
|
7168
|
-
pitch: -10,
|
|
7169
|
-
roll: 0
|
|
7170
|
-
};
|
|
7171
|
-
}
|
|
7172
|
-
const { segmentIndex, progressInSegment } = this.findCurrentSegment();
|
|
7173
|
-
const wp = this.waypoints[segmentIndex];
|
|
7174
|
-
const nextWp = this.waypoints[Math.min(segmentIndex + 1, this.waypoints.length - 1)];
|
|
7175
|
-
const position = this.interpolatePosition(wp.position, nextWp.position, progressInSegment);
|
|
7176
|
-
const heading = this.interpolateAngle(wp.heading, nextWp.heading, progressInSegment);
|
|
7177
|
-
const pitch = this.lerp(wp.pitch, nextWp.pitch, progressInSegment);
|
|
7178
|
-
const roll = this.lerp(wp.roll, nextWp.roll, progressInSegment);
|
|
7179
|
-
return { position, heading, pitch, roll };
|
|
7180
|
-
}
|
|
7181
|
-
/** 位置插值 */
|
|
7182
|
-
interpolatePosition(start, end, t) {
|
|
7183
|
-
const C = this.CesiumNS;
|
|
7184
|
-
return C.Cartesian3.lerp(start, end, t, new C.Cartesian3());
|
|
7185
|
-
}
|
|
7186
|
-
/** 角度插值(处理 0-360 环绕) */
|
|
7187
|
-
interpolateAngle(start, end, t) {
|
|
7188
|
-
let diff = end - start;
|
|
7189
|
-
if (diff > 180) diff -= 360;
|
|
7190
|
-
if (diff < -180) diff += 360;
|
|
7191
|
-
let result = start + diff * t;
|
|
7192
|
-
return (result % 360 + 360) % 360;
|
|
7193
|
-
}
|
|
7194
|
-
/** 线性插值 */
|
|
7195
|
-
lerp(start, end, t) {
|
|
7196
|
-
return start + (end - start) * t;
|
|
7197
|
-
}
|
|
7198
|
-
// ==================== 私有方法 - 轨迹高亮 ====================
|
|
7199
|
-
/** 获取已飞过的位置数组 */
|
|
7200
|
-
getTraveledPositions() {
|
|
7201
|
-
if (this.waypoints.length === 0) return [];
|
|
7202
|
-
const { segmentIndex, progressInSegment } = this.findCurrentSegment();
|
|
7203
|
-
const currentPos = this.getCurrentPose().position;
|
|
7204
|
-
const positions = [];
|
|
7205
|
-
for (let i = 0; i <= segmentIndex; i++) {
|
|
7206
|
-
positions.push(this.waypoints[i].position);
|
|
7207
|
-
}
|
|
7208
|
-
if (progressInSegment > 0) {
|
|
7209
|
-
positions.push(currentPos);
|
|
7210
|
-
}
|
|
7211
|
-
return positions;
|
|
7212
|
-
}
|
|
7213
|
-
/** 获取未飞过的位置数组 */
|
|
7214
|
-
getRemainingPositions() {
|
|
7215
|
-
if (this.waypoints.length === 0) return [];
|
|
7216
|
-
const { segmentIndex } = this.findCurrentSegment();
|
|
7217
|
-
const currentPos = this.getCurrentPose().position;
|
|
7218
|
-
const positions = [];
|
|
7219
|
-
positions.push(currentPos);
|
|
7220
|
-
for (let i = segmentIndex + 1; i < this.waypoints.length; i++) {
|
|
7221
|
-
positions.push(this.waypoints[i].position);
|
|
7222
|
-
}
|
|
7223
|
-
return positions;
|
|
7224
|
-
}
|
|
7225
|
-
// ==================== 私有方法 - 更新 ====================
|
|
7226
|
-
/** 更新可视化 */
|
|
7227
|
-
updateVisualization() {
|
|
7228
|
-
const pose = this.getCurrentPose();
|
|
7229
|
-
if (this.pathPreview) {
|
|
7230
|
-
this.pathPreview.setPose(
|
|
7231
|
-
pose.position,
|
|
7232
|
-
pose.heading,
|
|
7233
|
-
pose.pitch,
|
|
7234
|
-
pose.roll
|
|
7235
|
-
);
|
|
7236
|
-
}
|
|
7237
|
-
this.viewer.scene?.requestRender?.();
|
|
7238
|
-
const { segmentIndex } = this.findCurrentSegment();
|
|
7239
|
-
this.onProgressChange.emit({
|
|
7240
|
-
progress: this.getProgress(),
|
|
7241
|
-
position: pose.position,
|
|
7242
|
-
waypointIndex: segmentIndex,
|
|
7243
|
-
pose: {
|
|
7244
|
-
heading: pose.heading,
|
|
7245
|
-
pitch: pose.pitch,
|
|
7246
|
-
roll: pose.roll
|
|
7247
|
-
}
|
|
7248
|
-
});
|
|
7249
|
-
}
|
|
7250
|
-
/** 设置状态 */
|
|
7251
|
-
setState(newState) {
|
|
7252
|
-
if (this.state !== newState) {
|
|
7253
|
-
this.state = newState;
|
|
7254
|
-
this.onStateChange.emit({ state: newState });
|
|
7255
|
-
}
|
|
7256
|
-
}
|
|
7257
|
-
/** 销毁可视化组件 */
|
|
7258
|
-
destroyVisualization() {
|
|
7259
|
-
const layer = this.layer;
|
|
7260
|
-
if (this.droneEntity && layer) {
|
|
7261
|
-
try {
|
|
7262
|
-
layer.entities.remove(this.droneEntity);
|
|
7263
|
-
} catch {
|
|
7264
|
-
}
|
|
7265
|
-
}
|
|
7266
|
-
if (this.traveledPathEntity && layer) {
|
|
7267
|
-
try {
|
|
7268
|
-
layer.entities.remove(this.traveledPathEntity);
|
|
7269
|
-
} catch {
|
|
7270
|
-
}
|
|
7271
|
-
}
|
|
7272
|
-
if (this.remainingPathEntity && layer) {
|
|
7273
|
-
try {
|
|
7274
|
-
layer.entities.remove(this.remainingPathEntity);
|
|
7275
|
-
} catch {
|
|
7276
|
-
}
|
|
7277
|
-
}
|
|
7278
|
-
try {
|
|
7279
|
-
this.frustum?.destroy();
|
|
7280
|
-
} catch {
|
|
7281
|
-
}
|
|
7282
|
-
try {
|
|
7283
|
-
this.pathPreview?.destroy();
|
|
7284
|
-
} catch {
|
|
7285
|
-
}
|
|
7286
|
-
if (!this.options.layer && this.layer) {
|
|
7287
|
-
try {
|
|
7288
|
-
this.viewer.dataSources.remove(this.layer);
|
|
7289
|
-
} catch {
|
|
7290
|
-
}
|
|
7291
|
-
}
|
|
7292
|
-
this.droneEntity = void 0;
|
|
7293
|
-
this.traveledPathEntity = void 0;
|
|
7294
|
-
this.remainingPathEntity = void 0;
|
|
7295
|
-
this.frustum = void 0;
|
|
7296
|
-
this.pathPreview = void 0;
|
|
7297
|
-
this.layer = void 0;
|
|
7298
|
-
}
|
|
7299
|
-
};
|
|
7300
|
-
|
|
7301
6794
|
// src/core/CZMLPathManager.ts
|
|
7302
6795
|
var _CZMLPathManager = class _CZMLPathManager {
|
|
7303
6796
|
constructor(CesiumNS, viewer) {
|
|
@@ -7411,50 +6904,6 @@ var _CZMLPathManager = class _CZMLPathManager {
|
|
|
7411
6904
|
renderFlightPathPreview(options) {
|
|
7412
6905
|
return renderFlightPathPreview(this.CesiumNS, this.viewer, options);
|
|
7413
6906
|
}
|
|
7414
|
-
/**
|
|
7415
|
-
* 开始飞行预览:飞机沿航线飞行动画
|
|
7416
|
-
*
|
|
7417
|
-
* 功能:
|
|
7418
|
-
* - 飞机游标沿航线从起点飞到终点,循环播放
|
|
7419
|
-
* - 预览窗口实时显示飞机第一人称视角
|
|
7420
|
-
* - 轨迹高亮显示已飞过/未飞过的航段
|
|
7421
|
-
* - 预览模式下禁止选择/编辑航线、航点
|
|
7422
|
-
*
|
|
7423
|
-
* @param options 飞行预览选项
|
|
7424
|
-
* @returns 飞行预览控制器
|
|
7425
|
-
*
|
|
7426
|
-
* @example
|
|
7427
|
-
* ```typescript
|
|
7428
|
-
* const preview = pathManager.startFlightPreview({
|
|
7429
|
-
* waylineData: myWayline,
|
|
7430
|
-
* speed: 15, // 飞行速度 15m/s
|
|
7431
|
-
* loop: true, // 循环播放
|
|
7432
|
-
* trackHighlight: {
|
|
7433
|
-
* enabled: true,
|
|
7434
|
-
* traveledColor: Cesium.Color.fromCssColorString('#00FF88'),
|
|
7435
|
-
* remainingColor: Cesium.Color.fromCssColorString('#4A90D9'),
|
|
7436
|
-
* },
|
|
7437
|
-
* });
|
|
7438
|
-
*
|
|
7439
|
-
* preview.start(); // 开始播放
|
|
7440
|
-
* preview.pause(); // 暂停
|
|
7441
|
-
* preview.resume(); // 继续
|
|
7442
|
-
* preview.stop(); // 停止
|
|
7443
|
-
* preview.seek(0.5); // 跳转到 50%
|
|
7444
|
-
* preview.setSpeed(20); // 设置速度
|
|
7445
|
-
*
|
|
7446
|
-
* // 监听进度变化
|
|
7447
|
-
* preview.onProgressChange.on(({ progress, waypointIndex }) => {
|
|
7448
|
-
* console.log(`进度: ${(progress * 100).toFixed(1)}%`);
|
|
7449
|
-
* });
|
|
7450
|
-
*
|
|
7451
|
-
* // 销毁
|
|
7452
|
-
* preview.destroy();
|
|
7453
|
-
* ```
|
|
7454
|
-
*/
|
|
7455
|
-
startFlightPreview(options) {
|
|
7456
|
-
return new FlightPreviewController(this.CesiumNS, this.viewer, options);
|
|
7457
|
-
}
|
|
7458
6907
|
resolveEntity(entityOrId) {
|
|
7459
6908
|
return typeof entityOrId === "string" ? this.viewer.entities.getById(entityOrId) : entityOrId;
|
|
7460
6909
|
}
|
|
@@ -7650,6 +7099,8 @@ var PolygonEditor = class {
|
|
|
7650
7099
|
__publicField(this, "keydownHandler");
|
|
7651
7100
|
// 保存编码前的原始名称,用于还原
|
|
7652
7101
|
__publicField(this, "originalPolygonNames", /* @__PURE__ */ new Map());
|
|
7102
|
+
// 保存双击缩放是否被禁用的状态
|
|
7103
|
+
__publicField(this, "doubleClickZoomDisabled", false);
|
|
7653
7104
|
}
|
|
7654
7105
|
/**
|
|
7655
7106
|
* 根据图层名称获取颜色配置
|
|
@@ -7924,12 +7375,62 @@ var PolygonEditor = class {
|
|
|
7924
7375
|
}
|
|
7925
7376
|
this.handler = void 0;
|
|
7926
7377
|
}
|
|
7378
|
+
/**
|
|
7379
|
+
* 禁用 Cesium 默认的双击缩放行为
|
|
7380
|
+
* 在绘制/编辑过程中调用,防止双击导致相机乱飞
|
|
7381
|
+
*/
|
|
7382
|
+
disableDoubleClickZoom() {
|
|
7383
|
+
if (this.doubleClickZoomDisabled) return;
|
|
7384
|
+
try {
|
|
7385
|
+
const C = this.CesiumNS;
|
|
7386
|
+
const cesiumWidget = this.viewer.cesiumWidget;
|
|
7387
|
+
if (cesiumWidget?.screenSpaceEventHandler) {
|
|
7388
|
+
cesiumWidget.screenSpaceEventHandler.removeInputAction(
|
|
7389
|
+
C.ScreenSpaceEventType.LEFT_DOUBLE_CLICK
|
|
7390
|
+
);
|
|
7391
|
+
this.doubleClickZoomDisabled = true;
|
|
7392
|
+
}
|
|
7393
|
+
} catch (e) {
|
|
7394
|
+
console.warn("[PolygonEditor] \u7981\u7528\u53CC\u51FB\u7F29\u653E\u5931\u8D25:", e);
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7397
|
+
/**
|
|
7398
|
+
* 恢复 Cesium 默认的双击缩放行为
|
|
7399
|
+
* 在绘制/编辑结束后调用
|
|
7400
|
+
*/
|
|
7401
|
+
restoreDoubleClickZoom() {
|
|
7402
|
+
if (!this.doubleClickZoomDisabled) return;
|
|
7403
|
+
try {
|
|
7404
|
+
const C = this.CesiumNS;
|
|
7405
|
+
const viewer = this.viewer;
|
|
7406
|
+
const cesiumWidget = viewer.cesiumWidget;
|
|
7407
|
+
if (cesiumWidget?.screenSpaceEventHandler) {
|
|
7408
|
+
cesiumWidget.screenSpaceEventHandler.setInputAction(
|
|
7409
|
+
(movement) => {
|
|
7410
|
+
try {
|
|
7411
|
+
const pickedObject = viewer.scene.pick(movement.position);
|
|
7412
|
+
if (pickedObject?.id && viewer.trackedEntity !== pickedObject.id) {
|
|
7413
|
+
viewer.flyTo(pickedObject.id, { duration: 1.5 });
|
|
7414
|
+
}
|
|
7415
|
+
} catch {
|
|
7416
|
+
}
|
|
7417
|
+
},
|
|
7418
|
+
C.ScreenSpaceEventType.LEFT_DOUBLE_CLICK
|
|
7419
|
+
);
|
|
7420
|
+
this.doubleClickZoomDisabled = false;
|
|
7421
|
+
}
|
|
7422
|
+
} catch (e) {
|
|
7423
|
+
console.warn("[PolygonEditor] \u6062\u590D\u53CC\u51FB\u7F29\u653E\u5931\u8D25:", e);
|
|
7424
|
+
this.doubleClickZoomDisabled = false;
|
|
7425
|
+
}
|
|
7426
|
+
}
|
|
7927
7427
|
/**
|
|
7928
7428
|
* 完成编辑/绘制的通用清理逻辑
|
|
7929
7429
|
*/
|
|
7930
7430
|
finalizePolygonSession(layer, onComplete, entity) {
|
|
7931
7431
|
this.destroyHandler();
|
|
7932
7432
|
this.removeKeyboardListener();
|
|
7433
|
+
this.restoreDoubleClickZoom();
|
|
7933
7434
|
if (entity && onComplete) {
|
|
7934
7435
|
onComplete(entity);
|
|
7935
7436
|
}
|
|
@@ -7972,6 +7473,7 @@ var PolygonEditor = class {
|
|
|
7972
7473
|
}
|
|
7973
7474
|
this.clearAllPolygonHighlights();
|
|
7974
7475
|
this.stopInternal(layer);
|
|
7476
|
+
this.disableDoubleClickZoom();
|
|
7975
7477
|
this.drawingPositions = [];
|
|
7976
7478
|
this.drawHandles = [];
|
|
7977
7479
|
this.rubberBandPoint = void 0;
|
|
@@ -8116,6 +7618,7 @@ var PolygonEditor = class {
|
|
|
8116
7618
|
}
|
|
8117
7619
|
this.destroyHandler();
|
|
8118
7620
|
this.removeKeyboardListener();
|
|
7621
|
+
this.restoreDoubleClickZoom();
|
|
8119
7622
|
}
|
|
8120
7623
|
/**
|
|
8121
7624
|
* 开始编辑多边形
|
|
@@ -8127,6 +7630,7 @@ var PolygonEditor = class {
|
|
|
8127
7630
|
startEditing(polygonEntity, layer, onComplete) {
|
|
8128
7631
|
const C = this.CesiumNS;
|
|
8129
7632
|
this.cleanupHandles(layer);
|
|
7633
|
+
this.disableDoubleClickZoom();
|
|
8130
7634
|
const colors = this.getColorConfig(layer.name);
|
|
8131
7635
|
const line_color = colors.lineColor;
|
|
8132
7636
|
const line_fina_color = colors.lineFinalColor;
|
|
@@ -8693,7 +8197,7 @@ var PolygonEditor = class {
|
|
|
8693
8197
|
if (!polygon) return [];
|
|
8694
8198
|
const hierarchy = typeof polygon.hierarchy?.getValue === "function" ? polygon.hierarchy.getValue(C.JulianDate.now()) : polygon.hierarchy;
|
|
8695
8199
|
const positions = hierarchy?.positions ?? hierarchy ?? [];
|
|
8696
|
-
|
|
8200
|
+
const points = positions.map((cartesian) => {
|
|
8697
8201
|
const cartographic = C.Cartographic.fromCartesian(cartesian);
|
|
8698
8202
|
return {
|
|
8699
8203
|
lon: C.Math.toDegrees(cartographic.longitude),
|
|
@@ -8701,6 +8205,14 @@ var PolygonEditor = class {
|
|
|
8701
8205
|
height: cartographic.height
|
|
8702
8206
|
};
|
|
8703
8207
|
});
|
|
8208
|
+
if (points.length > 0) {
|
|
8209
|
+
const first = points[0];
|
|
8210
|
+
const last = points[points.length - 1];
|
|
8211
|
+
if (first.lon !== last.lon || first.lat !== last.lat || first.height !== last.height) {
|
|
8212
|
+
points.push({ ...first });
|
|
8213
|
+
}
|
|
8214
|
+
}
|
|
8215
|
+
return points;
|
|
8704
8216
|
}
|
|
8705
8217
|
/**
|
|
8706
8218
|
* 辅助方法:获取实体所属的 DataSource
|
|
@@ -9549,6 +9061,7 @@ var PolygonEditor = class {
|
|
|
9549
9061
|
const nameStr = `circle_${(circleCount + 1).toString().padStart(2, "0")}`;
|
|
9550
9062
|
this.clearAllPolygonHighlights();
|
|
9551
9063
|
this.stopInternal();
|
|
9064
|
+
this.disableDoubleClickZoom();
|
|
9552
9065
|
let centerPosition = null;
|
|
9553
9066
|
let currentRadius = 0;
|
|
9554
9067
|
let tempCenterPoint;
|
|
@@ -9762,6 +9275,7 @@ var PolygonEditor = class {
|
|
|
9762
9275
|
currentRadius = 0;
|
|
9763
9276
|
this.destroyHandler();
|
|
9764
9277
|
this.removeKeyboardListener();
|
|
9278
|
+
this.restoreDoubleClickZoom();
|
|
9765
9279
|
};
|
|
9766
9280
|
this.handler && this.handler.setInputAction(
|
|
9767
9281
|
cancelCircleDrawing,
|
|
@@ -9797,6 +9311,7 @@ var PolygonEditor = class {
|
|
|
9797
9311
|
const line_fina_color = colors.lineFinalColor;
|
|
9798
9312
|
this.clearAllPolygonHighlights();
|
|
9799
9313
|
this.stopInternal();
|
|
9314
|
+
this.disableDoubleClickZoom();
|
|
9800
9315
|
let pointCount = 0;
|
|
9801
9316
|
this.handler = new C.ScreenSpaceEventHandler(this.viewer.scene.canvas);
|
|
9802
9317
|
this.handler && this.handler.setInputAction(
|
|
@@ -9891,6 +9406,7 @@ var PolygonEditor = class {
|
|
|
9891
9406
|
const stopPointDrawing = () => {
|
|
9892
9407
|
this.destroyHandler();
|
|
9893
9408
|
this.removeKeyboardListener();
|
|
9409
|
+
this.restoreDoubleClickZoom();
|
|
9894
9410
|
};
|
|
9895
9411
|
this.handler && this.handler.setInputAction(
|
|
9896
9412
|
stopPointDrawing,
|
|
@@ -10822,7 +10338,15 @@ var CZMLManager = class {
|
|
|
10822
10338
|
const hProp = entity.polygon.hierarchy;
|
|
10823
10339
|
const hVal = typeof hProp?.getValue === "function" ? hProp.getValue(now) : hProp;
|
|
10824
10340
|
const outer = Array.isArray(hVal) ? hVal : hVal?.positions ?? [];
|
|
10825
|
-
|
|
10341
|
+
const points = outer.map(toLonLatHeight);
|
|
10342
|
+
if (points.length > 0) {
|
|
10343
|
+
const first = points[0];
|
|
10344
|
+
const last = points[points.length - 1];
|
|
10345
|
+
if (first.lon !== last.lon || first.lat !== last.lat || first.height !== last.height) {
|
|
10346
|
+
points.push({ ...first });
|
|
10347
|
+
}
|
|
10348
|
+
}
|
|
10349
|
+
return points;
|
|
10826
10350
|
}
|
|
10827
10351
|
if (entity?.polyline?.positions) {
|
|
10828
10352
|
const pProp = entity.polyline.positions;
|
|
@@ -11396,6 +10920,368 @@ function convertPathToSinofly(data, options) {
|
|
|
11396
10920
|
return sinoflyData;
|
|
11397
10921
|
}
|
|
11398
10922
|
|
|
10923
|
+
// src/core/path-manager/PathSafetyChecker.ts
|
|
10924
|
+
var PathSafetyChecker = class {
|
|
10925
|
+
constructor(CesiumNS, viewer) {
|
|
10926
|
+
__publicField(this, "CesiumNS");
|
|
10927
|
+
__publicField(this, "viewer");
|
|
10928
|
+
this.CesiumNS = CesiumNS;
|
|
10929
|
+
this.viewer = viewer;
|
|
10930
|
+
}
|
|
10931
|
+
/**
|
|
10932
|
+
* 检测整条路径的安全性
|
|
10933
|
+
* @param positions 航点位置数组
|
|
10934
|
+
* @param options 检测选项
|
|
10935
|
+
*/
|
|
10936
|
+
checkPath(positions, options) {
|
|
10937
|
+
const defaultDistance = options?.safetyDistance ?? 20;
|
|
10938
|
+
const terrainSafetyDistance = options?.terrainSafetyDistance ?? defaultDistance;
|
|
10939
|
+
const pointCloudSafetyDistance = options?.pointCloudSafetyDistance ?? defaultDistance;
|
|
10940
|
+
const minWaypointDistance = options?.minWaypointDistance ?? 0;
|
|
10941
|
+
const sampleInterval = options?.sampleInterval ?? 5;
|
|
10942
|
+
const results = [];
|
|
10943
|
+
const startSegment = 1 ;
|
|
10944
|
+
console.log("=".repeat(60));
|
|
10945
|
+
console.log("[PathSafetyChecker] \u5F00\u59CB\u5B89\u5168\u68C0\u6D4B");
|
|
10946
|
+
console.log(` \u5730\u5F62\u5B89\u5168\u8DDD\u79BB: ${terrainSafetyDistance}m`);
|
|
10947
|
+
console.log(` \u70B9\u4E91\u5B89\u5168\u8DDD\u79BB: ${pointCloudSafetyDistance}m`);
|
|
10948
|
+
if (minWaypointDistance > 0) {
|
|
10949
|
+
console.log(` \u822A\u70B9\u95F4\u6700\u5C0F\u8DDD\u79BB: ${minWaypointDistance}m`);
|
|
10950
|
+
}
|
|
10951
|
+
console.log(` \u91C7\u6837\u95F4\u9694: ${sampleInterval}m`);
|
|
10952
|
+
console.log(` \u822A\u70B9\u603B\u6570: ${positions.length}`);
|
|
10953
|
+
console.log(` \u68C0\u6D4B\u822A\u6BB5: ${startSegment} ~ ${positions.length - 2}`);
|
|
10954
|
+
console.log("=".repeat(60));
|
|
10955
|
+
for (let i = startSegment; i < positions.length - 1; i++) {
|
|
10956
|
+
const from = positions[i];
|
|
10957
|
+
const to = positions[i + 1];
|
|
10958
|
+
const segmentName = i === 1 ? "S" : String(i);
|
|
10959
|
+
console.log(`
|
|
10960
|
+
[\u68C0\u6D4B\u822A\u6BB5 ${segmentName}]`);
|
|
10961
|
+
const segmentResult = this.checkSegment(
|
|
10962
|
+
from,
|
|
10963
|
+
to,
|
|
10964
|
+
i,
|
|
10965
|
+
i + 1,
|
|
10966
|
+
terrainSafetyDistance,
|
|
10967
|
+
pointCloudSafetyDistance,
|
|
10968
|
+
minWaypointDistance,
|
|
10969
|
+
sampleInterval
|
|
10970
|
+
);
|
|
10971
|
+
results.push(segmentResult);
|
|
10972
|
+
const terrainStatus = segmentResult.minTerrainDistance < 0 ? "\u2753" : segmentResult.isTerrainSafe ? "\u2705" : "\u26A0\uFE0F";
|
|
10973
|
+
const pointCloudStatus = segmentResult.minPointCloudDistance < 0 ? "\u2705" : segmentResult.isPointCloudSafe ? "\u2705" : "\u26A0\uFE0F";
|
|
10974
|
+
const terrainDistStr = segmentResult.minTerrainDistance < 0 ? "\u672A\u68C0\u6D4B\u5230" : `${segmentResult.minTerrainDistance.toFixed(1)}m`;
|
|
10975
|
+
const pointCloudDistStr = segmentResult.minPointCloudDistance < 0 ? "\u65E0\u70B9\u4E91" : `${segmentResult.minPointCloudDistance.toFixed(1)}m`;
|
|
10976
|
+
console.log(` \u5730\u5F62\u8DDD\u79BB: ${terrainStatus} ${terrainDistStr} (\u8981\u6C42 >= ${terrainSafetyDistance}m)`);
|
|
10977
|
+
console.log(` \u70B9\u4E91\u8DDD\u79BB: ${pointCloudStatus} ${pointCloudDistStr} (\u8981\u6C42 >= ${pointCloudSafetyDistance}m)`);
|
|
10978
|
+
if (minWaypointDistance > 0) {
|
|
10979
|
+
const distanceStatus = segmentResult.isDistanceSafe ? "\u2705" : "\u26A0\uFE0F";
|
|
10980
|
+
console.log(` \u822A\u70B9\u95F4\u8DDD: ${distanceStatus} ${segmentResult.segmentLength.toFixed(1)}m (\u8981\u6C42 >= ${minWaypointDistance}m)`);
|
|
10981
|
+
}
|
|
10982
|
+
if (!segmentResult.isSafe) {
|
|
10983
|
+
console.log(` \u26A0\uFE0F \u8BE5\u822A\u6BB5\u4E0D\u5B89\u5168\uFF01\u5371\u9669\u70B9\u6570\u91CF: ${segmentResult.dangerPoints.length}`);
|
|
10984
|
+
}
|
|
10985
|
+
}
|
|
10986
|
+
const unsafeSegments = results.filter((r) => !r.isSafe);
|
|
10987
|
+
console.log("\n" + "=".repeat(60));
|
|
10988
|
+
console.log("[PathSafetyChecker] \u68C0\u6D4B\u5B8C\u6210");
|
|
10989
|
+
console.log(` \u603B\u822A\u6BB5\u6570: ${results.length}`);
|
|
10990
|
+
console.log(` \u5B89\u5168\u822A\u6BB5: ${results.length - unsafeSegments.length}`);
|
|
10991
|
+
console.log(` \u5371\u9669\u822A\u6BB5: ${unsafeSegments.length}`);
|
|
10992
|
+
if (unsafeSegments.length > 0) {
|
|
10993
|
+
console.log("\n\u26A0\uFE0F \u5371\u9669\u822A\u6BB5\u8BE6\u60C5:");
|
|
10994
|
+
unsafeSegments.forEach((seg) => {
|
|
10995
|
+
const issues = [];
|
|
10996
|
+
if (!seg.isTerrainSafe) {
|
|
10997
|
+
issues.push(`\u5730\u5F62\u8DDD\u79BB ${seg.minTerrainDistance.toFixed(1)}m < ${terrainSafetyDistance}m`);
|
|
10998
|
+
}
|
|
10999
|
+
if (!seg.isPointCloudSafe) {
|
|
11000
|
+
issues.push(`\u70B9\u4E91\u8DDD\u79BB ${seg.minPointCloudDistance.toFixed(1)}m < ${pointCloudSafetyDistance}m`);
|
|
11001
|
+
}
|
|
11002
|
+
if (!seg.isDistanceSafe) {
|
|
11003
|
+
issues.push(`\u822A\u70B9\u95F4\u8DDD ${seg.segmentLength.toFixed(1)}m < ${minWaypointDistance}m`);
|
|
11004
|
+
}
|
|
11005
|
+
const segName = seg.fromIndex === 1 ? "S" : String(seg.fromIndex);
|
|
11006
|
+
console.log(` \u822A\u6BB5 ${segName}: ${issues.join(", ")}`);
|
|
11007
|
+
});
|
|
11008
|
+
}
|
|
11009
|
+
console.log("=".repeat(60));
|
|
11010
|
+
return {
|
|
11011
|
+
allSafe: unsafeSegments.length === 0,
|
|
11012
|
+
segments: results,
|
|
11013
|
+
unsafeSegments,
|
|
11014
|
+
terrainSafetyDistance,
|
|
11015
|
+
pointCloudSafetyDistance,
|
|
11016
|
+
minWaypointDistance
|
|
11017
|
+
};
|
|
11018
|
+
}
|
|
11019
|
+
/**
|
|
11020
|
+
* 检测单个航段的安全性
|
|
11021
|
+
* 支持多方向检测(上下左右前后6方向)和按间隔采样
|
|
11022
|
+
*/
|
|
11023
|
+
checkSegment(from, to, fromIndex, toIndex, terrainSafetyDistance, pointCloudSafetyDistance, minWaypointDistance, sampleInterval) {
|
|
11024
|
+
const C = this.CesiumNS;
|
|
11025
|
+
const scene = this.viewer.scene;
|
|
11026
|
+
const dangerPoints = [];
|
|
11027
|
+
let minTerrainDistance = Infinity;
|
|
11028
|
+
let minPointCloudDistance = Infinity;
|
|
11029
|
+
const segmentLength = C.Cartesian3.distance(from, to);
|
|
11030
|
+
const sampleCount = Math.max(2, Math.ceil(segmentLength / sampleInterval) + 1);
|
|
11031
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
11032
|
+
const t = i / (sampleCount - 1);
|
|
11033
|
+
const samplePoint = C.Cartesian3.lerp(from, to, t, new C.Cartesian3());
|
|
11034
|
+
const cartographic = C.Cartographic.fromCartesian(samplePoint);
|
|
11035
|
+
const sampleHeight = cartographic.height;
|
|
11036
|
+
try {
|
|
11037
|
+
const terrainHeight = scene.globe.getHeight(cartographic);
|
|
11038
|
+
if (terrainHeight !== void 0 && terrainHeight !== null) {
|
|
11039
|
+
const terrainDistance = sampleHeight - terrainHeight;
|
|
11040
|
+
minTerrainDistance = Math.min(minTerrainDistance, terrainDistance);
|
|
11041
|
+
if (terrainDistance < terrainSafetyDistance) {
|
|
11042
|
+
dangerPoints.push(C.Cartesian3.clone(samplePoint));
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
} catch (e) {
|
|
11046
|
+
}
|
|
11047
|
+
if (pointCloudSafetyDistance <= 0) {
|
|
11048
|
+
continue;
|
|
11049
|
+
}
|
|
11050
|
+
try {
|
|
11051
|
+
const forward = C.Cartesian3.subtract(to, from, new C.Cartesian3());
|
|
11052
|
+
C.Cartesian3.normalize(forward, forward);
|
|
11053
|
+
const localUp = C.Cartesian3.normalize(samplePoint, new C.Cartesian3());
|
|
11054
|
+
let right = C.Cartesian3.cross(forward, localUp, new C.Cartesian3());
|
|
11055
|
+
let rightMag = C.Cartesian3.magnitude(right);
|
|
11056
|
+
if (rightMag < 1e-3) {
|
|
11057
|
+
right = C.Cartesian3.cross(forward, new C.Cartesian3(1, 0, 0), new C.Cartesian3());
|
|
11058
|
+
rightMag = C.Cartesian3.magnitude(right);
|
|
11059
|
+
if (rightMag < 1e-3) {
|
|
11060
|
+
right = C.Cartesian3.cross(forward, new C.Cartesian3(0, 1, 0), new C.Cartesian3());
|
|
11061
|
+
rightMag = C.Cartesian3.magnitude(right);
|
|
11062
|
+
}
|
|
11063
|
+
}
|
|
11064
|
+
if (rightMag > 1e-3) {
|
|
11065
|
+
C.Cartesian3.normalize(right, right);
|
|
11066
|
+
} else {
|
|
11067
|
+
continue;
|
|
11068
|
+
}
|
|
11069
|
+
const up2 = C.Cartesian3.cross(right, forward, new C.Cartesian3());
|
|
11070
|
+
C.Cartesian3.normalize(up2, up2);
|
|
11071
|
+
const numDirections = 16;
|
|
11072
|
+
for (let d = 0; d < numDirections; d++) {
|
|
11073
|
+
const angle = d / numDirections * Math.PI * 2;
|
|
11074
|
+
const outwardDir = new C.Cartesian3();
|
|
11075
|
+
C.Cartesian3.multiplyByScalar(right, Math.cos(angle), outwardDir);
|
|
11076
|
+
const upComponent = new C.Cartesian3();
|
|
11077
|
+
C.Cartesian3.multiplyByScalar(up2, Math.sin(angle), upComponent);
|
|
11078
|
+
C.Cartesian3.add(outwardDir, upComponent, outwardDir);
|
|
11079
|
+
C.Cartesian3.normalize(outwardDir, outwardDir);
|
|
11080
|
+
const surfacePoint = new C.Cartesian3();
|
|
11081
|
+
C.Cartesian3.multiplyByScalar(outwardDir, pointCloudSafetyDistance, surfacePoint);
|
|
11082
|
+
C.Cartesian3.add(samplePoint, surfacePoint, surfacePoint);
|
|
11083
|
+
const inwardDir = C.Cartesian3.negate(outwardDir, new C.Cartesian3());
|
|
11084
|
+
const ray = new C.Ray(surfacePoint, inwardDir);
|
|
11085
|
+
const intersections = scene.drillPickFromRay(ray, 10);
|
|
11086
|
+
for (const intersection of intersections || []) {
|
|
11087
|
+
if (!intersection?.position) continue;
|
|
11088
|
+
const primitive = intersection.object?.primitive;
|
|
11089
|
+
const primitiveName = primitive?.constructor?.name || "";
|
|
11090
|
+
if (primitiveName === "Polyline" || primitiveName === "PointPrimitive" || primitiveName === "PointPrimitiveCollection" || primitiveName === "Billboard" || primitiveName === "Label" || primitiveName === "Cylinder" || primitiveName === "Ellipse" || primitiveName === "Model" || primitiveName === "ModelExperimental" || primitiveName === "Globe" || primitiveName === "GlobeSurfaceTile") {
|
|
11091
|
+
continue;
|
|
11092
|
+
}
|
|
11093
|
+
if (primitiveName === "Primitive" && primitive?.appearance) {
|
|
11094
|
+
const appearanceName = primitive.appearance?.constructor?.name || "";
|
|
11095
|
+
if (appearanceName === "PerInstanceColorAppearance" || appearanceName === "MaterialAppearance" || appearanceName === "EllipsoidSurfaceAppearance") {
|
|
11096
|
+
continue;
|
|
11097
|
+
}
|
|
11098
|
+
}
|
|
11099
|
+
const is3DTiles = primitive?.tileset !== void 0 || primitive?._tileset !== void 0 || intersection.object?.content?.tileset !== void 0;
|
|
11100
|
+
if (!is3DTiles && primitiveName !== "Cesium3DTilePointFeature") {
|
|
11101
|
+
continue;
|
|
11102
|
+
}
|
|
11103
|
+
if (i === 0 && d === 0) {
|
|
11104
|
+
console.log("[\u8C03\u8BD5] \u68C0\u6D4B\u5230\u70B9\u4E91\u5BF9\u8C61:", primitiveName);
|
|
11105
|
+
console.log("[\u8C03\u8BD5] primitive:", primitive);
|
|
11106
|
+
}
|
|
11107
|
+
const distFromCenter = C.Cartesian3.distance(samplePoint, intersection.position);
|
|
11108
|
+
minPointCloudDistance = Math.min(minPointCloudDistance, distFromCenter);
|
|
11109
|
+
if (distFromCenter < pointCloudSafetyDistance) {
|
|
11110
|
+
const alreadyAdded = dangerPoints.some(
|
|
11111
|
+
(p) => C.Cartesian3.distance(p, samplePoint) < 0.1
|
|
11112
|
+
);
|
|
11113
|
+
if (!alreadyAdded) {
|
|
11114
|
+
dangerPoints.push(C.Cartesian3.clone(samplePoint));
|
|
11115
|
+
console.log(`[\u5371\u9669] \u91C7\u6837\u70B9 ${i}, \u65B9\u5411 ${d}: \u70B9\u4E91(${primitiveName})\u5728\u5706\u67F1\u4F53\u5185\uFF0C\u8DDD\u4E2D\u5FC3 ${distFromCenter.toFixed(1)}m`);
|
|
11116
|
+
}
|
|
11117
|
+
}
|
|
11118
|
+
break;
|
|
11119
|
+
}
|
|
11120
|
+
}
|
|
11121
|
+
} catch (e) {
|
|
11122
|
+
}
|
|
11123
|
+
}
|
|
11124
|
+
const isTerrainSafe = minTerrainDistance !== Infinity && minTerrainDistance >= terrainSafetyDistance;
|
|
11125
|
+
const isPointCloudSafe = minPointCloudDistance === Infinity || minPointCloudDistance >= pointCloudSafetyDistance;
|
|
11126
|
+
const isDistanceSafe = minWaypointDistance <= 0 || segmentLength >= minWaypointDistance;
|
|
11127
|
+
return {
|
|
11128
|
+
fromIndex,
|
|
11129
|
+
toIndex,
|
|
11130
|
+
isTerrainSafe,
|
|
11131
|
+
isPointCloudSafe,
|
|
11132
|
+
isDistanceSafe,
|
|
11133
|
+
isSafe: isTerrainSafe && isPointCloudSafe && isDistanceSafe,
|
|
11134
|
+
minTerrainDistance: minTerrainDistance === Infinity ? -1 : minTerrainDistance,
|
|
11135
|
+
minPointCloudDistance: minPointCloudDistance === Infinity ? -1 : minPointCloudDistance,
|
|
11136
|
+
segmentLength,
|
|
11137
|
+
dangerPoints
|
|
11138
|
+
};
|
|
11139
|
+
}
|
|
11140
|
+
/**
|
|
11141
|
+
* 可视化危险点(用于调试)
|
|
11142
|
+
*/
|
|
11143
|
+
visualizeDangerPoints(result, layer) {
|
|
11144
|
+
const C = this.CesiumNS;
|
|
11145
|
+
const entities = [];
|
|
11146
|
+
result.unsafeSegments.forEach((seg) => {
|
|
11147
|
+
seg.dangerPoints.forEach((point, idx) => {
|
|
11148
|
+
const entity = layer.entities.add({
|
|
11149
|
+
position: point,
|
|
11150
|
+
point: {
|
|
11151
|
+
pixelSize: 10,
|
|
11152
|
+
color: C.Color.RED,
|
|
11153
|
+
outlineColor: C.Color.WHITE,
|
|
11154
|
+
outlineWidth: 2,
|
|
11155
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
|
11156
|
+
},
|
|
11157
|
+
label: {
|
|
11158
|
+
text: `\u26A0\uFE0F ${seg.fromIndex}-${seg.toIndex}`,
|
|
11159
|
+
font: "12px sans-serif",
|
|
11160
|
+
fillColor: C.Color.RED,
|
|
11161
|
+
outlineColor: C.Color.WHITE,
|
|
11162
|
+
outlineWidth: 2,
|
|
11163
|
+
style: C.LabelStyle.FILL_AND_OUTLINE,
|
|
11164
|
+
pixelOffset: new C.Cartesian2(0, -20),
|
|
11165
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY
|
|
11166
|
+
},
|
|
11167
|
+
properties: {
|
|
11168
|
+
_type: "safety-danger-point",
|
|
11169
|
+
_segmentFrom: seg.fromIndex,
|
|
11170
|
+
_segmentTo: seg.toIndex
|
|
11171
|
+
}
|
|
11172
|
+
});
|
|
11173
|
+
entities.push(entity);
|
|
11174
|
+
});
|
|
11175
|
+
});
|
|
11176
|
+
return entities;
|
|
11177
|
+
}
|
|
11178
|
+
/**
|
|
11179
|
+
* 清除危险点可视化
|
|
11180
|
+
*/
|
|
11181
|
+
clearDangerPoints(layer) {
|
|
11182
|
+
const C = this.CesiumNS;
|
|
11183
|
+
const toRemove = [];
|
|
11184
|
+
layer.entities.values.forEach((entity) => {
|
|
11185
|
+
const type = entity.properties?._type?.getValue?.(C.JulianDate.now());
|
|
11186
|
+
if (type === "safety-danger-point") {
|
|
11187
|
+
toRemove.push(entity);
|
|
11188
|
+
}
|
|
11189
|
+
});
|
|
11190
|
+
toRemove.forEach((entity) => {
|
|
11191
|
+
layer.entities.remove(entity);
|
|
11192
|
+
});
|
|
11193
|
+
}
|
|
11194
|
+
/**
|
|
11195
|
+
* 沿航线创建3D安全管道可视化(使用 Cylinder)
|
|
11196
|
+
* 每个航段创建一个水平圆柱体,圆柱轴心与航线重合
|
|
11197
|
+
* @param positions 航点位置数组
|
|
11198
|
+
* @param layer 实体图层
|
|
11199
|
+
* @param options 可视化选项
|
|
11200
|
+
*/
|
|
11201
|
+
visualizeSafetyTube(positions, layer, options) {
|
|
11202
|
+
const C = this.CesiumNS;
|
|
11203
|
+
const entities = [];
|
|
11204
|
+
const radius = options?.radius ?? 20;
|
|
11205
|
+
const color = options?.color ?? C.Color.YELLOW.withAlpha(0.3);
|
|
11206
|
+
const skipFirst = options?.skipFirstSegment ?? true;
|
|
11207
|
+
const startIndex = skipFirst ? 1 : 0;
|
|
11208
|
+
if (positions.length < startIndex + 2) {
|
|
11209
|
+
console.warn("[PathSafetyChecker] \u822A\u70B9\u4E0D\u8DB3\uFF0C\u65E0\u6CD5\u521B\u5EFA\u5B89\u5168\u7BA1\u9053");
|
|
11210
|
+
return entities;
|
|
11211
|
+
}
|
|
11212
|
+
console.log("[PathSafetyChecker] \u521B\u5EFA\u5B89\u5168\u7BA1\u9053\uFF08Cylinder \u65B9\u5F0F\uFF09");
|
|
11213
|
+
console.log(` \u534A\u5F84: ${radius}m`);
|
|
11214
|
+
console.log(` \u8D77\u59CB\u822A\u6BB5: ${startIndex}`);
|
|
11215
|
+
for (let i = startIndex; i < positions.length - 1; i++) {
|
|
11216
|
+
const from = positions[i];
|
|
11217
|
+
const to = positions[i + 1];
|
|
11218
|
+
const midPoint = C.Cartesian3.midpoint(from, to, new C.Cartesian3());
|
|
11219
|
+
const length = C.Cartesian3.distance(from, to);
|
|
11220
|
+
const direction = C.Cartesian3.subtract(to, from, new C.Cartesian3());
|
|
11221
|
+
C.Cartesian3.normalize(direction, direction);
|
|
11222
|
+
const localUp = C.Cartesian3.normalize(midPoint, new C.Cartesian3());
|
|
11223
|
+
const zAxis = direction;
|
|
11224
|
+
let xAxis = C.Cartesian3.cross(localUp, zAxis, new C.Cartesian3());
|
|
11225
|
+
if (C.Cartesian3.magnitude(xAxis) < 1e-3) {
|
|
11226
|
+
xAxis = C.Cartesian3.cross(new C.Cartesian3(1, 0, 0), zAxis, new C.Cartesian3());
|
|
11227
|
+
}
|
|
11228
|
+
C.Cartesian3.normalize(xAxis, xAxis);
|
|
11229
|
+
const yAxis = C.Cartesian3.cross(zAxis, xAxis, new C.Cartesian3());
|
|
11230
|
+
C.Cartesian3.normalize(yAxis, yAxis);
|
|
11231
|
+
const rotationMatrix = new C.Matrix3(
|
|
11232
|
+
xAxis.x,
|
|
11233
|
+
yAxis.x,
|
|
11234
|
+
zAxis.x,
|
|
11235
|
+
xAxis.y,
|
|
11236
|
+
yAxis.y,
|
|
11237
|
+
zAxis.y,
|
|
11238
|
+
xAxis.z,
|
|
11239
|
+
yAxis.z,
|
|
11240
|
+
zAxis.z
|
|
11241
|
+
);
|
|
11242
|
+
const orientation = C.Quaternion.fromRotationMatrix(rotationMatrix);
|
|
11243
|
+
console.log(`[\u822A\u6BB5 ${i}] \u957F\u5EA6: ${length.toFixed(1)}m, \u65B9\u5411: (${direction.x.toFixed(2)}, ${direction.y.toFixed(2)}, ${direction.z.toFixed(2)})`);
|
|
11244
|
+
const entity = layer.entities.add({
|
|
11245
|
+
position: midPoint,
|
|
11246
|
+
orientation,
|
|
11247
|
+
cylinder: {
|
|
11248
|
+
length,
|
|
11249
|
+
topRadius: radius,
|
|
11250
|
+
bottomRadius: radius,
|
|
11251
|
+
material: color,
|
|
11252
|
+
outline: true,
|
|
11253
|
+
outlineColor: C.Color.YELLOW,
|
|
11254
|
+
outlineWidth: 1
|
|
11255
|
+
},
|
|
11256
|
+
properties: {
|
|
11257
|
+
_type: "safety-tube",
|
|
11258
|
+
_segmentIndex: i
|
|
11259
|
+
}
|
|
11260
|
+
});
|
|
11261
|
+
entities.push(entity);
|
|
11262
|
+
}
|
|
11263
|
+
console.log(`[PathSafetyChecker] \u521B\u5EFA\u4E86 ${entities.length} \u4E2A\u822A\u6BB5\u5706\u67F1`);
|
|
11264
|
+
return entities;
|
|
11265
|
+
}
|
|
11266
|
+
/**
|
|
11267
|
+
* 清除安全管道可视化
|
|
11268
|
+
*/
|
|
11269
|
+
clearSafetyTube(layer) {
|
|
11270
|
+
const C = this.CesiumNS;
|
|
11271
|
+
const toRemove = [];
|
|
11272
|
+
layer.entities.values.forEach((entity) => {
|
|
11273
|
+
const type = entity.properties?._type?.getValue?.(C.JulianDate.now());
|
|
11274
|
+
if (type === "safety-tube") {
|
|
11275
|
+
toRemove.push(entity);
|
|
11276
|
+
}
|
|
11277
|
+
});
|
|
11278
|
+
toRemove.forEach((entity) => {
|
|
11279
|
+
layer.entities.remove(entity);
|
|
11280
|
+
});
|
|
11281
|
+
console.log(`[PathSafetyChecker] \u6E05\u9664\u4E86 ${toRemove.length} \u4E2A\u5B89\u5168\u7BA1\u9053`);
|
|
11282
|
+
}
|
|
11283
|
+
};
|
|
11284
|
+
|
|
11399
11285
|
// src/index.ts
|
|
11400
11286
|
var placeholder = { ready: true };
|
|
11401
11287
|
|
|
@@ -11404,9 +11290,9 @@ exports.CameraEventBus = CameraEventBus;
|
|
|
11404
11290
|
exports.CameraFOVController = CameraFOVController;
|
|
11405
11291
|
exports.CameraManager = CameraManager;
|
|
11406
11292
|
exports.Emitter = Emitter;
|
|
11407
|
-
exports.FlightPreviewController = FlightPreviewController;
|
|
11408
11293
|
exports.FrustumPyramid = FrustumPyramid;
|
|
11409
11294
|
exports.LayerManager = LayerManager;
|
|
11295
|
+
exports.PathSafetyChecker = PathSafetyChecker;
|
|
11410
11296
|
exports.PolygonEditor = PolygonEditor;
|
|
11411
11297
|
exports.SceneManager = SceneManager;
|
|
11412
11298
|
exports.Selector = Selector;
|